WIP: simplification and error handling
This commit is contained in:
parent
e8f362ccfe
commit
c623309430
@ -63,7 +63,7 @@
|
||||
"@types/chrome": "^0.0.91",
|
||||
"@types/urijs": "^1.19.3",
|
||||
"axios": "^0.19.0",
|
||||
"idb-bridge": "^0.0.11",
|
||||
"idb-bridge": "^0.0.14",
|
||||
"qrcode-generator": "^1.4.3",
|
||||
"source-map-support": "^0.5.12",
|
||||
"urijs": "^1.18.10"
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
DenominationRecord,
|
||||
DenominationStatus,
|
||||
ReserveRecord,
|
||||
ReserveRecordStatus,
|
||||
} from "../dbTypes";
|
||||
|
||||
import { CryptoApi } from "./cryptoApi";
|
||||
@ -86,18 +87,18 @@ test("precoin creation", async t => {
|
||||
const crypto = new CryptoApi(new NodeCryptoWorkerFactory());
|
||||
const { priv, pub } = await crypto.createEddsaKeypair();
|
||||
const r: ReserveRecord = {
|
||||
created: 0,
|
||||
current_amount: null,
|
||||
exchange_base_url: "https://example.com/exchange",
|
||||
created: { t_ms: 0 },
|
||||
currentAmount: null,
|
||||
exchangeBaseUrl: "https://example.com/exchange",
|
||||
hasPayback: false,
|
||||
precoin_amount: { currency: "PUDOS", value: 0, fraction: 0 },
|
||||
requested_amount: { currency: "PUDOS", value: 0, fraction: 0 },
|
||||
reserve_priv: priv,
|
||||
reserve_pub: pub,
|
||||
timestamp_confirmed: 0,
|
||||
timestamp_depleted: 0,
|
||||
timestamp_reserve_info_posted: 0,
|
||||
exchangeWire: "payto://foo"
|
||||
precoinAmount: { currency: "PUDOS", value: 0, fraction: 0 },
|
||||
requestedAmount: { currency: "PUDOS", value: 0, fraction: 0 },
|
||||
reservePriv: priv,
|
||||
reservePub: pub,
|
||||
timestampConfirmed: undefined,
|
||||
timestampReserveInfoPosted: undefined,
|
||||
exchangeWire: "payto://foo",
|
||||
reserveStatus: ReserveRecordStatus.UNCONFIRMED,
|
||||
};
|
||||
|
||||
const precoin = await crypto.createPreCoin(denomValid1, r);
|
||||
|
@ -45,6 +45,7 @@ import * as native from "./emscInterface";
|
||||
import { AmountJson } from "../amounts";
|
||||
import * as Amounts from "../amounts";
|
||||
import * as timer from "../timer";
|
||||
import { getRandomBytes, encodeCrock } from "./nativeCrypto";
|
||||
|
||||
export class CryptoImplementation {
|
||||
static enableTracing: boolean = false;
|
||||
@ -60,9 +61,9 @@ export class CryptoImplementation {
|
||||
reserve: ReserveRecord,
|
||||
): PreCoinRecord {
|
||||
const reservePriv = new native.EddsaPrivateKey(this.emsc);
|
||||
reservePriv.loadCrock(reserve.reserve_priv);
|
||||
reservePriv.loadCrock(reserve.reservePriv);
|
||||
const reservePub = new native.EddsaPublicKey(this.emsc);
|
||||
reservePub.loadCrock(reserve.reserve_pub);
|
||||
reservePub.loadCrock(reserve.reservePub);
|
||||
const denomPub = native.RsaPublicKey.fromCrock(this.emsc, denom.denomPub);
|
||||
const coinPriv = native.EddsaPrivateKey.create(this.emsc);
|
||||
const coinPub = coinPriv.getPublicKey();
|
||||
@ -103,7 +104,7 @@ export class CryptoImplementation {
|
||||
coinValue: denom.value,
|
||||
denomPub: denomPub.toCrock(),
|
||||
denomPubHash: denomPubHash.toCrock(),
|
||||
exchangeBaseUrl: reserve.exchange_base_url,
|
||||
exchangeBaseUrl: reserve.exchangeBaseUrl,
|
||||
isFromTip: false,
|
||||
reservePub: reservePub.toCrock(),
|
||||
withdrawSig: sig.toCrock(),
|
||||
@ -199,14 +200,14 @@ export class CryptoImplementation {
|
||||
isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean {
|
||||
const p = new native.MasterWireFeePS(this.emsc, {
|
||||
closing_fee: new native.Amount(this.emsc, wf.closingFee).toNbo(),
|
||||
end_date: native.AbsoluteTimeNbo.fromStampSeconds(this.emsc, wf.endStamp),
|
||||
end_date: native.AbsoluteTimeNbo.fromStampSeconds(this.emsc, (wf.endStamp.t_ms / 1000)),
|
||||
h_wire_method: native.ByteArray.fromStringWithNull(
|
||||
this.emsc,
|
||||
type,
|
||||
).hash(),
|
||||
start_date: native.AbsoluteTimeNbo.fromStampSeconds(
|
||||
this.emsc,
|
||||
wf.startStamp,
|
||||
Math.floor(wf.startStamp.t_ms / 1000),
|
||||
),
|
||||
wire_fee: new native.Amount(this.emsc, wf.wireFee).toNbo(),
|
||||
});
|
||||
@ -354,7 +355,7 @@ export class CryptoImplementation {
|
||||
const newAmount = new native.Amount(this.emsc, cd.coin.currentAmount);
|
||||
newAmount.sub(coinSpend);
|
||||
cd.coin.currentAmount = newAmount.toJson();
|
||||
cd.coin.status = CoinStatus.PurchasePending;
|
||||
cd.coin.status = CoinStatus.Dirty;
|
||||
|
||||
const d = new native.DepositRequestPS(this.emsc, {
|
||||
amount_with_fee: coinSpend.toNbo(),
|
||||
@ -505,7 +506,10 @@ export class CryptoImplementation {
|
||||
valueOutput = Amounts.add(valueOutput, denom.value).amount;
|
||||
}
|
||||
|
||||
const refreshSessionId = encodeCrock(getRandomBytes(32));
|
||||
|
||||
const refreshSession: RefreshSessionRecord = {
|
||||
refreshSessionId,
|
||||
confirmSig,
|
||||
exchangeBaseUrl,
|
||||
finished: false,
|
||||
|
@ -12,7 +12,6 @@ export function openTalerDb(
|
||||
onVersionChange: () => void,
|
||||
onUpgradeUnsupported: (oldVersion: number, newVersion: number) => void,
|
||||
): Promise<IDBDatabase> {
|
||||
console.log("in openTalerDb");
|
||||
return new Promise<IDBDatabase>((resolve, reject) => {
|
||||
const req = idbFactory.open(DB_NAME, WALLET_DB_VERSION);
|
||||
req.onerror = e => {
|
||||
|
171
src/dbTypes.ts
171
src/dbTypes.ts
@ -46,6 +46,36 @@ import { Timestamp, OperationError } from "./walletTypes";
|
||||
*/
|
||||
export const WALLET_DB_VERSION = 27;
|
||||
|
||||
export enum ReserveRecordStatus {
|
||||
/**
|
||||
* Waiting for manual confirmation.
|
||||
*/
|
||||
UNCONFIRMED = "unconfirmed",
|
||||
|
||||
/**
|
||||
* Reserve must be registered with the bank.
|
||||
*/
|
||||
REGISTERING_BANK = "registering-bank",
|
||||
|
||||
/**
|
||||
* Querying reserve status with the exchange.
|
||||
*/
|
||||
QUERYING_STATUS = "querying-status",
|
||||
|
||||
/**
|
||||
* Status is queried, the wallet must now select coins
|
||||
* and start withdrawing.
|
||||
*/
|
||||
WITHDRAWING = "withdrawing",
|
||||
|
||||
/**
|
||||
* The corresponding withdraw record has been created.
|
||||
* No further processing is done, unless explicitly requested
|
||||
* by the user.
|
||||
*/
|
||||
DORMANT = "dormant",
|
||||
}
|
||||
|
||||
/**
|
||||
* A reserve record as stored in the wallet's database.
|
||||
*/
|
||||
@ -53,28 +83,22 @@ export interface ReserveRecord {
|
||||
/**
|
||||
* The reserve public key.
|
||||
*/
|
||||
reserve_pub: string;
|
||||
reservePub: string;
|
||||
|
||||
/**
|
||||
* The reserve private key.
|
||||
*/
|
||||
reserve_priv: string;
|
||||
reservePriv: string;
|
||||
|
||||
/**
|
||||
* The exchange base URL.
|
||||
*/
|
||||
exchange_base_url: string;
|
||||
exchangeBaseUrl: string;
|
||||
|
||||
/**
|
||||
* Time when the reserve was created.
|
||||
*/
|
||||
created: number;
|
||||
|
||||
/**
|
||||
* Time when the reserve was depleted.
|
||||
* Set to 0 if not depleted yet.
|
||||
*/
|
||||
timestamp_depleted: number;
|
||||
created: Timestamp;
|
||||
|
||||
/**
|
||||
* Time when the information about this reserve was posted to the bank.
|
||||
@ -83,32 +107,32 @@ export interface ReserveRecord {
|
||||
*
|
||||
* Set to 0 if that hasn't happened yet.
|
||||
*/
|
||||
timestamp_reserve_info_posted: number;
|
||||
timestampReserveInfoPosted: Timestamp | undefined;
|
||||
|
||||
/**
|
||||
* Time when the reserve was confirmed.
|
||||
*
|
||||
* Set to 0 if not confirmed yet.
|
||||
*/
|
||||
timestamp_confirmed: number;
|
||||
timestampConfirmed: Timestamp | undefined;
|
||||
|
||||
/**
|
||||
* Current amount left in the reserve
|
||||
*/
|
||||
current_amount: AmountJson | null;
|
||||
currentAmount: AmountJson | null;
|
||||
|
||||
/**
|
||||
* Amount requested when the reserve was created.
|
||||
* When a reserve is re-used (rare!) the current_amount can
|
||||
* be higher than the requested_amount
|
||||
*/
|
||||
requested_amount: AmountJson;
|
||||
requestedAmount: AmountJson;
|
||||
|
||||
/**
|
||||
* What's the current amount that sits
|
||||
* in precoins?
|
||||
*/
|
||||
precoin_amount: AmountJson;
|
||||
precoinAmount: AmountJson;
|
||||
|
||||
/**
|
||||
* We got some payback to this reserve. We'll cease to automatically
|
||||
@ -129,6 +153,10 @@ export interface ReserveRecord {
|
||||
exchangeWire: string;
|
||||
|
||||
bankWithdrawStatusUrl?: string;
|
||||
|
||||
reserveStatus: ReserveRecordStatus;
|
||||
|
||||
lastError?: OperationError;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -341,9 +369,9 @@ export interface ExchangeDetails {
|
||||
}
|
||||
|
||||
export enum ExchangeUpdateStatus {
|
||||
NONE = "none",
|
||||
FETCH_KEYS = "fetch_keys",
|
||||
FETCH_WIRE = "fetch_wire",
|
||||
FINISHED = "finished",
|
||||
}
|
||||
|
||||
export interface ExchangeBankAccount {
|
||||
@ -374,13 +402,18 @@ export interface ExchangeRecord {
|
||||
*/
|
||||
wireInfo: ExchangeWireInfo | undefined;
|
||||
|
||||
/**
|
||||
* When was the exchange added to the wallet?
|
||||
*/
|
||||
timestampAdded: Timestamp;
|
||||
|
||||
/**
|
||||
* Time when the update to the exchange has been started or
|
||||
* undefined if no update is in progress.
|
||||
*/
|
||||
updateStarted: Timestamp | undefined;
|
||||
|
||||
updateStatus: ExchangeUpdateStatus;
|
||||
updateReason?: "initial" | "forced";
|
||||
|
||||
lastError?: OperationError;
|
||||
}
|
||||
@ -436,31 +469,15 @@ export enum CoinStatus {
|
||||
/**
|
||||
* Withdrawn and never shown to anybody.
|
||||
*/
|
||||
Fresh,
|
||||
/**
|
||||
* Currently planned to be sent to a merchant for a purchase.
|
||||
*/
|
||||
PurchasePending,
|
||||
Fresh = "fresh",
|
||||
/**
|
||||
* Used for a completed transaction and now dirty.
|
||||
*/
|
||||
Dirty,
|
||||
Dirty = "dirty",
|
||||
/**
|
||||
* A coin that was refreshed.
|
||||
* A coin that has been spent and refreshed.
|
||||
*/
|
||||
Refreshed,
|
||||
/**
|
||||
* Coin marked to be paid back, but payback not finished.
|
||||
*/
|
||||
PaybackPending,
|
||||
/**
|
||||
* Coin fully paid back.
|
||||
*/
|
||||
PaybackDone,
|
||||
/**
|
||||
* Coin was dirty but can't be refreshed.
|
||||
*/
|
||||
Useless,
|
||||
Dormant = "dormant",
|
||||
}
|
||||
|
||||
/**
|
||||
@ -569,7 +586,7 @@ export class ProposalDownloadRecord {
|
||||
* was created.
|
||||
*/
|
||||
@Checkable.Number()
|
||||
timestamp: number;
|
||||
timestamp: Timestamp;
|
||||
|
||||
/**
|
||||
* Private key for the nonce.
|
||||
@ -658,7 +675,7 @@ export interface TipRecord {
|
||||
*/
|
||||
nextUrl?: string;
|
||||
|
||||
timestamp: number;
|
||||
timestamp: Timestamp;
|
||||
|
||||
pickupUrl: string;
|
||||
}
|
||||
@ -735,9 +752,9 @@ export interface RefreshSessionRecord {
|
||||
finished: boolean;
|
||||
|
||||
/**
|
||||
* Record ID when retrieved from the DB.
|
||||
* A 32-byte base32-crockford encoded random identifier.
|
||||
*/
|
||||
id?: number;
|
||||
refreshSessionId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -771,12 +788,12 @@ export interface WireFee {
|
||||
/**
|
||||
* Start date of the fee.
|
||||
*/
|
||||
startStamp: number;
|
||||
startStamp: Timestamp;
|
||||
|
||||
/**
|
||||
* End date of the fee.
|
||||
*/
|
||||
endStamp: number;
|
||||
endStamp: Timestamp;
|
||||
|
||||
/**
|
||||
* Signature made by the exchange master key.
|
||||
@ -830,14 +847,13 @@ export interface PurchaseRecord {
|
||||
* When was the purchase made?
|
||||
* Refers to the time that the user accepted.
|
||||
*/
|
||||
timestamp: number;
|
||||
timestamp: Timestamp;
|
||||
|
||||
/**
|
||||
* When was the last refund made?
|
||||
* Set to 0 if no refund was made on the purchase.
|
||||
*/
|
||||
timestamp_refund: number;
|
||||
|
||||
timestamp_refund: Timestamp | undefined;
|
||||
|
||||
/**
|
||||
* Last session signature that we submitted to /pay (if any).
|
||||
@ -917,7 +933,6 @@ export interface CoinsReturnRecord {
|
||||
wire: any;
|
||||
}
|
||||
|
||||
|
||||
export interface WithdrawalRecord {
|
||||
/**
|
||||
* Reserve that we're withdrawing from.
|
||||
@ -928,18 +943,22 @@ export interface WithdrawalRecord {
|
||||
* When was the withdrawal operation started started?
|
||||
* Timestamp in milliseconds.
|
||||
*/
|
||||
startTimestamp: number;
|
||||
startTimestamp: Timestamp;
|
||||
|
||||
/**
|
||||
* When was the withdrawal operation completed?
|
||||
*/
|
||||
finishTimestamp?: number;
|
||||
finishTimestamp?: Timestamp;
|
||||
|
||||
/**
|
||||
* Amount that is being withdrawn with this operation.
|
||||
* This does not include fees.
|
||||
*/
|
||||
withdrawalAmount: string;
|
||||
|
||||
numCoinsTotal: number;
|
||||
|
||||
numCoinsWithdrawn: number;
|
||||
}
|
||||
|
||||
/* tslint:disable:completed-docs */
|
||||
@ -983,11 +1002,6 @@ export namespace Stores {
|
||||
"urlIndex",
|
||||
"url",
|
||||
);
|
||||
timestampIndex = new Index<string, ProposalDownloadRecord>(
|
||||
this,
|
||||
"timestampIndex",
|
||||
"timestamp",
|
||||
);
|
||||
}
|
||||
|
||||
class PurchasesStore extends Store<PurchaseRecord> {
|
||||
@ -1005,11 +1019,6 @@ export namespace Stores {
|
||||
"orderIdIndex",
|
||||
"contractTerms.order_id",
|
||||
);
|
||||
timestampIndex = new Index<string, PurchaseRecord>(
|
||||
this,
|
||||
"timestampIndex",
|
||||
"timestamp",
|
||||
);
|
||||
}
|
||||
|
||||
class DenominationsStore extends Store<DenominationRecord> {
|
||||
@ -1051,23 +1060,8 @@ export namespace Stores {
|
||||
|
||||
class ReservesStore extends Store<ReserveRecord> {
|
||||
constructor() {
|
||||
super("reserves", { keyPath: "reserve_pub" });
|
||||
super("reserves", { keyPath: "reservePub" });
|
||||
}
|
||||
timestampCreatedIndex = new Index<string, ReserveRecord>(
|
||||
this,
|
||||
"timestampCreatedIndex",
|
||||
"created",
|
||||
);
|
||||
timestampConfirmedIndex = new Index<string, ReserveRecord>(
|
||||
this,
|
||||
"timestampConfirmedIndex",
|
||||
"timestamp_confirmed",
|
||||
);
|
||||
timestampDepletedIndex = new Index<string, ReserveRecord>(
|
||||
this,
|
||||
"timestampDepletedIndex",
|
||||
"timestamp_depleted",
|
||||
);
|
||||
}
|
||||
|
||||
class TipsStore extends Store<TipRecord> {
|
||||
@ -1092,8 +1086,26 @@ export namespace Stores {
|
||||
|
||||
class WithdrawalsStore extends Store<WithdrawalRecord> {
|
||||
constructor() {
|
||||
super("withdrawals", { keyPath: "id", autoIncrement: true })
|
||||
super("withdrawals", { keyPath: "id", autoIncrement: true });
|
||||
}
|
||||
byReservePub = new Index<string, WithdrawalRecord>(
|
||||
this,
|
||||
"withdrawalsReservePubIndex",
|
||||
"reservePub",
|
||||
);
|
||||
}
|
||||
|
||||
class PreCoinsStore extends Store<PreCoinRecord> {
|
||||
constructor() {
|
||||
super("precoins", {
|
||||
keyPath: "coinPub",
|
||||
});
|
||||
}
|
||||
byReservePub = new Index<string, PreCoinRecord>(
|
||||
this,
|
||||
"precoinsReservePubIndex",
|
||||
"reservePub",
|
||||
);
|
||||
}
|
||||
|
||||
export const coins = new CoinsStore();
|
||||
@ -1104,13 +1116,10 @@ export namespace Stores {
|
||||
export const currencies = new CurrenciesStore();
|
||||
export const denominations = new DenominationsStore();
|
||||
export const exchanges = new ExchangeStore();
|
||||
export const precoins = new Store<PreCoinRecord>("precoins", {
|
||||
keyPath: "coinPub",
|
||||
});
|
||||
export const precoins = new PreCoinsStore();
|
||||
export const proposals = new ProposalsStore();
|
||||
export const refresh = new Store<RefreshSessionRecord>("refresh", {
|
||||
keyPath: "id",
|
||||
autoIncrement: true,
|
||||
keyPath: "refreshSessionId",
|
||||
});
|
||||
export const reserves = new ReservesStore();
|
||||
export const purchases = new PurchasesStore();
|
||||
|
@ -440,7 +440,7 @@ export class CommandGroup<GN extends keyof any, TG> {
|
||||
if (option.isFlag == false && option.required == true) {
|
||||
if (!foundOptions[option.name]) {
|
||||
if (option.args.default !== undefined) {
|
||||
parsedArgs[this.argKey] = option.args.default;
|
||||
myArgs[option.name] = option.args.default;
|
||||
} else {
|
||||
const name = option.flagspec.join(",")
|
||||
console.error(`error: missing option '${name}'`);
|
||||
|
@ -21,7 +21,7 @@
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import { Wallet } from "../wallet";
|
||||
import { Wallet, OperationFailedAndReportedError } from "../wallet";
|
||||
import { Notifier, Badge } from "../walletTypes";
|
||||
import { MemoryBackend, BridgeIDBFactory, shimIndexedDB } from "idb-bridge";
|
||||
import { SynchronousCryptoWorkerFactory } from "../crypto/synchronousWorker";
|
||||
@ -139,18 +139,16 @@ export async function getDefaultNodeWallet(
|
||||
|
||||
const storagePath = args.persistentStoragePath;
|
||||
if (storagePath) {
|
||||
console.log(`using storage path ${storagePath}`);
|
||||
|
||||
try {
|
||||
const dbContentStr: string = fs.readFileSync(storagePath, { encoding: "utf-8" });
|
||||
const dbContent = JSON.parse(dbContentStr);
|
||||
myBackend.importDump(dbContent);
|
||||
console.log("imported wallet");
|
||||
} catch (e) {
|
||||
console.log("could not read wallet file");
|
||||
console.error("could not read wallet file");
|
||||
}
|
||||
|
||||
myBackend.afterCommitCallback = async () => {
|
||||
console.log("DATABASE COMMITTED");
|
||||
// Allow caller to stop persisting the wallet.
|
||||
if (args.persistentStoragePath === undefined) {
|
||||
return;
|
||||
@ -190,8 +188,6 @@ export async function getDefaultNodeWallet(
|
||||
myUnsupportedUpgrade,
|
||||
);
|
||||
|
||||
console.log("opened db");
|
||||
|
||||
return new Wallet(
|
||||
myDb,
|
||||
myHttpLib,
|
||||
@ -214,6 +210,8 @@ export async function withdrawTestBalance(
|
||||
exchangeWire: "payto://unknown",
|
||||
});
|
||||
|
||||
const reservePub = reserveResponse.reservePub;
|
||||
|
||||
const bank = new Bank(bankBaseUrl);
|
||||
|
||||
const bankUser = await bank.registerRandomUser();
|
||||
@ -228,11 +226,11 @@ export async function withdrawTestBalance(
|
||||
await bank.createReserve(
|
||||
bankUser,
|
||||
amount,
|
||||
reserveResponse.reservePub,
|
||||
reservePub,
|
||||
exchangePaytoUri,
|
||||
);
|
||||
|
||||
await myWallet.confirmReserve({ reservePub: reserveResponse.reservePub });
|
||||
|
||||
await myWallet.processReserve(reserveResponse.reservePub);
|
||||
await myWallet.runUntilReserveDepleted(reservePub);
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ export async function runIntegrationTest(args: {
|
||||
amountToWithdraw: string;
|
||||
amountToSpend: string;
|
||||
}) {
|
||||
console.log("running test with", args);
|
||||
const myWallet = await getDefaultNodeWallet();
|
||||
|
||||
await withdrawTestBalance(myWallet, args.amountToWithdraw, args.bankBaseUrl, args.exchangeBaseUrl);
|
||||
|
@ -18,9 +18,14 @@ import os = require("os");
|
||||
import { getDefaultNodeWallet, withdrawTestBalance } from "./helpers";
|
||||
import { MerchantBackendConnection } from "./merchant";
|
||||
import { runIntegrationTest } from "./integrationtest";
|
||||
import { Wallet } from "../wallet";
|
||||
import { Wallet, OperationFailedAndReportedError } from "../wallet";
|
||||
import qrcodeGenerator = require("qrcode-generator");
|
||||
import * as clk from "./clk";
|
||||
import { BridgeIDBFactory, MemoryBackend } from "idb-bridge";
|
||||
import { Logger } from "../logging";
|
||||
import * as Amounts from "../amounts";
|
||||
|
||||
const logger = new Logger("taler-wallet-cli.ts");
|
||||
|
||||
const walletDbPath = os.homedir + "/" + ".talerwalletdb.json";
|
||||
|
||||
@ -82,6 +87,7 @@ function applyVerbose(verbose: boolean) {
|
||||
if (verbose) {
|
||||
console.log("enabled verbose logging");
|
||||
Wallet.enableTracing = true;
|
||||
BridgeIDBFactory.enableTracing = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,16 +109,21 @@ async function withWallet<T>(
|
||||
walletCliArgs: WalletCliArgsType,
|
||||
f: (w: Wallet) => Promise<T>,
|
||||
): Promise<T> {
|
||||
applyVerbose(walletCliArgs.wallet.verbose);
|
||||
const wallet = await getDefaultNodeWallet({
|
||||
persistentStoragePath: walletDbPath,
|
||||
});
|
||||
applyVerbose(walletCliArgs.wallet.verbose);
|
||||
try {
|
||||
await wallet.fillDefaults();
|
||||
const ret = await f(wallet);
|
||||
return ret;
|
||||
} catch (e) {
|
||||
console.error("caught exception:", e);
|
||||
if (e instanceof OperationFailedAndReportedError) {
|
||||
console.error("Operation failed: " + e.message);
|
||||
console.log("Hint: check pending operations for details.");
|
||||
} else {
|
||||
console.error("caught exception:", e);
|
||||
}
|
||||
process.exit(1);
|
||||
} finally {
|
||||
wallet.stop();
|
||||
@ -120,6 +131,161 @@ async function withWallet<T>(
|
||||
}
|
||||
|
||||
walletCli
|
||||
.subcommand("", "balance", { help: "Show wallet balance." })
|
||||
.action(async args => {
|
||||
console.log("balance command called");
|
||||
await withWallet(args, async wallet => {
|
||||
const balance = await wallet.getBalances();
|
||||
console.log(JSON.stringify(balance, undefined, 2));
|
||||
});
|
||||
});
|
||||
|
||||
walletCli
|
||||
.subcommand("", "history", { help: "Show wallet event history." })
|
||||
.maybeOption("from", ["--from"], clk.STRING)
|
||||
.maybeOption("to", ["--to"], clk.STRING)
|
||||
.maybeOption("limit", ["--limit"], clk.STRING)
|
||||
.maybeOption("contEvt", ["--continue-with"], clk.STRING)
|
||||
.action(async args => {
|
||||
await withWallet(args, async wallet => {
|
||||
const history = await wallet.getHistory();
|
||||
console.log(JSON.stringify(history, undefined, 2));
|
||||
});
|
||||
});
|
||||
|
||||
walletCli
|
||||
.subcommand("", "pending", { help: "Show pending operations." })
|
||||
.action(async args => {
|
||||
await withWallet(args, async wallet => {
|
||||
const pending = await wallet.getPendingOperations();
|
||||
console.log(JSON.stringify(pending, undefined, 2));
|
||||
});
|
||||
});
|
||||
|
||||
async function asyncSleep(milliSeconds: number): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
setTimeout(() => resolve(), milliSeconds);
|
||||
});
|
||||
}
|
||||
|
||||
walletCli
|
||||
.subcommand("runPendingOpt", "run-pending", {
|
||||
help: "Run pending operations.",
|
||||
})
|
||||
.action(async args => {
|
||||
await withWallet(args, async wallet => {
|
||||
await wallet.runPending();
|
||||
});
|
||||
});
|
||||
|
||||
walletCli
|
||||
.subcommand("handleUri", "handle-uri", {
|
||||
help: "Handle a taler:// URI.",
|
||||
})
|
||||
.requiredArgument("uri", clk.STRING)
|
||||
.flag("autoYes", ["-y", "--yes"])
|
||||
.action(async args => {
|
||||
await withWallet(args, async wallet => {
|
||||
const uri: string = args.handleUri.uri;
|
||||
if (uri.startsWith("taler://pay/")) {
|
||||
await doPay(wallet, uri, { alwaysYes: args.handleUri.autoYes });
|
||||
} else if (uri.startsWith("taler://tip/")) {
|
||||
const res = await wallet.getTipStatus(uri);
|
||||
console.log("tip status", res);
|
||||
await wallet.acceptTip(uri);
|
||||
} else if (uri.startsWith("taler://refund/")) {
|
||||
await wallet.applyRefund(uri);
|
||||
} else if (uri.startsWith("taler://withdraw/")) {
|
||||
const withdrawInfo = await wallet.getWithdrawalInfo(uri);
|
||||
const selectedExchange = withdrawInfo.suggestedExchange;
|
||||
if (!selectedExchange) {
|
||||
console.error("no suggested exchange!");
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
const { confirmTransferUrl } = await wallet.acceptWithdrawal(
|
||||
uri,
|
||||
selectedExchange,
|
||||
);
|
||||
if (confirmTransferUrl) {
|
||||
console.log("please confirm the transfer at", confirmTransferUrl);
|
||||
}
|
||||
} else {
|
||||
console.error("unrecognized URI");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const exchangesCli = walletCli.subcommand("exchangesCmd", "exchanges", {
|
||||
help: "Manage exchanges.",
|
||||
});
|
||||
|
||||
exchangesCli
|
||||
.subcommand("exchangesListCmd", "list", {
|
||||
help: "List known exchanges.",
|
||||
})
|
||||
.action(async args => {
|
||||
console.log("Listing exchanges ...");
|
||||
await withWallet(args, async wallet => {
|
||||
const exchanges = await wallet.getExchanges();
|
||||
console.log("exchanges", exchanges);
|
||||
});
|
||||
});
|
||||
|
||||
exchangesCli
|
||||
.subcommand("exchangesUpdateCmd", "update", {
|
||||
help: "Update or add an exchange by base URL.",
|
||||
})
|
||||
.requiredArgument("url", clk.STRING, {
|
||||
help: "Base URL of the exchange.",
|
||||
})
|
||||
.flag("force", ["-f", "--force"])
|
||||
.action(async args => {
|
||||
await withWallet(args, async wallet => {
|
||||
const res = await wallet.updateExchangeFromUrl(
|
||||
args.exchangesUpdateCmd.url,
|
||||
args.exchangesUpdateCmd.force,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const advancedCli = walletCli.subcommand("advancedArgs", "advanced", {
|
||||
help:
|
||||
"Subcommands for advanced operations (only use if you know what you're doing!).",
|
||||
});
|
||||
|
||||
advancedCli
|
||||
.subcommand("refresh", "force-refresh", {
|
||||
help: "Force a refresh on a coin.",
|
||||
})
|
||||
.requiredArgument("coinPub", clk.STRING)
|
||||
.action(async args => {
|
||||
await withWallet(args, async wallet => {
|
||||
await wallet.refresh(args.refresh.coinPub, true);
|
||||
});
|
||||
});
|
||||
|
||||
advancedCli
|
||||
.subcommand("coins", "list-coins", {
|
||||
help: "List coins.",
|
||||
})
|
||||
.action(async args => {
|
||||
await withWallet(args, async wallet => {
|
||||
const coins = await wallet.getCoins();
|
||||
for (const coin of coins) {
|
||||
console.log(`coin ${coin.coinPub}`);
|
||||
console.log(` status ${coin.status}`);
|
||||
console.log(` exchange ${coin.exchangeBaseUrl}`);
|
||||
console.log(` remaining amount ${Amounts.toString(coin.currentAmount)}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const testCli = walletCli.subcommand("testingArgs", "testing", {
|
||||
help: "Subcommands for testing GNU Taler deployments.",
|
||||
});
|
||||
|
||||
testCli
|
||||
.subcommand("testPayCmd", "test-pay", { help: "create contract and pay" })
|
||||
.requiredOption("amount", ["-a", "--amount"], clk.STRING)
|
||||
.requiredOption("summary", ["-s", "--summary"], clk.STRING, {
|
||||
@ -146,63 +312,60 @@ walletCli
|
||||
return;
|
||||
}
|
||||
console.log("taler pay URI:", talerPayUri);
|
||||
|
||||
const wallet = await getDefaultNodeWallet({
|
||||
persistentStoragePath: walletDbPath,
|
||||
});
|
||||
|
||||
await doPay(wallet, talerPayUri, { alwaysYes: true });
|
||||
});
|
||||
|
||||
walletCli
|
||||
.subcommand("", "balance", { help: "Show wallet balance." })
|
||||
.action(async args => {
|
||||
console.log("balance command called");
|
||||
withWallet(args, async (wallet) => {
|
||||
const balance = await wallet.getBalances();
|
||||
console.log(JSON.stringify(balance, undefined, 2));
|
||||
await withWallet(args, async (wallet) => {
|
||||
await doPay(wallet, talerPayUri, { alwaysYes: true });
|
||||
});
|
||||
});
|
||||
|
||||
walletCli
|
||||
.subcommand("", "history", { help: "Show wallet event history." })
|
||||
.requiredOption("from", ["--from"], clk.STRING)
|
||||
.requiredOption("to", ["--to"], clk.STRING)
|
||||
.requiredOption("limit", ["--limit"], clk.STRING)
|
||||
.requiredOption("contEvt", ["--continue-with"], clk.STRING)
|
||||
.action(async args => {
|
||||
withWallet(args, async (wallet) => {
|
||||
const history = await wallet.getHistory();
|
||||
console.log(JSON.stringify(history, undefined, 2));
|
||||
});
|
||||
});
|
||||
|
||||
walletCli
|
||||
.subcommand("", "pending", { help: "Show pending operations." })
|
||||
.action(async args => {
|
||||
withWallet(args, async (wallet) => {
|
||||
const pending = await wallet.getPendingOperations();
|
||||
console.log(JSON.stringify(pending, undefined, 2));
|
||||
});
|
||||
});
|
||||
|
||||
async function asyncSleep(milliSeconds: number): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
setTimeout(() => resolve(), milliSeconds);
|
||||
});
|
||||
}
|
||||
|
||||
walletCli
|
||||
.subcommand("runPendingOpt", "run-pending", {
|
||||
help: "Run pending operations."
|
||||
testCli
|
||||
.subcommand("integrationtestCmd", "integrationtest", {
|
||||
help: "Run integration test with bank, exchange and merchant.",
|
||||
})
|
||||
.action(async (args) => {
|
||||
withWallet(args, async (wallet) => {
|
||||
await wallet.processPending();
|
||||
});
|
||||
.requiredOption("exchange", ["-e", "--exchange"], clk.STRING, {
|
||||
default: "https://exchange.test.taler.net/",
|
||||
})
|
||||
.requiredOption("merchant", ["-m", "--merchant"], clk.STRING, {
|
||||
default: "https://backend.test.taler.net/",
|
||||
})
|
||||
.requiredOption("merchantApiKey", ["-k", "--merchant-api-key"], clk.STRING, {
|
||||
default: "sandbox",
|
||||
})
|
||||
.requiredOption("bank", ["-b", "--bank"], clk.STRING, {
|
||||
default: "https://bank.test.taler.net/",
|
||||
})
|
||||
.requiredOption("withdrawAmount", ["-a", "--amount"], clk.STRING, {
|
||||
default: "TESTKUDOS:10",
|
||||
})
|
||||
.requiredOption("spendAmount", ["-s", "--spend-amount"], clk.STRING, {
|
||||
default: "TESTKUDOS:4",
|
||||
})
|
||||
.action(async args => {
|
||||
console.log("parsed args", args);
|
||||
applyVerbose(args.wallet.verbose);
|
||||
let cmdObj = args.integrationtestCmd;
|
||||
|
||||
try {
|
||||
await runIntegrationTest({
|
||||
amountToSpend: cmdObj.spendAmount,
|
||||
amountToWithdraw: cmdObj.withdrawAmount,
|
||||
bankBaseUrl: cmdObj.bank,
|
||||
exchangeBaseUrl: cmdObj.exchange,
|
||||
merchantApiKey: cmdObj.merchantApiKey,
|
||||
merchantBaseUrl: cmdObj.merchant,
|
||||
}).catch(err => {
|
||||
console.error("Failed with exception:");
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
process.exit(0);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
walletCli
|
||||
testCli
|
||||
.subcommand("testMerchantQrcodeCmd", "test-merchant-qrcode")
|
||||
.requiredOption("amount", ["-a", "--amount"], clk.STRING, {
|
||||
default: "TESTKUDOS:1",
|
||||
@ -249,174 +412,14 @@ walletCli
|
||||
}
|
||||
});
|
||||
|
||||
walletCli
|
||||
.subcommand("integrationtestCmd", "integrationtest", {
|
||||
help: "Run integration test with bank, exchange and merchant.",
|
||||
})
|
||||
.requiredOption("exchange", ["-e", "--exchange"], clk.STRING, {
|
||||
default: "https://exchange.test.taler.net/",
|
||||
})
|
||||
.requiredOption("merchant", ["-m", "--merchant"], clk.STRING, {
|
||||
default: "https://backend.test.taler.net/",
|
||||
})
|
||||
.requiredOption("merchantApiKey", ["-k", "--merchant-api-key"], clk.STRING, {
|
||||
default: "sandbox",
|
||||
})
|
||||
.requiredOption("bank", ["-b", "--bank"], clk.STRING, {
|
||||
default: "https://bank.test.taler.net/",
|
||||
})
|
||||
.requiredOption("withdrawAmount", ["-b", "--bank"], clk.STRING, {
|
||||
default: "TESTKUDOS:10",
|
||||
})
|
||||
.requiredOption("spendAmount", ["-s", "--spend-amount"], clk.STRING, {
|
||||
default: "TESTKUDOS:4",
|
||||
})
|
||||
.action(async args => {
|
||||
applyVerbose(args.wallet.verbose);
|
||||
let cmdObj = args.integrationtestCmd;
|
||||
|
||||
try {
|
||||
await runIntegrationTest({
|
||||
amountToSpend: cmdObj.spendAmount,
|
||||
amountToWithdraw: cmdObj.withdrawAmount,
|
||||
bankBaseUrl: cmdObj.bank,
|
||||
exchangeBaseUrl: cmdObj.exchange,
|
||||
merchantApiKey: cmdObj.merchantApiKey,
|
||||
merchantBaseUrl: cmdObj.merchant,
|
||||
}).catch(err => {
|
||||
console.error("Failed with exception:");
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
process.exit(0);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
walletCli
|
||||
.subcommand("withdrawUriCmd", "withdraw-uri")
|
||||
.requiredArgument("withdrawUri", clk.STRING)
|
||||
.action(async args => {
|
||||
applyVerbose(args.wallet.verbose);
|
||||
const cmdArgs = args.withdrawUriCmd;
|
||||
const withdrawUrl = cmdArgs.withdrawUri;
|
||||
console.log("withdrawing", withdrawUrl);
|
||||
const wallet = await getDefaultNodeWallet({
|
||||
persistentStoragePath: walletDbPath,
|
||||
});
|
||||
|
||||
const withdrawInfo = await wallet.getWithdrawalInfo(withdrawUrl);
|
||||
|
||||
console.log("withdraw info", withdrawInfo);
|
||||
|
||||
const selectedExchange = withdrawInfo.suggestedExchange;
|
||||
if (!selectedExchange) {
|
||||
console.error("no suggested exchange!");
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
const { reservePub, confirmTransferUrl } = await wallet.acceptWithdrawal(
|
||||
withdrawUrl,
|
||||
selectedExchange,
|
||||
);
|
||||
|
||||
if (confirmTransferUrl) {
|
||||
console.log("please confirm the transfer at", confirmTransferUrl);
|
||||
}
|
||||
|
||||
await wallet.processReserve(reservePub);
|
||||
|
||||
console.log("finished withdrawing");
|
||||
|
||||
wallet.stop();
|
||||
});
|
||||
|
||||
walletCli
|
||||
.subcommand("tipUriCmd", "tip-uri")
|
||||
.requiredArgument("uri", clk.STRING)
|
||||
.action(async args => {
|
||||
applyVerbose(args.wallet.verbose);
|
||||
const tipUri = args.tipUriCmd.uri;
|
||||
console.log("getting tip", tipUri);
|
||||
const wallet = await getDefaultNodeWallet({
|
||||
persistentStoragePath: walletDbPath,
|
||||
});
|
||||
const res = await wallet.getTipStatus(tipUri);
|
||||
console.log("tip status", res);
|
||||
await wallet.acceptTip(tipUri);
|
||||
wallet.stop();
|
||||
});
|
||||
|
||||
walletCli
|
||||
.subcommand("refundUriCmd", "refund-uri")
|
||||
.requiredArgument("uri", clk.STRING)
|
||||
.action(async args => {
|
||||
applyVerbose(args.wallet.verbose);
|
||||
const refundUri = args.refundUriCmd.uri;
|
||||
console.log("getting refund", refundUri);
|
||||
const wallet = await getDefaultNodeWallet({
|
||||
persistentStoragePath: walletDbPath,
|
||||
});
|
||||
await wallet.applyRefund(refundUri);
|
||||
wallet.stop();
|
||||
});
|
||||
|
||||
const exchangesCli = walletCli.subcommand("exchangesCmd", "exchanges", {
|
||||
help: "Manage exchanges.",
|
||||
});
|
||||
|
||||
exchangesCli
|
||||
.subcommand("exchangesListCmd", "list", {
|
||||
help: "List known exchanges.",
|
||||
})
|
||||
.action(async args => {
|
||||
console.log("Listing exchanges ...");
|
||||
withWallet(args, async (wallet) => {
|
||||
const exchanges = await wallet.getExchanges();
|
||||
console.log("exchanges", exchanges);
|
||||
});
|
||||
});
|
||||
|
||||
exchangesCli
|
||||
.subcommand("exchangesUpdateCmd", "update", {
|
||||
help: "Update or add an exchange by base URL.",
|
||||
})
|
||||
.requiredArgument("url", clk.STRING, {
|
||||
help: "Base URL of the exchange.",
|
||||
})
|
||||
.action(async args => {
|
||||
withWallet(args, async (wallet) => {
|
||||
const res = await wallet.updateExchangeFromUrl(args.exchangesUpdateCmd.url);
|
||||
});
|
||||
});
|
||||
|
||||
walletCli
|
||||
.subcommand("payUriCmd", "pay-uri")
|
||||
.requiredArgument("url", clk.STRING)
|
||||
.flag("autoYes", ["-y", "--yes"])
|
||||
.action(async args => {
|
||||
applyVerbose(args.wallet.verbose);
|
||||
const payUrl = args.payUriCmd.url;
|
||||
console.log("paying for", payUrl);
|
||||
const wallet = await getDefaultNodeWallet({
|
||||
persistentStoragePath: walletDbPath,
|
||||
});
|
||||
|
||||
await doPay(wallet, payUrl, { alwaysYes: args.payUriCmd.autoYes });
|
||||
wallet.stop();
|
||||
});
|
||||
|
||||
const testCli = walletCli.subcommand("testingArgs", "testing", {
|
||||
help: "Subcommands for testing GNU Taler deployments.",
|
||||
});
|
||||
|
||||
testCli
|
||||
.subcommand("withdrawArgs", "withdraw", {
|
||||
help: "Withdraw from a test bank (must support test registrations).",
|
||||
})
|
||||
.requiredOption("amount", ["-a", "--amount"], clk.STRING, {
|
||||
default: "TESTKUDOS:10",
|
||||
help: "Amount to withdraw.",
|
||||
})
|
||||
.requiredOption("exchange", ["-e", "--exchange"], clk.STRING, {
|
||||
default: "https://exchange.test.taler.net/",
|
||||
help: "Exchange base URL.",
|
||||
@ -426,14 +429,15 @@ testCli
|
||||
help: "Bank base URL",
|
||||
})
|
||||
.action(async args => {
|
||||
applyVerbose(args.wallet.verbose);
|
||||
console.log("balance command called");
|
||||
const wallet = await getDefaultNodeWallet({
|
||||
persistentStoragePath: walletDbPath,
|
||||
await withWallet(args, async wallet => {
|
||||
await withdrawTestBalance(
|
||||
wallet,
|
||||
args.withdrawArgs.amount,
|
||||
args.withdrawArgs.bank,
|
||||
args.withdrawArgs.exchange,
|
||||
);
|
||||
logger.info("Withdraw done");
|
||||
});
|
||||
console.log("got wallet");
|
||||
const balance = await wallet.getBalances();
|
||||
console.log(JSON.stringify(balance, undefined, 2));
|
||||
});
|
||||
|
||||
walletCli.run();
|
||||
|
@ -107,10 +107,3 @@ export class BrowserHttpLib implements HttpRequestLibrary {
|
||||
return this.req("post", url, { req: form });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown on request errors.
|
||||
*/
|
||||
export class RequestException {
|
||||
constructor(public detail: any) {}
|
||||
}
|
||||
|
78
src/query.ts
78
src/query.ts
@ -1,5 +1,3 @@
|
||||
import { openPromise } from "./promiseUtils";
|
||||
|
||||
/*
|
||||
This file is part of TALER
|
||||
(C) 2016 GNUnet e.V.
|
||||
@ -22,6 +20,12 @@ import { openPromise } from "./promiseUtils";
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import { openPromise } from "./promiseUtils";
|
||||
|
||||
|
||||
/**
|
||||
* Result of an inner join.
|
||||
*/
|
||||
@ -63,27 +67,48 @@ export interface IndexOptions {
|
||||
}
|
||||
|
||||
function requestToPromise(req: IDBRequest): Promise<any> {
|
||||
const stack = Error("Failed request was started here.")
|
||||
return new Promise((resolve, reject) => {
|
||||
req.onsuccess = () => {
|
||||
resolve(req.result);
|
||||
};
|
||||
req.onerror = () => {
|
||||
console.log("error in DB request", req.error);
|
||||
reject(req.error);
|
||||
console.log("Request failed:", stack);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function oneShotGet<T>(
|
||||
function transactionToPromise(tx: IDBTransaction): Promise<void> {
|
||||
const stack = Error("Failed transaction was started here.");
|
||||
return new Promise((resolve, reject) => {
|
||||
tx.onabort = () => {
|
||||
reject(TransactionAbort);
|
||||
};
|
||||
tx.oncomplete = () => {
|
||||
resolve();
|
||||
};
|
||||
tx.onerror = () => {
|
||||
console.error("Transaction failed:", stack);
|
||||
reject(tx.error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function oneShotGet<T>(
|
||||
db: IDBDatabase,
|
||||
store: Store<T>,
|
||||
key: any,
|
||||
): Promise<T | undefined> {
|
||||
const tx = db.transaction([store.name], "readonly");
|
||||
const req = tx.objectStore(store.name).get(key);
|
||||
return requestToPromise(req);
|
||||
const v = await requestToPromise(req)
|
||||
await transactionToPromise(tx);
|
||||
return v;
|
||||
}
|
||||
|
||||
export function oneShotGetIndexed<S extends IDBValidKey, T>(
|
||||
export async function oneShotGetIndexed<S extends IDBValidKey, T>(
|
||||
db: IDBDatabase,
|
||||
index: Index<S, T>,
|
||||
key: any,
|
||||
@ -93,10 +118,12 @@ export function oneShotGetIndexed<S extends IDBValidKey, T>(
|
||||
.objectStore(index.storeName)
|
||||
.index(index.indexName)
|
||||
.get(key);
|
||||
return requestToPromise(req);
|
||||
const v = await requestToPromise(req);
|
||||
await transactionToPromise(tx);
|
||||
return v;
|
||||
}
|
||||
|
||||
export function oneShotPut<T>(
|
||||
export async function oneShotPut<T>(
|
||||
db: IDBDatabase,
|
||||
store: Store<T>,
|
||||
value: T,
|
||||
@ -104,7 +131,9 @@ export function oneShotPut<T>(
|
||||
): Promise<any> {
|
||||
const tx = db.transaction([store.name], "readwrite");
|
||||
const req = tx.objectStore(store.name).put(value, key);
|
||||
return requestToPromise(req);
|
||||
const v = await requestToPromise(req);
|
||||
await transactionToPromise(tx);
|
||||
return v;
|
||||
}
|
||||
|
||||
function applyMutation<T>(
|
||||
@ -115,7 +144,7 @@ function applyMutation<T>(
|
||||
req.onsuccess = () => {
|
||||
const cursor = req.result;
|
||||
if (cursor) {
|
||||
const val = cursor.value();
|
||||
const val = cursor.value;
|
||||
const modVal = f(val);
|
||||
if (modVal !== undefined && modVal !== null) {
|
||||
const req2: IDBRequest = cursor.update(modVal);
|
||||
@ -138,7 +167,7 @@ function applyMutation<T>(
|
||||
});
|
||||
}
|
||||
|
||||
export function oneShotMutate<T>(
|
||||
export async function oneShotMutate<T>(
|
||||
db: IDBDatabase,
|
||||
store: Store<T>,
|
||||
key: any,
|
||||
@ -146,7 +175,8 @@ export function oneShotMutate<T>(
|
||||
): Promise<void> {
|
||||
const tx = db.transaction([store.name], "readwrite");
|
||||
const req = tx.objectStore(store.name).openCursor(key);
|
||||
return applyMutation(req, f);
|
||||
await applyMutation(req, f);
|
||||
await transactionToPromise(tx);
|
||||
}
|
||||
|
||||
type CursorResult<T> = CursorEmptyResult<T> | CursorValueResult<T>;
|
||||
@ -326,15 +356,12 @@ export function runWithWriteTransaction<T>(
|
||||
stores: Store<any>[],
|
||||
f: (t: TransactionHandle) => Promise<T>,
|
||||
): Promise<T> {
|
||||
const stack = Error("Failed transaction was started here.");
|
||||
return new Promise((resolve, reject) => {
|
||||
const storeName = stores.map(x => x.name);
|
||||
const tx = db.transaction(storeName, "readwrite");
|
||||
let funResult: any = undefined;
|
||||
let gotFunResult: boolean = false;
|
||||
tx.onerror = () => {
|
||||
console.error("error in transaction:", tx.error);
|
||||
reject(tx.error);
|
||||
};
|
||||
tx.oncomplete = () => {
|
||||
// This is a fatal error: The transaction completed *before*
|
||||
// the transaction function returned. Likely, the transaction
|
||||
@ -350,15 +377,30 @@ export function runWithWriteTransaction<T>(
|
||||
}
|
||||
resolve(funResult);
|
||||
};
|
||||
tx.onerror = () => {
|
||||
console.error("error in transaction");
|
||||
};
|
||||
tx.onabort = () => {
|
||||
console.error("aborted transaction");
|
||||
reject(AbortTransaction);
|
||||
if (tx.error) {
|
||||
console.error("Transaction aborted with error:", tx.error);
|
||||
} else {
|
||||
console.log("Trasaction aborted (no error)");
|
||||
}
|
||||
reject(TransactionAbort);
|
||||
};
|
||||
const th = new TransactionHandle(tx);
|
||||
const resP = f(th);
|
||||
resP.then(result => {
|
||||
gotFunResult = true;
|
||||
funResult = result;
|
||||
}).catch((e) => {
|
||||
if (e == TransactionAbort) {
|
||||
console.info("aborting transaction");
|
||||
} else {
|
||||
tx.abort();
|
||||
console.error("Transaction failed:", e);
|
||||
console.error(stack);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -401,4 +443,4 @@ export class Index<S extends IDBValidKey, T> {
|
||||
/**
|
||||
* Exception that should be thrown by client code to abort a transaction.
|
||||
*/
|
||||
export const AbortTransaction = Symbol("abort_transaction");
|
||||
export const TransactionAbort = Symbol("transaction_abort");
|
||||
|
1251
src/wallet.ts
1251
src/wallet.ts
File diff suppressed because it is too large
Load Diff
@ -233,7 +233,7 @@ export interface ConfirmPayResult {
|
||||
/**
|
||||
* Activity history record.
|
||||
*/
|
||||
export interface HistoryRecord {
|
||||
export interface HistoryEvent {
|
||||
/**
|
||||
* Type of the history event.
|
||||
*/
|
||||
@ -242,7 +242,7 @@ export interface HistoryRecord {
|
||||
/**
|
||||
* Time when the activity was recorded.
|
||||
*/
|
||||
timestamp: number;
|
||||
timestamp: Timestamp;
|
||||
|
||||
/**
|
||||
* Subject of the entry. Used to group multiple history records together.
|
||||
@ -254,6 +254,13 @@ export interface HistoryRecord {
|
||||
* Details used when rendering the history record.
|
||||
*/
|
||||
detail: any;
|
||||
|
||||
/**
|
||||
* Set to 'true' if the event has been explicitly created,
|
||||
* and set to 'false' if the event has been derived from the
|
||||
* state of the database.
|
||||
*/
|
||||
explicit: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -516,6 +523,8 @@ export interface WalletDiagnostics {
|
||||
|
||||
export interface PendingWithdrawOperation {
|
||||
type: "withdraw";
|
||||
stage: string;
|
||||
reservePub: string;
|
||||
}
|
||||
|
||||
export interface PendingRefreshOperation {
|
||||
@ -535,6 +544,7 @@ export interface OperationError {
|
||||
export interface PendingExchangeUpdateOperation {
|
||||
type: "exchange-update";
|
||||
stage: string;
|
||||
reason: string;
|
||||
exchangeBaseUrl: string;
|
||||
lastError?: OperationError;
|
||||
}
|
||||
@ -545,10 +555,28 @@ export interface PendingBugOperation {
|
||||
details: any;
|
||||
}
|
||||
|
||||
export interface PendingReserveOperation {
|
||||
type: "reserve";
|
||||
lastError?: OperationError;
|
||||
stage: string;
|
||||
timestampCreated: Timestamp;
|
||||
reserveType: string;
|
||||
}
|
||||
|
||||
export interface PendingRefreshOperation {
|
||||
type: "refresh";
|
||||
lastError?: OperationError;
|
||||
oldCoinPub: string;
|
||||
refreshStatus: string;
|
||||
refreshOutputSize: number;
|
||||
}
|
||||
|
||||
export type PendingOperationInfo =
|
||||
| PendingWithdrawOperation
|
||||
| PendingReserveOperation
|
||||
| PendingBugOperation
|
||||
| PendingExchangeUpdateOperation;
|
||||
| PendingExchangeUpdateOperation
|
||||
| PendingRefreshOperation;
|
||||
|
||||
export interface PendingOperationsResponse {
|
||||
pendingOperations: PendingOperationInfo[];
|
||||
|
@ -79,7 +79,7 @@ export interface MessageMap {
|
||||
};
|
||||
"get-history": {
|
||||
request: {};
|
||||
response: walletTypes.HistoryRecord[];
|
||||
response: walletTypes.HistoryEvent[];
|
||||
};
|
||||
"get-coins": {
|
||||
request: { exchangeBaseUrl: string };
|
||||
|
@ -57,11 +57,11 @@ function Payback() {
|
||||
<div>
|
||||
{reserves.map(r => (
|
||||
<div>
|
||||
<h2>Reserve for ${renderAmount(r.current_amount!)}</h2>
|
||||
<h2>Reserve for ${renderAmount(r.currentAmount!)}</h2>
|
||||
<ul>
|
||||
<li>Exchange: ${r.exchange_base_url}</li>
|
||||
<li>Exchange: ${r.exchangeBaseUrl}</li>
|
||||
</ul>
|
||||
<button onClick={() => withdrawPaybackReserve(r.reserve_pub)}>
|
||||
<button onClick={() => withdrawPaybackReserve(r.reservePub)}>
|
||||
Withdraw again
|
||||
</button>
|
||||
</div>
|
||||
|
@ -30,7 +30,7 @@ import { AmountJson } from "../../amounts";
|
||||
import * as Amounts from "../../amounts";
|
||||
|
||||
import {
|
||||
HistoryRecord,
|
||||
HistoryEvent,
|
||||
WalletBalance,
|
||||
WalletBalanceEntry,
|
||||
} from "../../walletTypes";
|
||||
@ -327,7 +327,7 @@ class WalletBalanceView extends React.Component<any, any> {
|
||||
}
|
||||
}
|
||||
|
||||
function formatHistoryItem(historyItem: HistoryRecord) {
|
||||
function formatHistoryItem(historyItem: HistoryEvent) {
|
||||
const d = historyItem.detail;
|
||||
console.log("hist item", historyItem);
|
||||
switch (historyItem.type) {
|
||||
@ -459,7 +459,7 @@ class WalletHistory extends React.Component<any, any> {
|
||||
|
||||
render(): JSX.Element {
|
||||
console.log("rendering history");
|
||||
const history: HistoryRecord[] = this.myHistory;
|
||||
const history: HistoryEvent[] = this.myHistory;
|
||||
if (this.gotError) {
|
||||
return i18n.str`Error: could not retrieve event history`;
|
||||
}
|
||||
@ -474,7 +474,7 @@ class WalletHistory extends React.Component<any, any> {
|
||||
const item = (
|
||||
<div className="historyItem">
|
||||
<div className="historyDate">
|
||||
{new Date(record.timestamp).toString()}
|
||||
{new Date(record.timestamp.t_ms).toString()}
|
||||
</div>
|
||||
{formatHistoryItem(record)}
|
||||
</div>
|
||||
|
@ -215,7 +215,7 @@ function FeeDetailsView(props: {
|
||||
<tbody>
|
||||
{rci!.wireFees.feesForType[s].map(f => (
|
||||
<tr>
|
||||
<td>{moment.unix(f.endStamp).format("llll")}</td>
|
||||
<td>{moment.unix(Math.floor(f.endStamp.t_ms / 1000)).format("llll")}</td>
|
||||
<td>{renderAmount(f.wireFee)}</td>
|
||||
<td>{renderAmount(f.closingFee)}</td>
|
||||
</tr>
|
||||
|
@ -176,7 +176,7 @@ async function handleMessage(
|
||||
if (typeof detail.exchangeBaseUrl !== "string") {
|
||||
return Promise.reject(Error("exchangBaseUrl missing"));
|
||||
}
|
||||
return needsWallet().getCoins(detail.exchangeBaseUrl);
|
||||
return needsWallet().getCoinsForExchange(detail.exchangeBaseUrl);
|
||||
}
|
||||
case "get-precoins": {
|
||||
if (typeof detail.exchangeBaseUrl !== "string") {
|
||||
|
@ -33,6 +33,8 @@
|
||||
"src/crypto/cryptoWorker.ts",
|
||||
"src/crypto/emscInterface-test.ts",
|
||||
"src/crypto/emscInterface.ts",
|
||||
"src/crypto/nativeCrypto-test.ts",
|
||||
"src/crypto/nativeCrypto.ts",
|
||||
"src/crypto/nodeEmscriptenLoader.ts",
|
||||
"src/crypto/nodeProcessWorker.ts",
|
||||
"src/crypto/nodeWorkerEntry.ts",
|
||||
@ -53,6 +55,7 @@
|
||||
"src/index.ts",
|
||||
"src/libtoolVersion-test.ts",
|
||||
"src/libtoolVersion.ts",
|
||||
"src/logging.ts",
|
||||
"src/promiseUtils.ts",
|
||||
"src/query.ts",
|
||||
"src/talerTypes.ts",
|
||||
|
@ -3412,10 +3412,10 @@ iconv-lite@0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
idb-bridge@^0.0.11:
|
||||
version "0.0.11"
|
||||
resolved "https://registry.yarnpkg.com/idb-bridge/-/idb-bridge-0.0.11.tgz#ba2fbd24b7e6f7f4de8333ed12b0912e64dda308"
|
||||
integrity sha512-fLlHce/WwT6eD3sc54gsfvM5fZqrhAPwBNH4uU/y6D0C1+0higH7OgC5/wploMhkmNYkQID3BMNZvSUBr0leSQ==
|
||||
idb-bridge@^0.0.14:
|
||||
version "0.0.14"
|
||||
resolved "https://registry.yarnpkg.com/idb-bridge/-/idb-bridge-0.0.14.tgz#5fd50cd68b574df0eb6b1a960cef0cb984a21ded"
|
||||
integrity sha512-jc9ZYGhhIrW6nh/pWyycGWzCmsLTFQ0iMY61lN+y9YcIOCxREpAkZxdfmhwNL7H0RvsYp7iJv0GH7ujs7HPC+g==
|
||||
|
||||
ieee754@^1.1.4:
|
||||
version "1.1.13"
|
||||
|
Loading…
Reference in New Issue
Block a user