hide internal wallet state, keep it internal to package
This commit is contained in:
parent
954ed23911
commit
99550b0011
@ -33,8 +33,7 @@ import {
|
|||||||
WALLET_EXCHANGE_PROTOCOL_VERSION,
|
WALLET_EXCHANGE_PROTOCOL_VERSION,
|
||||||
WALLET_MERCHANT_PROTOCOL_VERSION,
|
WALLET_MERCHANT_PROTOCOL_VERSION,
|
||||||
runRetryLoop,
|
runRetryLoop,
|
||||||
handleCoreApiRequest,
|
Wallet,
|
||||||
InternalWalletState,
|
|
||||||
} from "@gnu-taler/taler-wallet-core";
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
@ -156,8 +155,8 @@ function sendAkonoMessage(ev: CoreApiEnvelope): void {
|
|||||||
|
|
||||||
class AndroidWalletMessageHandler {
|
class AndroidWalletMessageHandler {
|
||||||
walletArgs: DefaultNodeWalletArgs | undefined;
|
walletArgs: DefaultNodeWalletArgs | undefined;
|
||||||
maybeWallet: InternalWalletState | undefined;
|
maybeWallet: Wallet | undefined;
|
||||||
wp = openPromise<InternalWalletState>();
|
wp = openPromise<Wallet>();
|
||||||
httpLib = new NodeHttpLib();
|
httpLib = new NodeHttpLib();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -180,8 +179,8 @@ class AndroidWalletMessageHandler {
|
|||||||
const reinit = async () => {
|
const reinit = async () => {
|
||||||
const w = await getDefaultNodeWallet(this.walletArgs);
|
const w = await getDefaultNodeWallet(this.walletArgs);
|
||||||
this.maybeWallet = w;
|
this.maybeWallet = w;
|
||||||
await handleCoreApiRequest(w, "initWallet", "akono-init", {});
|
await w.handleCoreApiRequest("initWallet", "akono-init", {});
|
||||||
runRetryLoop(w).catch((e) => {
|
w.runRetryLoop().catch((e) => {
|
||||||
console.error("Error during wallet retry loop", e);
|
console.error("Error during wallet retry loop", e);
|
||||||
});
|
});
|
||||||
this.wp.resolve(w);
|
this.wp.resolve(w);
|
||||||
@ -230,14 +229,14 @@ class AndroidWalletMessageHandler {
|
|||||||
}
|
}
|
||||||
const wallet = await this.wp.promise;
|
const wallet = await this.wp.promise;
|
||||||
wallet.stop();
|
wallet.stop();
|
||||||
this.wp = openPromise<InternalWalletState>();
|
this.wp = openPromise<Wallet>();
|
||||||
this.maybeWallet = undefined;
|
this.maybeWallet = undefined;
|
||||||
await reinit();
|
await reinit();
|
||||||
return wrapResponse({});
|
return wrapResponse({});
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
const wallet = await this.wp.promise;
|
const wallet = await this.wp.promise;
|
||||||
return await handleCoreApiRequest(wallet, operation, id, args);
|
return await wallet.handleCoreApiRequest(operation, id, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ import {
|
|||||||
getClientFromWalletState,
|
getClientFromWalletState,
|
||||||
WalletApiOperation,
|
WalletApiOperation,
|
||||||
WalletCoreApiClient,
|
WalletCoreApiClient,
|
||||||
InternalWalletState,
|
Wallet,
|
||||||
} from "@gnu-taler/taler-wallet-core";
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
|
|
||||||
// This module also serves as the entry point for the crypto
|
// This module also serves as the entry point for the crypto
|
||||||
@ -172,10 +172,7 @@ type WalletCliArgsType = clk.GetArgType<typeof walletCli>;
|
|||||||
|
|
||||||
async function withWallet<T>(
|
async function withWallet<T>(
|
||||||
walletCliArgs: WalletCliArgsType,
|
walletCliArgs: WalletCliArgsType,
|
||||||
f: (w: {
|
f: (w: { client: WalletCoreApiClient; ws: Wallet }) => Promise<T>,
|
||||||
client: WalletCoreApiClient;
|
|
||||||
ws: InternalWalletState;
|
|
||||||
}) => Promise<T>,
|
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const dbPath = walletCliArgs.wallet.walletDbFile ?? defaultWalletDbPath;
|
const dbPath = walletCliArgs.wallet.walletDbFile ?? defaultWalletDbPath;
|
||||||
const myHttpLib = new NodeHttpLib();
|
const myHttpLib = new NodeHttpLib();
|
||||||
@ -190,7 +187,7 @@ async function withWallet<T>(
|
|||||||
try {
|
try {
|
||||||
const w = {
|
const w = {
|
||||||
ws: wallet,
|
ws: wallet,
|
||||||
client: await getClientFromWalletState(wallet),
|
client: wallet.client,
|
||||||
};
|
};
|
||||||
const ret = await f(w);
|
const ret = await f(w);
|
||||||
return ret;
|
return ret;
|
||||||
@ -242,8 +239,7 @@ walletCli
|
|||||||
console.error("Invalid JSON");
|
console.error("Invalid JSON");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
const resp = await handleCoreApiRequest(
|
const resp = await wallet.ws.handleCoreApiRequest(
|
||||||
wallet.ws,
|
|
||||||
args.api.operation,
|
args.api.operation,
|
||||||
"reqid-1",
|
"reqid-1",
|
||||||
requestJson,
|
requestJson,
|
||||||
@ -294,7 +290,7 @@ walletCli
|
|||||||
.flag("forceNow", ["-f", "--force-now"])
|
.flag("forceNow", ["-f", "--force-now"])
|
||||||
.action(async (args) => {
|
.action(async (args) => {
|
||||||
await withWallet(args, async (wallet) => {
|
await withWallet(args, async (wallet) => {
|
||||||
await runPending(wallet.ws, args.runPendingOpt.forceNow);
|
await wallet.ws.runPending(args.runPendingOpt.forceNow);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -318,7 +314,7 @@ walletCli
|
|||||||
.maybeOption("maxRetries", ["--max-retries"], clk.INT)
|
.maybeOption("maxRetries", ["--max-retries"], clk.INT)
|
||||||
.action(async (args) => {
|
.action(async (args) => {
|
||||||
await withWallet(args, async (wallet) => {
|
await withWallet(args, async (wallet) => {
|
||||||
await runUntilDone(wallet.ws, {
|
await wallet.ws.runUntilDone({
|
||||||
maxRetries: args.finishPendingOpt.maxRetries,
|
maxRetries: args.finishPendingOpt.maxRetries,
|
||||||
});
|
});
|
||||||
wallet.ws.stop();
|
wallet.ws.stop();
|
||||||
@ -607,7 +603,7 @@ depositCli
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
console.log(`Created deposit ${resp.depositGroupId}`);
|
console.log(`Created deposit ${resp.depositGroupId}`);
|
||||||
await runPending(wallet.ws);
|
await wallet.ws.runPending();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -40,8 +40,6 @@ import {
|
|||||||
import { DbAccess, GetReadOnlyAccess } from "./util/query.js";
|
import { DbAccess, GetReadOnlyAccess } from "./util/query.js";
|
||||||
import { TimerGroup } from "./util/timer.js";
|
import { TimerGroup } from "./util/timer.js";
|
||||||
|
|
||||||
type NotificationListener = (n: WalletNotification) => void;
|
|
||||||
|
|
||||||
const logger = new Logger("state.ts");
|
const logger = new Logger("state.ts");
|
||||||
|
|
||||||
export const EXCHANGE_COINS_LOCK = "exchange-coins-lock";
|
export const EXCHANGE_COINS_LOCK = "exchange-coins-lock";
|
||||||
@ -79,114 +77,51 @@ export interface ExchangeOperations {
|
|||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type NotificationListener = (n: WalletNotification) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal state of the wallet.
|
* Internal, shard wallet state that is used by the implementation
|
||||||
|
* of wallet operations.
|
||||||
|
*
|
||||||
|
* FIXME: This should not be exported anywhere from the taler-wallet-core package,
|
||||||
|
* as it's an opaque implementation detail.
|
||||||
*/
|
*/
|
||||||
export class InternalWalletState implements InternalWalletState {
|
export interface InternalWalletState {
|
||||||
memoProcessReserve: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
|
memoProcessReserve: AsyncOpMemoMap<void>;
|
||||||
memoMakePlanchet: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
|
memoMakePlanchet: AsyncOpMemoMap<void>;
|
||||||
memoGetPending: AsyncOpMemoSingle<PendingOperationsResponse> = new AsyncOpMemoSingle();
|
memoGetPending: AsyncOpMemoSingle<PendingOperationsResponse>;
|
||||||
memoGetBalance: AsyncOpMemoSingle<BalancesResponse> = new AsyncOpMemoSingle();
|
memoGetBalance: AsyncOpMemoSingle<BalancesResponse>;
|
||||||
memoProcessRefresh: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
|
memoProcessRefresh: AsyncOpMemoMap<void>;
|
||||||
memoProcessRecoup: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
|
memoProcessRecoup: AsyncOpMemoMap<void>;
|
||||||
memoProcessDeposit: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
|
memoProcessDeposit: AsyncOpMemoMap<void>;
|
||||||
cryptoApi: CryptoApi;
|
cryptoApi: CryptoApi;
|
||||||
|
|
||||||
timerGroup: TimerGroup = new TimerGroup();
|
timerGroup: TimerGroup;
|
||||||
latch = new AsyncCondition();
|
latch: AsyncCondition;
|
||||||
stopped = false;
|
stopped: boolean;
|
||||||
memoRunRetryLoop = new AsyncOpMemoSingle<void>();
|
memoRunRetryLoop: AsyncOpMemoSingle<void>;
|
||||||
|
|
||||||
listeners: NotificationListener[] = [];
|
listeners: NotificationListener[];
|
||||||
|
|
||||||
initCalled: boolean = false;
|
initCalled: boolean;
|
||||||
|
|
||||||
// FIXME: This should be done in wallet.ts, here we should only give declarations
|
exchangeOps: ExchangeOperations;
|
||||||
exchangeOps: ExchangeOperations = {
|
|
||||||
getExchangeDetails,
|
|
||||||
getExchangeTrust,
|
|
||||||
updateExchangeFromUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
db: DbAccess<typeof WalletStoresV1>;
|
||||||
* Promises that are waiting for a particular resource.
|
http: HttpRequestLibrary;
|
||||||
*/
|
|
||||||
private resourceWaiters: Record<string, OpenedPromise<void>[]> = {};
|
|
||||||
|
|
||||||
/**
|
notify(n: WalletNotification): void;
|
||||||
* Resources that are currently locked.
|
|
||||||
*/
|
|
||||||
private resourceLocks: Set<string> = new Set();
|
|
||||||
|
|
||||||
constructor(
|
addNotificationListener(f: (n: WalletNotification) => void): void;
|
||||||
// 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 ongoing processing.
|
||||||
*/
|
*/
|
||||||
stop(): void {
|
stop(): void;
|
||||||
this.stopped = true;
|
|
||||||
this.timerGroup.stopCurrentAndFutureTimers();
|
|
||||||
this.cryptoApi.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run an async function after acquiring a list of locks, identified
|
* Run an async function after acquiring a list of locks, identified
|
||||||
* by string tokens.
|
* by string tokens.
|
||||||
*/
|
*/
|
||||||
async runSequentialized<T>(tokens: string[], f: () => Promise<T>) {
|
runSequentialized<T>(tokens: string[], f: () => Promise<T>): 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ import { SynchronousCryptoWorkerFactory } from "../crypto/workers/synchronousWor
|
|||||||
import type { IDBFactory } from "@gnu-taler/idb-bridge";
|
import type { IDBFactory } from "@gnu-taler/idb-bridge";
|
||||||
import { WalletNotification } from "@gnu-taler/taler-util";
|
import { WalletNotification } from "@gnu-taler/taler-util";
|
||||||
import { InternalWalletState } from "../common.js";
|
import { InternalWalletState } from "../common.js";
|
||||||
|
import { Wallet } from "../wallet.js";
|
||||||
|
|
||||||
const logger = new Logger("headless/helpers.ts");
|
const logger = new Logger("headless/helpers.ts");
|
||||||
|
|
||||||
@ -93,7 +94,7 @@ function makeId(length: number): string {
|
|||||||
*/
|
*/
|
||||||
export async function getDefaultNodeWallet(
|
export async function getDefaultNodeWallet(
|
||||||
args: DefaultNodeWalletArgs = {},
|
args: DefaultNodeWalletArgs = {},
|
||||||
): Promise<InternalWalletState> {
|
): Promise<Wallet> {
|
||||||
BridgeIDBFactory.enableTracing = false;
|
BridgeIDBFactory.enableTracing = false;
|
||||||
const myBackend = new MemoryBackend();
|
const myBackend = new MemoryBackend();
|
||||||
myBackend.enableTracing = false;
|
myBackend.enableTracing = false;
|
||||||
@ -172,7 +173,7 @@ export async function getDefaultNodeWallet(
|
|||||||
workerFactory = new SynchronousCryptoWorkerFactory();
|
workerFactory = new SynchronousCryptoWorkerFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
const w = new InternalWalletState(myDb, myHttpLib, workerFactory);
|
const w = await Wallet.create(myDb, myHttpLib, workerFactory);
|
||||||
|
|
||||||
if (args.notifyHandler) {
|
if (args.notifyHandler) {
|
||||||
w.addNotificationListener(args.notifyHandler);
|
w.addNotificationListener(args.notifyHandler);
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
|
BalancesResponse,
|
||||||
codecForAny,
|
codecForAny,
|
||||||
codecForDeleteTransactionRequest,
|
codecForDeleteTransactionRequest,
|
||||||
codecForRetryTransactionRequest,
|
codecForRetryTransactionRequest,
|
||||||
@ -32,9 +33,11 @@ import {
|
|||||||
getDurationRemaining,
|
getDurationRemaining,
|
||||||
isTimestampExpired,
|
isTimestampExpired,
|
||||||
j2s,
|
j2s,
|
||||||
|
PreparePayResultType,
|
||||||
TalerErrorCode,
|
TalerErrorCode,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
timestampMin,
|
timestampMin,
|
||||||
|
WalletNotification,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
addBackupProvider,
|
addBackupProvider,
|
||||||
@ -59,6 +62,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
acceptExchangeTermsOfService,
|
acceptExchangeTermsOfService,
|
||||||
getExchangeDetails,
|
getExchangeDetails,
|
||||||
|
getExchangeTrust,
|
||||||
updateExchangeFromUrl,
|
updateExchangeFromUrl,
|
||||||
} from "./operations/exchanges.js";
|
} from "./operations/exchanges.js";
|
||||||
import {
|
import {
|
||||||
@ -85,7 +89,11 @@ import {
|
|||||||
getFundingPaytoUris,
|
getFundingPaytoUris,
|
||||||
processReserve,
|
processReserve,
|
||||||
} from "./operations/reserves.js";
|
} from "./operations/reserves.js";
|
||||||
import { InternalWalletState } from "./common.js";
|
import {
|
||||||
|
ExchangeOperations,
|
||||||
|
InternalWalletState,
|
||||||
|
NotificationListener,
|
||||||
|
} from "./common.js";
|
||||||
import {
|
import {
|
||||||
runIntegrationTest,
|
runIntegrationTest,
|
||||||
testPay,
|
testPay,
|
||||||
@ -106,16 +114,16 @@ import {
|
|||||||
AuditorTrustRecord,
|
AuditorTrustRecord,
|
||||||
CoinSourceType,
|
CoinSourceType,
|
||||||
ReserveRecordStatus,
|
ReserveRecordStatus,
|
||||||
|
WalletStoresV1,
|
||||||
} from "./db.js";
|
} from "./db.js";
|
||||||
import { NotificationType } from "@gnu-taler/taler-util";
|
import { NotificationType } from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
PendingOperationInfo,
|
PendingOperationInfo,
|
||||||
|
PendingOperationsResponse,
|
||||||
PendingOperationType,
|
PendingOperationType,
|
||||||
} from "./pending-types.js";
|
} from "./pending-types.js";
|
||||||
import { CoinDumpJson } from "@gnu-taler/taler-util";
|
import { CoinDumpJson } from "@gnu-taler/taler-util";
|
||||||
import {
|
import { codecForTransactionsRequest } from "@gnu-taler/taler-util";
|
||||||
codecForTransactionsRequest,
|
|
||||||
} from "@gnu-taler/taler-util";
|
|
||||||
import {
|
import {
|
||||||
AcceptManualWithdrawalResult,
|
AcceptManualWithdrawalResult,
|
||||||
AcceptWithdrawalResponse,
|
AcceptWithdrawalResponse,
|
||||||
@ -151,6 +159,16 @@ import { assertUnreachable } from "./util/assertUnreachable.js";
|
|||||||
import { Logger } from "@gnu-taler/taler-util";
|
import { Logger } from "@gnu-taler/taler-util";
|
||||||
import { setWalletDeviceId } from "./operations/backup/state.js";
|
import { setWalletDeviceId } from "./operations/backup/state.js";
|
||||||
import { WalletCoreApiClient } from "./wallet-api-types.js";
|
import { WalletCoreApiClient } from "./wallet-api-types.js";
|
||||||
|
import { AsyncOpMemoMap, AsyncOpMemoSingle } from "./util/asyncMemo.js";
|
||||||
|
import { CryptoApi, CryptoWorkerFactory } from "./crypto/workers/cryptoApi.js";
|
||||||
|
import { TimerGroup } from "./util/timer.js";
|
||||||
|
import {
|
||||||
|
AsyncCondition,
|
||||||
|
OpenedPromise,
|
||||||
|
openPromise,
|
||||||
|
} from "./util/promiseUtils.js";
|
||||||
|
import { DbAccess } from "./util/query.js";
|
||||||
|
import { HttpRequestLibrary } from "./util/http.js";
|
||||||
|
|
||||||
const builtinAuditors: AuditorTrustRecord[] = [
|
const builtinAuditors: AuditorTrustRecord[] = [
|
||||||
{
|
{
|
||||||
@ -618,7 +636,6 @@ async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> {
|
|||||||
return coinsJson;
|
return coinsJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an API client from an internal wallet state object.
|
* Get an API client from an internal wallet state object.
|
||||||
*/
|
*/
|
||||||
@ -936,3 +953,178 @@ export async function handleCoreApiRequest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public handle to a running wallet.
|
||||||
|
*/
|
||||||
|
export class Wallet {
|
||||||
|
private ws: InternalWalletState;
|
||||||
|
private _client: WalletCoreApiClient;
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
db: DbAccess<typeof WalletStoresV1>,
|
||||||
|
http: HttpRequestLibrary,
|
||||||
|
cryptoWorkerFactory: CryptoWorkerFactory,
|
||||||
|
) {
|
||||||
|
this.ws = new InternalWalletStateImpl(db, http, cryptoWorkerFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
get client() {
|
||||||
|
return this._client;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async create(
|
||||||
|
db: DbAccess<typeof WalletStoresV1>,
|
||||||
|
http: HttpRequestLibrary,
|
||||||
|
cryptoWorkerFactory: CryptoWorkerFactory,
|
||||||
|
): Promise<Wallet> {
|
||||||
|
const w = new Wallet(db, http, cryptoWorkerFactory);
|
||||||
|
w._client = await getClientFromWalletState(w.ws);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
addNotificationListener(f: (n: WalletNotification) => void): void {
|
||||||
|
return this.ws.addNotificationListener(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(): void {
|
||||||
|
this.ws.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
runRetryLoop(): Promise<void> {
|
||||||
|
return runRetryLoop(this.ws);
|
||||||
|
}
|
||||||
|
|
||||||
|
runPending(forceNow: boolean = false) {
|
||||||
|
return runPending(this.ws, forceNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
runUntilDone(
|
||||||
|
req: {
|
||||||
|
maxRetries?: number;
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
return runUntilDone(this.ws, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCoreApiRequest(
|
||||||
|
operation: string,
|
||||||
|
id: string,
|
||||||
|
payload: unknown,
|
||||||
|
): Promise<CoreApiResponse> {
|
||||||
|
return handleCoreApiRequest(this.ws, operation, id, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal state of the wallet.
|
||||||
|
*
|
||||||
|
* This ties together all the operation implementations.
|
||||||
|
*/
|
||||||
|
class InternalWalletStateImpl implements 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 = {
|
||||||
|
getExchangeDetails,
|
||||||
|
getExchangeTrust,
|
||||||
|
updateExchangeFromUrl,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -37,6 +37,8 @@ import {
|
|||||||
runRetryLoop,
|
runRetryLoop,
|
||||||
handleNotifyReserve,
|
handleNotifyReserve,
|
||||||
InternalWalletState,
|
InternalWalletState,
|
||||||
|
Wallet,
|
||||||
|
WalletApiOperation,
|
||||||
} from "@gnu-taler/taler-wallet-core";
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
import {
|
import {
|
||||||
classifyTalerUri,
|
classifyTalerUri,
|
||||||
@ -52,8 +54,10 @@ import { BrowserCryptoWorkerFactory } from "./browserCryptoWorkerFactory";
|
|||||||
/**
|
/**
|
||||||
* Currently active wallet instance. Might be unloaded and
|
* Currently active wallet instance. Might be unloaded and
|
||||||
* re-instantiated when the database is reset.
|
* re-instantiated when the database is reset.
|
||||||
|
*
|
||||||
|
* FIXME: Maybe move the wallet reseting into the Wallet class?
|
||||||
*/
|
*/
|
||||||
let currentWallet: InternalWalletState | undefined;
|
let currentWallet: Wallet | undefined;
|
||||||
|
|
||||||
let currentDatabase: DbAccess<typeof WalletStoresV1> | undefined;
|
let currentDatabase: DbAccess<typeof WalletStoresV1> | undefined;
|
||||||
|
|
||||||
@ -170,7 +174,7 @@ async function dispatch(
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
r = await handleCoreApiRequest(w, req.operation, req.id, req.payload);
|
r = await w.handleCoreApiRequest(req.operation, req.id, req.payload);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -256,7 +260,7 @@ async function reinitWallet(): Promise<void> {
|
|||||||
}
|
}
|
||||||
const http = new BrowserHttpLib();
|
const http = new BrowserHttpLib();
|
||||||
console.log("setting wallet");
|
console.log("setting wallet");
|
||||||
const wallet = new InternalWalletState(
|
const wallet = await Wallet.create(
|
||||||
currentDatabase,
|
currentDatabase,
|
||||||
http,
|
http,
|
||||||
new BrowserCryptoWorkerFactory(),
|
new BrowserCryptoWorkerFactory(),
|
||||||
@ -270,7 +274,7 @@ async function reinitWallet(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
runRetryLoop(wallet).catch((e) => {
|
wallet.runRetryLoop().catch((e) => {
|
||||||
console.log("error during wallet retry loop", e);
|
console.log("error during wallet retry loop", e);
|
||||||
});
|
});
|
||||||
// Useful for debugging in the background page.
|
// Useful for debugging in the background page.
|
||||||
@ -360,7 +364,8 @@ function headerListener(
|
|||||||
if (!w) {
|
if (!w) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleNotifyReserve(w);
|
// FIXME: Is this still useful?
|
||||||
|
// handleNotifyReserve(w);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -451,4 +456,3 @@ export async function wxMain(): Promise<void> {
|
|||||||
setupHeaderListener();
|
setupHeaderListener();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user