summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/common.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2021-06-17 15:49:05 +0200
committerFlorian Dold <florian@dold.me>2021-06-17 15:49:05 +0200
commita70d37ef1675b53241f707c6730fab1537bd9d24 (patch)
tree5493e11884c9071efda9a8f4680e3787c6cdf0a5 /packages/taler-wallet-core/src/common.ts
parent453656b240c7e8771068ab877b6f5c9e3a26a4dc (diff)
downloadwallet-core-a70d37ef1675b53241f707c6730fab1537bd9d24.tar.gz
wallet-core-a70d37ef1675b53241f707c6730fab1537bd9d24.tar.bz2
wallet-core-a70d37ef1675b53241f707c6730fab1537bd9d24.zip
towards factoring out cyclic dependencies
Diffstat (limited to 'packages/taler-wallet-core/src/common.ts')
-rw-r--r--packages/taler-wallet-core/src/common.ts182
1 files changed, 182 insertions, 0 deletions
diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts
new file mode 100644
index 000000000..cbbbb1e32
--- /dev/null
+++ b/packages/taler-wallet-core/src/common.ts
@@ -0,0 +1,182 @@
+/*
+ This file is part of GNU Taler
+ (C) 2019 GNUnet e.V.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
+import {
+ WalletNotification,
+ BalancesResponse,
+ Logger,
+} from "@gnu-taler/taler-util";
+import { CryptoApi, CryptoWorkerFactory } from "./crypto/workers/cryptoApi.js";
+import { ExchangeDetailsRecord, ExchangeRecord, WalletStoresV1 } from "./db.js";
+import { PendingOperationsResponse } from "./pending-types.js";
+import { AsyncOpMemoMap, AsyncOpMemoSingle } from "./util/asyncMemo.js";
+import { HttpRequestLibrary } from "./util/http.js";
+import {
+ AsyncCondition,
+ OpenedPromise,
+ openPromise,
+} from "./util/promiseUtils.js";
+import { DbAccess, GetReadOnlyAccess } from "./util/query.js";
+import { TimerGroup } from "./util/timer.js";
+
+type NotificationListener = (n: WalletNotification) => void;
+
+const logger = new Logger("state.ts");
+
+export const EXCHANGE_COINS_LOCK = "exchange-coins-lock";
+export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock";
+
+export interface TrustInfo {
+ isTrusted: boolean;
+ isAudited: boolean;
+}
+
+/**
+ * Interface for exchange-related operations.
+ */
+export interface ExchangeOperations {
+ // FIXME: Should other operations maybe always use
+ // updateExchangeFromUrl?
+ getExchangeDetails(
+ tx: GetReadOnlyAccess<{
+ exchanges: typeof WalletStoresV1.exchanges;
+ exchangeDetails: typeof WalletStoresV1.exchangeDetails;
+ }>,
+ exchangeBaseUrl: string,
+ ): Promise<ExchangeDetailsRecord | undefined>;
+ getExchangeTrust(
+ ws: InternalWalletState,
+ exchangeInfo: ExchangeRecord,
+ ): Promise<TrustInfo>;
+ updateExchangeFromUrl(
+ ws: InternalWalletState,
+ baseUrl: string,
+ forceNow?: boolean,
+ ): Promise<{
+ exchange: ExchangeRecord;
+ exchangeDetails: ExchangeDetailsRecord;
+ }>;
+}
+
+/**
+ * Internal state of the wallet.
+ */
+export class InternalWalletState {
+ memoProcessReserve: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
+ memoMakePlanchet: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
+ memoGetPending: AsyncOpMemoSingle<PendingOperationsResponse> = new AsyncOpMemoSingle();
+ memoGetBalance: AsyncOpMemoSingle<BalancesResponse> = new AsyncOpMemoSingle();
+ memoProcessRefresh: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
+ memoProcessRecoup: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
+ memoProcessDeposit: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
+ cryptoApi: CryptoApi;
+
+ timerGroup: TimerGroup = new TimerGroup();
+ latch = new AsyncCondition();
+ stopped = false;
+ memoRunRetryLoop = new AsyncOpMemoSingle<void>();
+
+ listeners: NotificationListener[] = [];
+
+ initCalled: boolean = false;
+
+ exchangeOps: ExchangeOperations;
+
+ /**
+ * Promises that are waiting for a particular resource.
+ */
+ private resourceWaiters: Record<string, OpenedPromise<void>[]> = {};
+
+ /**
+ * Resources that are currently locked.
+ */
+ private resourceLocks: Set<string> = new Set();
+
+ constructor(
+ // FIXME: Make this a getter and make
+ // the actual value nullable.
+ // Check if we are in a DB migration / garbage collection
+ // and throw an error in that case.
+ public db: DbAccess<typeof WalletStoresV1>,
+ public http: HttpRequestLibrary,
+ cryptoWorkerFactory: CryptoWorkerFactory,
+ ) {
+ this.cryptoApi = new CryptoApi(cryptoWorkerFactory);
+ }
+
+ notify(n: WalletNotification): void {
+ logger.trace("Notification", n);
+ for (const l of this.listeners) {
+ const nc = JSON.parse(JSON.stringify(n));
+ setTimeout(() => {
+ l(nc);
+ }, 0);
+ }
+ }
+
+ addNotificationListener(f: (n: WalletNotification) => void): void {
+ this.listeners.push(f);
+ }
+
+ /**
+ * Stop ongoing processing.
+ */
+ stop(): void {
+ this.stopped = true;
+ this.timerGroup.stopCurrentAndFutureTimers();
+ this.cryptoApi.stop();
+ }
+
+ /**
+ * Run an async function after acquiring a list of locks, identified
+ * by string tokens.
+ */
+ async runSequentialized<T>(tokens: string[], f: () => Promise<T>) {
+ // Make sure locks are always acquired in the same order
+ tokens = [...tokens].sort();
+
+ for (const token of tokens) {
+ if (this.resourceLocks.has(token)) {
+ const p = openPromise<void>();
+ let waitList = this.resourceWaiters[token];
+ if (!waitList) {
+ waitList = this.resourceWaiters[token] = [];
+ }
+ waitList.push(p);
+ await p.promise;
+ }
+ this.resourceLocks.add(token);
+ }
+
+ try {
+ logger.trace(`begin exclusive execution on ${JSON.stringify(tokens)}`);
+ const result = await f();
+ logger.trace(`end exclusive execution on ${JSON.stringify(tokens)}`);
+ return result;
+ } finally {
+ for (const token of tokens) {
+ this.resourceLocks.delete(token);
+ let waiter = (this.resourceWaiters[token] ?? []).shift();
+ if (waiter) {
+ waiter.resolve();
+ }
+ }
+ }
+ }
+}