parent
							
								
									4c203631db
								
							
						
					
					
						commit
						4dacf292c2
					
				@ -1,5 +1,6 @@
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
import * as os from 'os';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
 | 
			
		||||
class BackendInfo {
 | 
			
		||||
  gitCommitHash = '';
 | 
			
		||||
@ -21,7 +22,7 @@ class BackendInfo {
 | 
			
		||||
    try {
 | 
			
		||||
      this.gitCommitHash = fs.readFileSync('../.git/refs/heads/master').toString().trim();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log('Could not load git commit info, skipping.');
 | 
			
		||||
      logger.err('Could not load git commit info, skipping.');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ import * as request from 'request';
 | 
			
		||||
import { BisqBlocks, BisqBlock, BisqTransaction, BisqStats, BisqTrade } from './interfaces';
 | 
			
		||||
import { Common } from '../common';
 | 
			
		||||
import { Block } from '../../interfaces';
 | 
			
		||||
 | 
			
		||||
import logger from '../../logger';
 | 
			
		||||
class Bisq {
 | 
			
		||||
  private static BLOCKS_JSON_FILE_PATH = '/all/blocks.json';
 | 
			
		||||
  private latestBlockHeight = 0;
 | 
			
		||||
@ -38,7 +38,7 @@ class Bisq {
 | 
			
		||||
 | 
			
		||||
  handleNewBitcoinBlock(block: Block): void {
 | 
			
		||||
    if (block.height - 2 > this.latestBlockHeight && this.latestBlockHeight !== 0) {
 | 
			
		||||
      console.log(`Bitcoin block height (#${block.height}) has diverged from the latest Bisq block height (#${this.latestBlockHeight}). Restarting watchers...`);
 | 
			
		||||
      logger.info(`Bitcoin block height (#${block.height}) has diverged from the latest Bisq block height (#${this.latestBlockHeight}). Restarting watchers...`);
 | 
			
		||||
      this.startTopDirectoryWatcher();
 | 
			
		||||
      this.startSubDirectoryWatcher();
 | 
			
		||||
    }
 | 
			
		||||
@ -82,7 +82,7 @@ class Bisq {
 | 
			
		||||
 | 
			
		||||
  private checkForBisqDataFolder() {
 | 
			
		||||
    if (!fs.existsSync(config.BISQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH)) {
 | 
			
		||||
      console.log(config.BISQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist. Make sure Bisq is running and the config is correct before starting the server.`);
 | 
			
		||||
      logger.info(config.BISQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist. Make sure Bisq is running and the config is correct before starting the server.`);
 | 
			
		||||
      return process.exit(1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -100,7 +100,7 @@ class Bisq {
 | 
			
		||||
        this.subdirectoryWatcher.close();
 | 
			
		||||
      }
 | 
			
		||||
      fsWait = setTimeout(() => {
 | 
			
		||||
        console.log(`Bisq restart detected. Resetting both watchers in 3 minutes.`);
 | 
			
		||||
        logger.info(`Bisq restart detected. Resetting both watchers in 3 minutes.`);
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          this.startTopDirectoryWatcher();
 | 
			
		||||
          this.startSubDirectoryWatcher();
 | 
			
		||||
@ -115,7 +115,7 @@ class Bisq {
 | 
			
		||||
      this.subdirectoryWatcher.close();
 | 
			
		||||
    }
 | 
			
		||||
    if (!fs.existsSync(config.BISQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH)) {
 | 
			
		||||
      console.log(config.BISQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist. Trying to restart sub directory watcher again in 3 minutes.`);
 | 
			
		||||
      logger.warn(config.BISQ_BLOCKS_DATA_PATH + Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist. Trying to restart sub directory watcher again in 3 minutes.`);
 | 
			
		||||
      setTimeout(() => this.startSubDirectoryWatcher(), 180000);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
@ -125,7 +125,7 @@ class Bisq {
 | 
			
		||||
        clearTimeout(fsWait);
 | 
			
		||||
      }
 | 
			
		||||
      fsWait = setTimeout(() => {
 | 
			
		||||
        console.log(`Change detected in the Bisq data folder.`);
 | 
			
		||||
        logger.info(`Change detected in the Bisq data folder.`);
 | 
			
		||||
        this.loadBisqDumpFile();
 | 
			
		||||
      }, 2000);
 | 
			
		||||
    });
 | 
			
		||||
@ -133,7 +133,7 @@ class Bisq {
 | 
			
		||||
 | 
			
		||||
  private updatePrice() {
 | 
			
		||||
    request('https://markets.bisq.network/api/trades/?market=bsq_btc', { json: true }, (err, res, trades: BisqTrade[]) => {
 | 
			
		||||
      if (err) { return console.log(err); }
 | 
			
		||||
      if (err) { return logger.err(err); }
 | 
			
		||||
 | 
			
		||||
      const prices: number[] = [];
 | 
			
		||||
      trades.forEach((trade) => {
 | 
			
		||||
@ -154,7 +154,7 @@ class Bisq {
 | 
			
		||||
      this.buildIndex();
 | 
			
		||||
      this.calculateStats();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log('loadBisqDumpFile() error.', e.message);
 | 
			
		||||
      logger.err('loadBisqDumpFile() error.' + e.message);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -198,7 +198,7 @@ class Bisq {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const time = new Date().getTime() - start;
 | 
			
		||||
    console.log('Bisq data index rebuilt in ' + time + ' ms');
 | 
			
		||||
    logger.info('Bisq data index rebuilt in ' + time + ' ms');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private calculateStats() {
 | 
			
		||||
@ -236,14 +236,14 @@ class Bisq {
 | 
			
		||||
  private async loadBisqBlocksDump(cacheData: string): Promise<void> {
 | 
			
		||||
    const start = new Date().getTime();
 | 
			
		||||
    if (cacheData && cacheData.length !== 0) {
 | 
			
		||||
      console.log('Processing Bisq data dump...');
 | 
			
		||||
      logger.info('Processing Bisq data dump...');
 | 
			
		||||
      const data: BisqBlocks = JSON.parse(cacheData);
 | 
			
		||||
      if (data.blocks && data.blocks.length !== this.blocks.length) {
 | 
			
		||||
        this.blocks = data.blocks.filter((block) => block.txs.length > 0);
 | 
			
		||||
        this.blocks.reverse();
 | 
			
		||||
        this.latestBlockHeight = data.chainHeight;
 | 
			
		||||
        const time = new Date().getTime() - start;
 | 
			
		||||
        console.log('Bisq dump processed in ' + time + ' ms');
 | 
			
		||||
        logger.info('Bisq dump processed in ' + time + ' ms');
 | 
			
		||||
      } else {
 | 
			
		||||
        throw new Error(`Bisq dump didn't contain any blocks`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ const config = require('../../../mempool-config.json');
 | 
			
		||||
import * as fs from 'fs';
 | 
			
		||||
import { OffersData as OffersData, TradesData, Currency } from './interfaces';
 | 
			
		||||
import bisqMarket from './markets-api';
 | 
			
		||||
import logger from '../../logger';
 | 
			
		||||
 | 
			
		||||
class Bisq {
 | 
			
		||||
  private static FOLDER_WATCH_CHANGE_DETECTION_DEBOUNCE = 4000;
 | 
			
		||||
@ -30,7 +31,7 @@ class Bisq {
 | 
			
		||||
 | 
			
		||||
  private checkForBisqDataFolder() {
 | 
			
		||||
    if (!fs.existsSync(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency)) {
 | 
			
		||||
      console.log(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency + ` doesn't exist. Make sure Bisq is running and the config is correct before starting the server.`);
 | 
			
		||||
      logger.err(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency + ` doesn't exist. Make sure Bisq is running and the config is correct before starting the server.`);
 | 
			
		||||
      return process.exit(1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -40,7 +41,7 @@ class Bisq {
 | 
			
		||||
      this.subdirectoryWatcher.close();
 | 
			
		||||
    }
 | 
			
		||||
    if (!fs.existsSync(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency)) {
 | 
			
		||||
      console.log(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency + ` doesn't exist. Trying to restart sub directory watcher again in 3 minutes.`);
 | 
			
		||||
      logger.warn(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency + ` doesn't exist. Trying to restart sub directory watcher again in 3 minutes.`);
 | 
			
		||||
      setTimeout(() => this.startBisqDirectoryWatcher(), 180000);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
@ -50,7 +51,7 @@ class Bisq {
 | 
			
		||||
        clearTimeout(fsWait);
 | 
			
		||||
      }
 | 
			
		||||
      fsWait = setTimeout(() => {
 | 
			
		||||
        console.log(`Change detected in the Bisq market data folder.`);
 | 
			
		||||
        logger.info(`Change detected in the Bisq market data folder.`);
 | 
			
		||||
        this.loadBisqDumpFile();
 | 
			
		||||
      }, Bisq.FOLDER_WATCH_CHANGE_DETECTION_DEBOUNCE);
 | 
			
		||||
    });
 | 
			
		||||
@ -65,7 +66,7 @@ class Bisq {
 | 
			
		||||
      if (cryptoMtime > this.cryptoCurrencyLastMtime || fiatMtime > this.fiatCurrencyLastMtime) {
 | 
			
		||||
        const cryptoCurrencyData = await this.loadData<Currency[]>(Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency);
 | 
			
		||||
        const fiatCurrencyData = await this.loadData<Currency[]>(Bisq.MARKET_JSON_FILE_PATHS.fiatCurrency);
 | 
			
		||||
        console.log('Updating Bisq Market Currency Data');
 | 
			
		||||
        logger.info('Updating Bisq Market Currency Data');
 | 
			
		||||
        bisqMarket.setCurrencyData(cryptoCurrencyData, fiatCurrencyData);
 | 
			
		||||
        if (cryptoMtime > this.cryptoCurrencyLastMtime) {
 | 
			
		||||
          this.cryptoCurrencyLastMtime = cryptoMtime;
 | 
			
		||||
@ -78,7 +79,7 @@ class Bisq {
 | 
			
		||||
      const offersMtime = this.getFileMtime(Bisq.MARKET_JSON_FILE_PATHS.offers);
 | 
			
		||||
      if (offersMtime > this.offersLastMtime) {
 | 
			
		||||
        const offersData = await this.loadData<OffersData[]>(Bisq.MARKET_JSON_FILE_PATHS.offers);
 | 
			
		||||
        console.log('Updating Bisq Market Offers Data');
 | 
			
		||||
        logger.info('Updating Bisq Market Offers Data');
 | 
			
		||||
        bisqMarket.setOffersData(offersData);
 | 
			
		||||
        this.offersLastMtime = offersMtime;
 | 
			
		||||
        marketsDataUpdated = true;
 | 
			
		||||
@ -86,7 +87,7 @@ class Bisq {
 | 
			
		||||
      const tradesMtime = this.getFileMtime(Bisq.MARKET_JSON_FILE_PATHS.trades);
 | 
			
		||||
      if (tradesMtime > this.tradesLastMtime) {
 | 
			
		||||
        const tradesData = await this.loadData<TradesData[]>(Bisq.MARKET_JSON_FILE_PATHS.trades);
 | 
			
		||||
        console.log('Updating Bisq Market Trades Data');
 | 
			
		||||
        logger.info('Updating Bisq Market Trades Data');
 | 
			
		||||
        bisqMarket.setTradesData(tradesData);
 | 
			
		||||
        this.tradesLastMtime = tradesMtime;
 | 
			
		||||
        marketsDataUpdated = true;
 | 
			
		||||
@ -94,10 +95,10 @@ class Bisq {
 | 
			
		||||
      if (marketsDataUpdated) {
 | 
			
		||||
        bisqMarket.updateCache();
 | 
			
		||||
        const time = new Date().getTime() - start;
 | 
			
		||||
        console.log('Bisq market data updated in ' + time + ' ms');
 | 
			
		||||
        logger.info('Bisq market data updated in ' + time + ' ms');
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log('loadBisqMarketDataDumpFile() error.', e.message);
 | 
			
		||||
      logger.err('loadBisqMarketDataDumpFile() error.' + e.message);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
const config = require('../../mempool-config.json');
 | 
			
		||||
import bitcoinApi from './bitcoin/electrs-api';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
import memPool from './mempool';
 | 
			
		||||
import { Block, TransactionExtended, TransactionMinerInfo } from '../interfaces';
 | 
			
		||||
import { Common } from './common';
 | 
			
		||||
@ -35,7 +36,7 @@ class Blocks {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (blockHeightTip - this.currentBlockHeight > config.INITIAL_BLOCK_AMOUNT * 2) {
 | 
			
		||||
        console.log(`${blockHeightTip - this.currentBlockHeight} blocks since tip. Fast forwarding to the ${config.INITIAL_BLOCK_AMOUNT} recent blocks`);
 | 
			
		||||
        logger.info(`${blockHeightTip - this.currentBlockHeight} blocks since tip. Fast forwarding to the ${config.INITIAL_BLOCK_AMOUNT} recent blocks`);
 | 
			
		||||
        this.currentBlockHeight = blockHeightTip - config.INITIAL_BLOCK_AMOUNT;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -51,7 +52,7 @@ class Blocks {
 | 
			
		||||
          this.currentBlockHeight = blockHeightTip;
 | 
			
		||||
        } else {
 | 
			
		||||
          this.currentBlockHeight++;
 | 
			
		||||
          console.log(`New block found (#${this.currentBlockHeight})!`);
 | 
			
		||||
          logger.info(`New block found (#${this.currentBlockHeight})!`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const blockHash = await bitcoinApi.getBlockHash(this.currentBlockHeight);
 | 
			
		||||
@ -69,7 +70,7 @@ class Blocks {
 | 
			
		||||
            transactions.push(mempool[txIds[i]]);
 | 
			
		||||
            found++;
 | 
			
		||||
          } else {
 | 
			
		||||
            console.log(`Fetching block tx ${i} of ${txIds.length}`);
 | 
			
		||||
            logger.info(`Fetching block tx ${i} of ${txIds.length}`);
 | 
			
		||||
            const tx = await memPool.getTransactionExtended(txIds[i]);
 | 
			
		||||
            if (tx) {
 | 
			
		||||
              transactions.push(tx);
 | 
			
		||||
@ -78,7 +79,7 @@ class Blocks {
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        console.log(`${found} of ${txIds.length} found in mempool. ${notFound} not found.`);
 | 
			
		||||
        logger.info(`${found} of ${txIds.length} found in mempool. ${notFound} not found.`);
 | 
			
		||||
 | 
			
		||||
        block.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
 | 
			
		||||
        block.coinbaseTx = this.stripCoinbaseTransaction(transactions[0]);
 | 
			
		||||
@ -101,7 +102,7 @@ class Blocks {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      console.log('updateBlocks error', err);
 | 
			
		||||
      logger.err('updateBlocks error' + err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ import * as fs from 'fs';
 | 
			
		||||
import * as process from 'process';
 | 
			
		||||
import memPool from './mempool';
 | 
			
		||||
import blocks from './blocks';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
 | 
			
		||||
class DiskCache {
 | 
			
		||||
  static FILE_NAME = './cache.json';
 | 
			
		||||
@ -26,13 +27,13 @@ class DiskCache {
 | 
			
		||||
      mempool: memPool.getMempool(),
 | 
			
		||||
      blocks: blocks.getBlocks(),
 | 
			
		||||
    }));
 | 
			
		||||
    console.log('Mempool and blocks data saved to disk cache');
 | 
			
		||||
    logger.info('Mempool and blocks data saved to disk cache');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  loadMempoolCache() {
 | 
			
		||||
    const cacheData = this.loadData();
 | 
			
		||||
    if (cacheData) {
 | 
			
		||||
      console.log('Restoring mempool and blocks data from disk cache');
 | 
			
		||||
      logger.info('Restoring mempool and blocks data from disk cache');
 | 
			
		||||
      const data = JSON.parse(cacheData);
 | 
			
		||||
      memPool.setMempool(data.mempool);
 | 
			
		||||
      blocks.setBlocks(data.blocks);
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
const config = require('../../mempool-config.json');
 | 
			
		||||
import * as request from 'request';
 | 
			
		||||
import { DB } from '../database';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
 | 
			
		||||
class Donations {
 | 
			
		||||
  private notifyDonationStatusCallback: ((invoiceId: string) => void) | undefined;
 | 
			
		||||
@ -45,6 +46,7 @@ class Donations {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async $handleWebhookRequest(data: any) {
 | 
			
		||||
    logger.debug('Received BTCPayServer webhook data: ' + JSON.stringify(data));
 | 
			
		||||
    if (!data || !data.id) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
@ -70,21 +72,24 @@ class Donations {
 | 
			
		||||
        imageUrl = hiveData.imageUrl;
 | 
			
		||||
        handle = hiveData.screenName;
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        console.log('Error fetching twitter image', e.message);
 | 
			
		||||
        logger.err('Error fetching twitter image' + e.message);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    logger.debug('Creating database entry for donation with invoice id: ' + response.id);
 | 
			
		||||
    this.$addDonationToDatabase(response.btcPaid, handle, response.id, imageUrl);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getStatus(id: string): Promise<any> {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      logger.debug('Fetching status for invoice: ' + id);
 | 
			
		||||
      request.get({
 | 
			
		||||
        uri: '/invoices/' + id,
 | 
			
		||||
        json: true,
 | 
			
		||||
        ...this.options,
 | 
			
		||||
      }, (err, res, body) => {
 | 
			
		||||
        if (err) { return reject(err); }
 | 
			
		||||
        logger.debug('Invoice status received: ' + JSON.stringify(body.data));
 | 
			
		||||
        resolve(body.data);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
@ -98,7 +103,7 @@ class Donations {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return rows;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log('$getDonationsFromDatabase() error', e);
 | 
			
		||||
      logger.err('$getDonationsFromDatabase() error' + e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -115,17 +120,19 @@ class Donations {
 | 
			
		||||
      const [result]: any = await connection.query(query, params);
 | 
			
		||||
      connection.release();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log('$addDonationToDatabase() error', e);
 | 
			
		||||
      logger.err('$addDonationToDatabase() error' + e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $getTwitterImageUrl(handle: string): Promise<any> {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      logger.debug('Fetching Hive.one data...');
 | 
			
		||||
      request.get({
 | 
			
		||||
        uri: `https://api.hive.one/v1/influencers/screen_name/${handle}/?format=json`,
 | 
			
		||||
        json: true,
 | 
			
		||||
      }, (err, res, body) => {
 | 
			
		||||
        if (err) { return reject(err); }
 | 
			
		||||
        logger.debug('Hive.one data fetched:' + JSON.stringify(body.data));
 | 
			
		||||
        resolve(body.data);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
import * as request from 'request';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
 | 
			
		||||
class FiatConversion {
 | 
			
		||||
  private tickers = {
 | 
			
		||||
@ -10,7 +11,7 @@ class FiatConversion {
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  public startService() {
 | 
			
		||||
    console.log('Starting currency rates service');
 | 
			
		||||
    logger.info('Starting currency rates service');
 | 
			
		||||
    setInterval(this.updateCurrency.bind(this), 1000 * 60 * 60);
 | 
			
		||||
    this.updateCurrency();
 | 
			
		||||
  }
 | 
			
		||||
@ -21,7 +22,7 @@ class FiatConversion {
 | 
			
		||||
 | 
			
		||||
  private updateCurrency() {
 | 
			
		||||
    request('https://api.opennode.co/v1/rates', { json: true }, (err, res, body) => {
 | 
			
		||||
      if (err) { return console.log(err); }
 | 
			
		||||
      if (err) { return logger.info(err); }
 | 
			
		||||
      if (body && body.data) {
 | 
			
		||||
        this.tickers = body.data;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
const config = require('../../mempool-config.json');
 | 
			
		||||
import bitcoinApi from './bitcoin/electrs-api';
 | 
			
		||||
import { MempoolInfo, TransactionExtended, Transaction, VbytesPerSecond } from '../interfaces';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
import { Common } from './common';
 | 
			
		||||
 | 
			
		||||
class Mempool {
 | 
			
		||||
@ -49,8 +50,8 @@ class Mempool {
 | 
			
		||||
  public async updateMemPoolInfo() {
 | 
			
		||||
    try {
 | 
			
		||||
      this.mempoolInfo = await bitcoinApi.getMempoolInfo();
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      console.log('Error getMempoolInfo', err);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('Error getMempoolInfo ' + e.message || e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -87,13 +88,13 @@ class Mempool {
 | 
			
		||||
        firstSeen: Math.round((new Date().getTime() / 1000)),
 | 
			
		||||
      }, transaction);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log(txId + ' not found');
 | 
			
		||||
      logger.warn(txId + ' not found');
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async updateMempool() {
 | 
			
		||||
    console.log('Updating mempool');
 | 
			
		||||
    logger.info('Updating mempool');
 | 
			
		||||
    const start = new Date().getTime();
 | 
			
		||||
    let hasChange: boolean = false;
 | 
			
		||||
    const currentMempoolSize = Object.keys(this.mempoolCache).length;
 | 
			
		||||
@ -118,13 +119,13 @@ class Mempool {
 | 
			
		||||
            }
 | 
			
		||||
            hasChange = true;
 | 
			
		||||
            if (diff > 0) {
 | 
			
		||||
              console.log('Fetched transaction ' + txCount + ' / ' + diff);
 | 
			
		||||
              logger.info('Fetched transaction ' + txCount + ' / ' + diff);
 | 
			
		||||
            } else {
 | 
			
		||||
              console.log('Fetched transaction ' + txCount);
 | 
			
		||||
              logger.info('Fetched transaction ' + txCount);
 | 
			
		||||
            }
 | 
			
		||||
            newTransactions.push(transaction);
 | 
			
		||||
          } else {
 | 
			
		||||
            console.log('Error finding transaction in mempool.');
 | 
			
		||||
            logger.err('Error finding transaction in mempool.');
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -137,10 +138,10 @@ class Mempool {
 | 
			
		||||
      if (this.mempoolProtection === 0 && transactions.length / currentMempoolSize <= 0.80) {
 | 
			
		||||
        this.mempoolProtection = 1;
 | 
			
		||||
        this.inSync = false;
 | 
			
		||||
        console.log('Mempool clear protection triggered.');
 | 
			
		||||
        logger.info('Mempool clear protection triggered.');
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          this.mempoolProtection = 2;
 | 
			
		||||
          console.log('Mempool clear protection resumed.');
 | 
			
		||||
          logger.info('Mempool clear protection resumed.');
 | 
			
		||||
        }, 1000 * 60 * 2);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -170,7 +171,7 @@ class Mempool {
 | 
			
		||||
 | 
			
		||||
      if (!this.inSync && transactions.length === Object.keys(newMempool).length) {
 | 
			
		||||
        this.inSync = true;
 | 
			
		||||
        console.log('The mempool is now in sync!');
 | 
			
		||||
        logger.info('The mempool is now in sync!');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this.mempoolChangedCallback && (hasChange || deletedTransactions.length)) {
 | 
			
		||||
@ -180,10 +181,10 @@ class Mempool {
 | 
			
		||||
 | 
			
		||||
      const end = new Date().getTime();
 | 
			
		||||
      const time = end - start;
 | 
			
		||||
      console.log(`New mempool size: ${Object.keys(newMempool).length} Change: ${diff}`);
 | 
			
		||||
      console.log('Mempool updated in ' + time / 1000 + ' seconds');
 | 
			
		||||
      logger.info(`New mempool size: ${Object.keys(newMempool).length} Change: ${diff}`);
 | 
			
		||||
      logger.info('Mempool updated in ' + time / 1000 + ' seconds');
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      console.log('getRawMempool error.', err);
 | 
			
		||||
      logger.err('getRawMempool error. ' + err.message || err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import memPool from './mempool';
 | 
			
		||||
import { DB } from '../database';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
 | 
			
		||||
import { Statistic, TransactionExtended, OptimizedStatistic } from '../interfaces';
 | 
			
		||||
 | 
			
		||||
@ -14,7 +15,7 @@ class Statistics {
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  public startStatistics(): void {
 | 
			
		||||
    console.log('Starting statistics service');
 | 
			
		||||
    logger.info('Starting statistics service');
 | 
			
		||||
 | 
			
		||||
    const now = new Date();
 | 
			
		||||
    const nextInterval = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(),
 | 
			
		||||
@ -37,7 +38,7 @@ class Statistics {
 | 
			
		||||
    const txPerSecond = memPool.getTxPerSecond();
 | 
			
		||||
    const vBytesPerSecond = memPool.getVBytesPerSecond();
 | 
			
		||||
 | 
			
		||||
    console.log('Running statistics');
 | 
			
		||||
    logger.info('Running statistics');
 | 
			
		||||
 | 
			
		||||
    let memPoolArray: TransactionExtended[] = [];
 | 
			
		||||
    for (const i in currentMempool) {
 | 
			
		||||
@ -233,7 +234,7 @@ class Statistics {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return result.insertId;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log('$create() error', e);
 | 
			
		||||
      logger.err('$create() error' + e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -292,7 +293,7 @@ class Statistics {
 | 
			
		||||
        return this.mapStatisticToOptimizedStatistic([rows[0]])[0];
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log('$list2H() error', e);
 | 
			
		||||
      logger.err('$list2H() error' + e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -304,7 +305,7 @@ class Statistics {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log('$list2H() error', e);
 | 
			
		||||
      logger.err('$list2H() error' + e);
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -329,7 +330,7 @@ class Statistics {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log('$list1W() error', e);
 | 
			
		||||
      logger.err('$list1W() error' + e);
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -342,7 +343,7 @@ class Statistics {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log('$list1M() error', e);
 | 
			
		||||
      logger.err('$list1M() error' + e);
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -355,7 +356,7 @@ class Statistics {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log('$list3M() error', e);
 | 
			
		||||
      logger.err('$list3M() error' + e);
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -368,7 +369,7 @@ class Statistics {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log('$list6M() error', e);
 | 
			
		||||
      logger.err('$list6M() error' + e);
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -381,7 +382,7 @@ class Statistics {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log('$list6M() error', e);
 | 
			
		||||
      logger.err('$list6M() error' + e);
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
const config = require('../../mempool-config.json');
 | 
			
		||||
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
import * as WebSocket from 'ws';
 | 
			
		||||
import { Block, TransactionExtended, WebsocketResponse, MempoolBlock, OptimizedStatistic } from '../interfaces';
 | 
			
		||||
import blocks from './blocks';
 | 
			
		||||
@ -107,7 +107,7 @@ class WebsocketHandler {
 | 
			
		||||
            client.send(JSON.stringify(response));
 | 
			
		||||
          }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          console.log(e);
 | 
			
		||||
          logger.err(e);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
const config = require('../mempool-config.json');
 | 
			
		||||
import { createPool } from 'mysql2/promise';
 | 
			
		||||
import logger from './logger';
 | 
			
		||||
 | 
			
		||||
export class DB {
 | 
			
		||||
  static pool = createPool({
 | 
			
		||||
@ -16,11 +17,11 @@ export class DB {
 | 
			
		||||
export async function checkDbConnection() {
 | 
			
		||||
  try {
 | 
			
		||||
    const connection = await DB.pool.getConnection();
 | 
			
		||||
    console.log('Database connection established.');
 | 
			
		||||
    logger.info('Database connection established.');
 | 
			
		||||
    connection.release();
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.log('Could not connect to database.');
 | 
			
		||||
    console.log(e);
 | 
			
		||||
    logger.err('Could not connect to database.');
 | 
			
		||||
    logger.err(e);
 | 
			
		||||
    process.exit(1);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ import fiatConversion from './api/fiat-conversion';
 | 
			
		||||
import bisq from './api/bisq/bisq';
 | 
			
		||||
import bisqMarkets from './api/bisq/markets';
 | 
			
		||||
import donations from './api/donations';
 | 
			
		||||
import logger from './logger';
 | 
			
		||||
 | 
			
		||||
class Server {
 | 
			
		||||
  private wss: WebSocket.Server | undefined;
 | 
			
		||||
@ -35,7 +36,7 @@ class Server {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (cluster.isMaster) {
 | 
			
		||||
      console.log(`Mempool Server is running on port ${config.HTTP_PORT}`);
 | 
			
		||||
      logger.info(`Mempool Server is running on port ${config.HTTP_PORT}`);
 | 
			
		||||
 | 
			
		||||
      const numCPUs = config.CLUSTER_NUM_CORES;
 | 
			
		||||
      for (let i = 0; i < numCPUs; i++) {
 | 
			
		||||
@ -46,7 +47,7 @@ class Server {
 | 
			
		||||
 | 
			
		||||
      cluster.on('exit', (worker, code, signal) => {
 | 
			
		||||
        const workerId = worker.process['env'].workerId;
 | 
			
		||||
        console.log(`Mempool Worker PID #${worker.process.pid} workerId: ${workerId} died. Restarting in 10 seconds...`, signal || code);
 | 
			
		||||
        logger.info(`Mempool Worker PID #${worker.process.pid} workerId: ${workerId} died. Restarting in 10 seconds... ${signal || code}`);
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          const env = { workerId: workerId };
 | 
			
		||||
          const newWorker = cluster.fork(env);
 | 
			
		||||
@ -104,9 +105,9 @@ class Server {
 | 
			
		||||
 | 
			
		||||
    this.server.listen(config.HTTP_PORT, () => {
 | 
			
		||||
      if (worker) {
 | 
			
		||||
        console.log(`Mempool Server worker #${process.pid} started`);
 | 
			
		||||
        logger.info(`Mempool Server worker #${process.pid} started`);
 | 
			
		||||
      } else {
 | 
			
		||||
        console.log(`Mempool Server is running on port ${config.HTTP_PORT}`);
 | 
			
		||||
        logger.info(`Mempool Server is running on port ${config.HTTP_PORT}`);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										155
									
								
								backend/src/logger.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								backend/src/logger.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,155 @@
 | 
			
		||||
import * as dgram from 'dgram';
 | 
			
		||||
 | 
			
		||||
class Logger {
 | 
			
		||||
  static priorities = {
 | 
			
		||||
    emerg: 0,
 | 
			
		||||
    alert: 1,
 | 
			
		||||
    crit: 2,
 | 
			
		||||
    err: 3,
 | 
			
		||||
    warn: 4,
 | 
			
		||||
    notice: 5,
 | 
			
		||||
    info: 6,
 | 
			
		||||
    debug: 7
 | 
			
		||||
  };
 | 
			
		||||
  static facilities = {
 | 
			
		||||
    kern: 0,
 | 
			
		||||
    user: 1,
 | 
			
		||||
    mail: 2,
 | 
			
		||||
    daemon: 3,
 | 
			
		||||
    auth: 4,
 | 
			
		||||
    syslog: 5,
 | 
			
		||||
    lpr: 6,
 | 
			
		||||
    news: 7,
 | 
			
		||||
    uucp: 8,
 | 
			
		||||
    local0: 16,
 | 
			
		||||
    local1: 17,
 | 
			
		||||
    local2: 18,
 | 
			
		||||
    local3: 19,
 | 
			
		||||
    local4: 20,
 | 
			
		||||
    local5: 21,
 | 
			
		||||
    local6: 22,
 | 
			
		||||
    local7: 23
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  public emerg: ((msg: string) => void);
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  public alert: ((msg: string) => void);
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  public crit: ((msg: string) => void);
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  public err: ((msg: string) => void);
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  public warn: ((msg: string) => void);
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  public notice: ((msg: string) => void);
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  public info: ((msg: string) => void);
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  public debug: ((msg: string) => void);
 | 
			
		||||
 | 
			
		||||
  private name = 'mempool';
 | 
			
		||||
  private fac: any;
 | 
			
		||||
  private loghost: string;
 | 
			
		||||
  private logport: number;
 | 
			
		||||
 | 
			
		||||
  constructor(fac) {
 | 
			
		||||
    let prio;
 | 
			
		||||
    this.fac = fac != null ? fac : Logger.facilities.local0;
 | 
			
		||||
    this.loghost = '127.0.0.1';
 | 
			
		||||
    this.logport = 514;
 | 
			
		||||
    for (prio in Logger.priorities) {
 | 
			
		||||
      if (true) {
 | 
			
		||||
        this.addprio(prio);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private addprio(prio): void {
 | 
			
		||||
    this[prio] = (function(_this) {
 | 
			
		||||
      return function(msg) {
 | 
			
		||||
        return _this.msg(prio, msg);
 | 
			
		||||
      };
 | 
			
		||||
    })(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private tag() {
 | 
			
		||||
    let e, stack, stacklevel, tag;
 | 
			
		||||
    stacklevel = 4;
 | 
			
		||||
    try {
 | 
			
		||||
      const err = new Error().stack;
 | 
			
		||||
      if (err) {
 | 
			
		||||
        stack = err.replace(/^\s+at\s+/gm, '').replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@').split('\n');
 | 
			
		||||
        tag = stack[stacklevel].split(' ')[1];
 | 
			
		||||
        tag = '(' + tag.split('/').reverse()[0];
 | 
			
		||||
      } else {
 | 
			
		||||
        tag = '';
 | 
			
		||||
      }
 | 
			
		||||
    } catch (_error) {
 | 
			
		||||
      e = _error;
 | 
			
		||||
      tag = '';
 | 
			
		||||
    }
 | 
			
		||||
    return tag;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private msg(priority, msg) {
 | 
			
		||||
    let consolemsg, prionum, syslogmsg;
 | 
			
		||||
    if (typeof msg === 'string' && msg.length > 0) {
 | 
			
		||||
      while (msg[msg.length - 1].charCodeAt(0) === 10) {
 | 
			
		||||
        msg = msg.slice(0, msg.length - 1);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    prionum = Logger.priorities[priority] || Logger.priorities.info;
 | 
			
		||||
    const tag = this.tag();
 | 
			
		||||
    syslogmsg = `<${(this.fac * 8 + prionum)}> ${this.name}[${process.pid}]: ${tag}: ${priority.toUpperCase()} ${msg}`;
 | 
			
		||||
    consolemsg = `${this.ts()} [${process.pid}] ${priority.toUpperCase()}: ${msg}`;
 | 
			
		||||
 | 
			
		||||
    this.syslog(syslogmsg);
 | 
			
		||||
    if (priority === 'warning') {
 | 
			
		||||
      priority = 'warn';
 | 
			
		||||
    }
 | 
			
		||||
    if (priority === 'debug') {
 | 
			
		||||
      priority = 'info';
 | 
			
		||||
    }
 | 
			
		||||
    if (priority === 'err') {
 | 
			
		||||
      priority = 'error';
 | 
			
		||||
    }
 | 
			
		||||
    return (console[priority] || console.error)(consolemsg);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private syslog(msg) {
 | 
			
		||||
    let client, msgbuf;
 | 
			
		||||
    msgbuf = Buffer.from(msg);
 | 
			
		||||
    client = dgram.createSocket('udp4');
 | 
			
		||||
    client.send(msgbuf, 0, msgbuf.length, this.logport, this.loghost, function(err, bytes) {
 | 
			
		||||
      if (err) {
 | 
			
		||||
        console.log(err);
 | 
			
		||||
      }
 | 
			
		||||
      client.close();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private leadZero(n: number): number | string {
 | 
			
		||||
    if (n < 10) {
 | 
			
		||||
      return '0' + n;
 | 
			
		||||
    }
 | 
			
		||||
    return n;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private ts() {
 | 
			
		||||
    let day, dt, hours, minutes, month, months, seconds;
 | 
			
		||||
    dt = new Date();
 | 
			
		||||
    hours = this.leadZero(dt.getHours());
 | 
			
		||||
    minutes = this.leadZero(dt.getMinutes());
 | 
			
		||||
    seconds = this.leadZero(dt.getSeconds());
 | 
			
		||||
    month = dt.getMonth();
 | 
			
		||||
    day = dt.getDate();
 | 
			
		||||
    if (day < 10) {
 | 
			
		||||
      day = ' ' + day;
 | 
			
		||||
    }
 | 
			
		||||
    months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
 | 
			
		||||
    return months[month] + ' ' + day + ' ' + hours + ':' + minutes + ':' + seconds;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new Logger(Logger.facilities.local7);
 | 
			
		||||
@ -10,6 +10,7 @@ import bisqMarket from './api/bisq/markets-api';
 | 
			
		||||
import { RequiredSpec } from './interfaces';
 | 
			
		||||
import { MarketsApiError } from './api/bisq/interfaces';
 | 
			
		||||
import donations from './api/donations';
 | 
			
		||||
import logger from './logger';
 | 
			
		||||
 | 
			
		||||
class Routes {
 | 
			
		||||
  private cache = {};
 | 
			
		||||
@ -28,7 +29,7 @@ class Routes {
 | 
			
		||||
    this.cache['3m'] = await statistics.$list3M();
 | 
			
		||||
    this.cache['6m'] = await statistics.$list6M();
 | 
			
		||||
    this.cache['1y'] = await statistics.$list1Y();
 | 
			
		||||
    console.log('Statistics cache created');
 | 
			
		||||
    logger.info('Statistics cache created');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async get2HStatistics(req: Request, res: Response) {
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ export class AudioService {
 | 
			
		||||
    this.audio.src = '../../../resources/sounds/' + name + '.mp3';
 | 
			
		||||
    this.audio.load();
 | 
			
		||||
    this.audio.play().catch((e) => {
 | 
			
		||||
      console.log('Play sound failed', e);
 | 
			
		||||
      console.log('Play sound failed' + e);
 | 
			
		||||
    });
 | 
			
		||||
    setTimeout(() => this.isPlaying = false, 100);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user