UNPKG

sparrow-controllers

Version:

Collection of platform-agnostic modules for creating secure data models for cryptocurrency wallets

189 lines 7.77 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CurrencyRateController = void 0; const async_mutex_1 = require("async-mutex"); const BaseControllerV2_1 = require("../BaseControllerV2"); const util_1 = require("../util"); const crypto_compare_1 = require("../apis/crypto-compare"); const name = 'CurrencyRateController'; const metadata = { conversionDate: { persist: true, anonymous: true }, conversionRate: { persist: true, anonymous: true }, currentCurrency: { persist: true, anonymous: true }, nativeCurrency: { persist: true, anonymous: true }, pendingCurrentCurrency: { persist: false, anonymous: true }, pendingNativeCurrency: { persist: false, anonymous: true }, usdConversionRate: { persist: true, anonymous: true }, }; const defaultState = { conversionDate: 0, conversionRate: 0, currentCurrency: 'usd', nativeCurrency: 'ETH', pendingCurrentCurrency: null, pendingNativeCurrency: null, usdConversionRate: null, }; /** * Controller that passively polls on a set interval for an exchange rate from the current base * asset to the current currency */ class CurrencyRateController extends BaseControllerV2_1.BaseController { /** * Creates a CurrencyRateController instance. * * @param options - Constructor options. * @param options.includeUsdRate - Keep track of the USD rate in addition to the current currency rate. * @param options.interval - The polling interval, in milliseconds. * @param options.messenger - A reference to the messaging system. * @param options.state - Initial state to set on this controller. * @param options.fetchExchangeRate - Fetches the exchange rate from an external API. This option is primarily meant for use in unit tests. */ constructor({ includeUsdRate = false, interval = 180000, messenger, state, fetchExchangeRate = crypto_compare_1.fetchExchangeRate, }) { super({ name, metadata, messenger, state: Object.assign(Object.assign({}, defaultState), state), }); this.mutex = new async_mutex_1.Mutex(); this.includeUsdRate = includeUsdRate; this.intervalDelay = interval; this.fetchExchangeRate = fetchExchangeRate; } /** * Start polling for the currency rate. */ start() { return __awaiter(this, void 0, void 0, function* () { yield this.startPolling(); }); } /** * Stop polling for the currency rate. */ stop() { this.stopPolling(); } /** * Prepare to discard this controller. * * This stops any active polling. */ destroy() { super.destroy(); this.stopPolling(); } /** * Sets a currency to track. * * @param currentCurrency - ISO 4217 currency code. */ setCurrentCurrency(currentCurrency) { return __awaiter(this, void 0, void 0, function* () { this.update((state) => { state.pendingCurrentCurrency = currentCurrency; }); yield this.updateExchangeRate(); }); } /** * Sets a new native currency. * * @param symbol - Symbol for the base asset. */ setNativeCurrency(symbol) { return __awaiter(this, void 0, void 0, function* () { this.update((state) => { state.pendingNativeCurrency = symbol; }); yield this.updateExchangeRate(); }); } stopPolling() { if (this.intervalId) { clearInterval(this.intervalId); } } /** * Starts a new polling interval. */ startPolling() { return __awaiter(this, void 0, void 0, function* () { this.stopPolling(); // TODO: Expose polling currency rate update errors yield (0, util_1.safelyExecute)(() => this.updateExchangeRate()); this.intervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () { yield (0, util_1.safelyExecute)(() => this.updateExchangeRate()); }), this.intervalDelay); }); } /** * Updates exchange rate for the current currency. * * @returns The controller state. */ updateExchangeRate() { return __awaiter(this, void 0, void 0, function* () { const releaseLock = yield this.mutex.acquire(); const { currentCurrency: stateCurrentCurrency, nativeCurrency: stateNativeCurrency, pendingCurrentCurrency, pendingNativeCurrency, } = this.state; let conversionDate = null; let conversionRate = null; let usdConversionRate = null; const currentCurrency = pendingCurrentCurrency !== null && pendingCurrentCurrency !== void 0 ? pendingCurrentCurrency : stateCurrentCurrency; const nativeCurrency = pendingNativeCurrency !== null && pendingNativeCurrency !== void 0 ? pendingNativeCurrency : stateNativeCurrency; try { if (currentCurrency && nativeCurrency && // if either currency is an empty string we can skip the comparison // because it will result in an error from the api and ultimately // a null conversionRate either way. currentCurrency !== '' && nativeCurrency !== '') { ({ conversionRate, usdConversionRate } = yield this.fetchExchangeRate(currentCurrency, nativeCurrency, this.includeUsdRate)); conversionDate = Date.now() / 1000; } } catch (error) { if (!(error instanceof Error && error.message.includes('market does not exist for this coin pair'))) { throw error; } } finally { try { this.update(() => { return { conversionDate, conversionRate, // we currently allow and handle an empty string as a valid nativeCurrency // in cases where a user has not entered a native ticker symbol for a custom network // currentCurrency is not from user input but this protects us from unexpected changes. nativeCurrency, currentCurrency, pendingCurrentCurrency: null, pendingNativeCurrency: null, usdConversionRate, }; }); } finally { releaseLock(); } } return this.state; }); } } exports.CurrencyRateController = CurrencyRateController; exports.default = CurrencyRateController; //# sourceMappingURL=CurrencyRateController.js.map