WIP: simplify DB queries and error handling
This commit is contained in:
parent
faedf69762
commit
553da64990
@ -64,7 +64,7 @@
|
|||||||
"@types/urijs": "^1.19.3",
|
"@types/urijs": "^1.19.3",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
"commander": "^3.0.1",
|
"commander": "^3.0.1",
|
||||||
"idb-bridge": "^0.0.10",
|
"idb-bridge": "^0.0.11",
|
||||||
"qrcode-generator": "^1.4.3",
|
"qrcode-generator": "^1.4.3",
|
||||||
"source-map-support": "^0.5.12",
|
"source-map-support": "^0.5.12",
|
||||||
"urijs": "^1.18.10"
|
"urijs": "^1.18.10"
|
||||||
|
3
packages/idb-bridge/.vscode/settings.json
vendored
3
packages/idb-bridge/.vscode/settings.json
vendored
@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"editor.tabSize": 2
|
"editor.tabSize": 2,
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib"
|
||||||
}
|
}
|
102
src/dbTypes.ts
102
src/dbTypes.ts
@ -36,6 +36,7 @@ import {
|
|||||||
} from "./talerTypes";
|
} from "./talerTypes";
|
||||||
|
|
||||||
import { Index, Store } from "./query";
|
import { Index, Store } from "./query";
|
||||||
|
import { Timestamp, OperationError } from "./walletTypes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current database version, should be incremented
|
* Current database version, should be incremented
|
||||||
@ -310,13 +311,10 @@ export class DenominationRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exchange record as stored in the wallet's database.
|
* Details about the exchange that we only know after
|
||||||
|
* querying /keys and /wire.
|
||||||
*/
|
*/
|
||||||
export interface ExchangeRecord {
|
export interface ExchangeDetails {
|
||||||
/**
|
|
||||||
* Base url of the exchange.
|
|
||||||
*/
|
|
||||||
baseUrl: string;
|
|
||||||
/**
|
/**
|
||||||
* Master public key of the exchange.
|
* Master public key of the exchange.
|
||||||
*/
|
*/
|
||||||
@ -331,22 +329,60 @@ export interface ExchangeRecord {
|
|||||||
*/
|
*/
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Timestamp for last update.
|
|
||||||
*/
|
|
||||||
lastUpdateTime: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When did we actually use this exchange last (in milliseconds). If we
|
|
||||||
* never used the exchange for anything but just updated its info, this is
|
|
||||||
* set to 0. (Currently only updated when reserves are created.)
|
|
||||||
*/
|
|
||||||
lastUsedTime: number;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last observed protocol version.
|
* Last observed protocol version.
|
||||||
*/
|
*/
|
||||||
protocolVersion?: string;
|
protocolVersion: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp for last update.
|
||||||
|
*/
|
||||||
|
lastUpdateTime: Timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ExchangeUpdateStatus {
|
||||||
|
NONE = "none",
|
||||||
|
FETCH_KEYS = "fetch_keys",
|
||||||
|
FETCH_WIRE = "fetch_wire",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExchangeBankAccount {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExchangeWireInfo {
|
||||||
|
feesForType: { [wireMethod: string]: WireFee[] };
|
||||||
|
accounts: ExchangeBankAccount[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchange record as stored in the wallet's database.
|
||||||
|
*/
|
||||||
|
export interface ExchangeRecord {
|
||||||
|
/**
|
||||||
|
* Base url of the exchange.
|
||||||
|
*/
|
||||||
|
baseUrl: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details, once known.
|
||||||
|
*/
|
||||||
|
details: ExchangeDetails | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping from wire method type to the wire fee.
|
||||||
|
*/
|
||||||
|
wireInfo: ExchangeWireInfo | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time when the update to the exchange has been started or
|
||||||
|
* undefined if no update is in progress.
|
||||||
|
*/
|
||||||
|
updateStarted: Timestamp | undefined;
|
||||||
|
|
||||||
|
updateStatus: ExchangeUpdateStatus;
|
||||||
|
|
||||||
|
lastError?: OperationError;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -554,21 +590,6 @@ export class ProposalDownloadRecord {
|
|||||||
static checked: (obj: any) => ProposalDownloadRecord;
|
static checked: (obj: any) => ProposalDownloadRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Wire fees for an exchange.
|
|
||||||
*/
|
|
||||||
export interface ExchangeWireFeesRecord {
|
|
||||||
/**
|
|
||||||
* Base URL of the exchange.
|
|
||||||
*/
|
|
||||||
exchangeBaseUrl: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapping from wire method type to the wire fee.
|
|
||||||
*/
|
|
||||||
feesForType: { [wireMethod: string]: WireFee[] };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status of a tip we got from a merchant.
|
* Status of a tip we got from a merchant.
|
||||||
*/
|
*/
|
||||||
@ -931,12 +952,6 @@ export namespace Stores {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super("exchanges", { keyPath: "baseUrl" });
|
super("exchanges", { keyPath: "baseUrl" });
|
||||||
}
|
}
|
||||||
|
|
||||||
pubKeyIndex = new Index<string, ExchangeRecord>(
|
|
||||||
this,
|
|
||||||
"pubKeyIndex",
|
|
||||||
"masterPublicKey",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CoinsStore extends Store<CoinRecord> {
|
class CoinsStore extends Store<CoinRecord> {
|
||||||
@ -1034,12 +1049,6 @@ export namespace Stores {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExchangeWireFeesStore extends Store<ExchangeWireFeesRecord> {
|
|
||||||
constructor() {
|
|
||||||
super("exchangeWireFees", { keyPath: "exchangeBaseUrl" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReservesStore extends Store<ReserveRecord> {
|
class ReservesStore extends Store<ReserveRecord> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("reserves", { keyPath: "reserve_pub" });
|
super("reserves", { keyPath: "reserve_pub" });
|
||||||
@ -1094,7 +1103,6 @@ export namespace Stores {
|
|||||||
export const config = new ConfigStore();
|
export const config = new ConfigStore();
|
||||||
export const currencies = new CurrenciesStore();
|
export const currencies = new CurrenciesStore();
|
||||||
export const denominations = new DenominationsStore();
|
export const denominations = new DenominationsStore();
|
||||||
export const exchangeWireFees = new ExchangeWireFeesStore();
|
|
||||||
export const exchanges = new ExchangeStore();
|
export const exchanges = new ExchangeStore();
|
||||||
export const precoins = new Store<PreCoinRecord>("precoins", {
|
export const precoins = new Store<PreCoinRecord>("precoins", {
|
||||||
keyPath: "coinPub",
|
keyPath: "coinPub",
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
import process = require("process");
|
import process = require("process");
|
||||||
import path = require("path");
|
import path = require("path");
|
||||||
import readline = require("readline");
|
import readline = require("readline");
|
||||||
import { symlinkSync } from "fs";
|
|
||||||
|
|
||||||
class Converter<T> {}
|
class Converter<T> {}
|
||||||
|
|
||||||
@ -54,6 +53,7 @@ interface ArgumentDef {
|
|||||||
name: string;
|
name: string;
|
||||||
conv: Converter<any>;
|
conv: Converter<any>;
|
||||||
args: ArgumentArgs<any>;
|
args: ArgumentArgs<any>;
|
||||||
|
required: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SubcommandDef {
|
interface SubcommandDef {
|
||||||
@ -181,7 +181,7 @@ export class CommandGroup<GN extends keyof any, TG> {
|
|||||||
return this as any;
|
return this as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
argument<N extends keyof any, V>(
|
requiredArgument<N extends keyof any, V>(
|
||||||
name: N,
|
name: N,
|
||||||
conv: Converter<V>,
|
conv: Converter<V>,
|
||||||
args: ArgumentArgs<V> = {},
|
args: ArgumentArgs<V> = {},
|
||||||
@ -190,6 +190,22 @@ export class CommandGroup<GN extends keyof any, TG> {
|
|||||||
args: args,
|
args: args,
|
||||||
conv: conv,
|
conv: conv,
|
||||||
name: name as string,
|
name: name as string,
|
||||||
|
required: true,
|
||||||
|
};
|
||||||
|
this.arguments.push(argDef);
|
||||||
|
return this as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeArgument<N extends keyof any, V>(
|
||||||
|
name: N,
|
||||||
|
conv: Converter<V>,
|
||||||
|
args: ArgumentArgs<V> = {},
|
||||||
|
): CommandGroup<GN, TG & SubRecord<GN, N, V | undefined>> {
|
||||||
|
const argDef: ArgumentDef = {
|
||||||
|
args: args,
|
||||||
|
conv: conv,
|
||||||
|
name: name as string,
|
||||||
|
required: false,
|
||||||
};
|
};
|
||||||
this.arguments.push(argDef);
|
this.arguments.push(argDef);
|
||||||
return this as any;
|
return this as any;
|
||||||
@ -401,10 +417,25 @@ export class CommandGroup<GN extends keyof any, TG> {
|
|||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
throw Error("not reached");
|
throw Error("not reached");
|
||||||
}
|
}
|
||||||
|
myArgs[d.name] = unparsedArgs[i];
|
||||||
posArgIndex++;
|
posArgIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let i = posArgIndex; i < this.arguments.length; i++) {
|
||||||
|
const d = this.arguments[i];
|
||||||
|
const n = this.name ?? progname;
|
||||||
|
if (d.required) {
|
||||||
|
if (d.args.default !== undefined) {
|
||||||
|
myArgs[d.name] = d.args.default;
|
||||||
|
} else {
|
||||||
|
console.error(`error: missing positional argument '${d.name}' for ${n}`);
|
||||||
|
process.exit(-1);
|
||||||
|
throw Error("not reached");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (let option of this.options) {
|
for (let option of this.options) {
|
||||||
if (option.isFlag == false && option.required == true) {
|
if (option.isFlag == false && option.required == true) {
|
||||||
if (!foundOptions[option.name]) {
|
if (!foundOptions[option.name]) {
|
||||||
@ -433,9 +464,7 @@ export class CommandGroup<GN extends keyof any, TG> {
|
|||||||
unparsedArgs.slice(i + 1),
|
unparsedArgs.slice(i + 1),
|
||||||
parsedArgs,
|
parsedArgs,
|
||||||
);
|
);
|
||||||
}
|
} else if (this.myAction) {
|
||||||
|
|
||||||
if (this.myAction) {
|
|
||||||
this.myAction(parsedArgs);
|
this.myAction(parsedArgs);
|
||||||
} else {
|
} else {
|
||||||
this.printHelp(progname, parents);
|
this.printHelp(progname, parents);
|
||||||
@ -513,18 +542,35 @@ export class Program<PN extends keyof any, T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a positional argument to the program.
|
* Add a required positional argument to the program.
|
||||||
*/
|
*/
|
||||||
argument<N extends keyof any, V>(
|
requiredArgument<N extends keyof any, V>(
|
||||||
name: N,
|
name: N,
|
||||||
conv: Converter<V>,
|
conv: Converter<V>,
|
||||||
args: ArgumentArgs<V> = {},
|
args: ArgumentArgs<V> = {},
|
||||||
): Program<N, T & SubRecord<PN, N, V>> {
|
): Program<N, T & SubRecord<PN, N, V>> {
|
||||||
this.mainCommand.argument(name, conv, args);
|
this.mainCommand.requiredArgument(name, conv, args);
|
||||||
|
return this as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an optional argument to the program.
|
||||||
|
*/
|
||||||
|
maybeArgument<N extends keyof any, V>(
|
||||||
|
name: N,
|
||||||
|
conv: Converter<V>,
|
||||||
|
args: ArgumentArgs<V> = {},
|
||||||
|
): Program<N, T & SubRecord<PN, N, V | undefined>> {
|
||||||
|
this.mainCommand.maybeArgument(name, conv, args);
|
||||||
return this as any;
|
return this as any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type GetArgType<T> =
|
||||||
|
T extends Program<any, infer AT> ? AT :
|
||||||
|
T extends CommandGroup<any, infer AT> ? AT :
|
||||||
|
any;
|
||||||
|
|
||||||
export function program<PN extends keyof any>(
|
export function program<PN extends keyof any>(
|
||||||
argKey: PN,
|
argKey: PN,
|
||||||
args: ProgramArgs = {},
|
args: ProgramArgs = {},
|
||||||
@ -532,6 +578,8 @@ export function program<PN extends keyof any>(
|
|||||||
return new Program(argKey as string, args);
|
return new Program(argKey as string, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function prompt(question: string): Promise<string> {
|
export function prompt(question: string): Promise<string> {
|
||||||
const stdinReadline = readline.createInterface({
|
const stdinReadline = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
|
@ -97,6 +97,28 @@ const walletCli = clk
|
|||||||
help: "Enable verbose output.",
|
help: "Enable verbose output.",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type WalletCliArgsType = clk.GetArgType<typeof walletCli>;
|
||||||
|
|
||||||
|
async function withWallet<T>(
|
||||||
|
walletCliArgs: WalletCliArgsType,
|
||||||
|
f: (w: Wallet) => Promise<T>,
|
||||||
|
): Promise<T> {
|
||||||
|
applyVerbose(walletCliArgs.wallet.verbose);
|
||||||
|
const wallet = await getDefaultNodeWallet({
|
||||||
|
persistentStoragePath: walletDbPath,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await wallet.fillDefaults();
|
||||||
|
const ret = await f(wallet);
|
||||||
|
return ret;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("caught exception:", e);
|
||||||
|
process.exit(1);
|
||||||
|
} finally {
|
||||||
|
wallet.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
walletCli
|
walletCli
|
||||||
.subcommand("testPayCmd", "test-pay", { help: "create contract and pay" })
|
.subcommand("testPayCmd", "test-pay", { help: "create contract and pay" })
|
||||||
.requiredOption("amount", ["-a", "--amount"], clk.STRING)
|
.requiredOption("amount", ["-a", "--amount"], clk.STRING)
|
||||||
@ -135,15 +157,11 @@ walletCli
|
|||||||
walletCli
|
walletCli
|
||||||
.subcommand("", "balance", { help: "Show wallet balance." })
|
.subcommand("", "balance", { help: "Show wallet balance." })
|
||||||
.action(async args => {
|
.action(async args => {
|
||||||
applyVerbose(args.wallet.verbose);
|
|
||||||
console.log("balance command called");
|
console.log("balance command called");
|
||||||
const wallet = await getDefaultNodeWallet({
|
withWallet(args, async (wallet) => {
|
||||||
persistentStoragePath: walletDbPath,
|
const balance = await wallet.getBalances();
|
||||||
|
console.log(JSON.stringify(balance, undefined, 2));
|
||||||
});
|
});
|
||||||
console.log("got wallet");
|
|
||||||
const balance = await wallet.getBalances();
|
|
||||||
console.log(JSON.stringify(balance, undefined, 2));
|
|
||||||
process.exit(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
walletCli
|
walletCli
|
||||||
@ -153,29 +171,19 @@ walletCli
|
|||||||
.requiredOption("limit", ["--limit"], clk.STRING)
|
.requiredOption("limit", ["--limit"], clk.STRING)
|
||||||
.requiredOption("contEvt", ["--continue-with"], clk.STRING)
|
.requiredOption("contEvt", ["--continue-with"], clk.STRING)
|
||||||
.action(async args => {
|
.action(async args => {
|
||||||
applyVerbose(args.wallet.verbose);
|
withWallet(args, async (wallet) => {
|
||||||
console.log("history command called");
|
const history = await wallet.getHistory();
|
||||||
const wallet = await getDefaultNodeWallet({
|
console.log(JSON.stringify(history, undefined, 2));
|
||||||
persistentStoragePath: walletDbPath,
|
|
||||||
});
|
});
|
||||||
console.log("got wallet");
|
|
||||||
const history = await wallet.getHistory();
|
|
||||||
console.log(JSON.stringify(history, undefined, 2));
|
|
||||||
process.exit(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
walletCli
|
walletCli
|
||||||
.subcommand("", "pending", { help: "Show pending operations." })
|
.subcommand("", "pending", { help: "Show pending operations." })
|
||||||
.action(async args => {
|
.action(async args => {
|
||||||
applyVerbose(args.wallet.verbose);
|
withWallet(args, async (wallet) => {
|
||||||
console.log("history command called");
|
const pending = await wallet.getPendingOperations();
|
||||||
const wallet = await getDefaultNodeWallet({
|
console.log(JSON.stringify(pending, undefined, 2));
|
||||||
persistentStoragePath: walletDbPath,
|
|
||||||
});
|
});
|
||||||
console.log("got wallet");
|
|
||||||
const pending = await wallet.getPendingOperations();
|
|
||||||
console.log(JSON.stringify(pending, undefined, 2));
|
|
||||||
process.exit(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function asyncSleep(milliSeconds: number): Promise<void> {
|
async function asyncSleep(milliSeconds: number): Promise<void> {
|
||||||
@ -184,6 +192,16 @@ async function asyncSleep(milliSeconds: number): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
walletCli
|
||||||
|
.subcommand("runPendingOpt", "run-pending", {
|
||||||
|
help: "Run pending operations."
|
||||||
|
})
|
||||||
|
.action(async (args) => {
|
||||||
|
withWallet(args, async (wallet) => {
|
||||||
|
await wallet.processPending();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
walletCli
|
walletCli
|
||||||
.subcommand("testMerchantQrcodeCmd", "test-merchant-qrcode")
|
.subcommand("testMerchantQrcodeCmd", "test-merchant-qrcode")
|
||||||
.requiredOption("amount", ["-a", "--amount"], clk.STRING, {
|
.requiredOption("amount", ["-a", "--amount"], clk.STRING, {
|
||||||
@ -279,7 +297,7 @@ walletCli
|
|||||||
|
|
||||||
walletCli
|
walletCli
|
||||||
.subcommand("withdrawUriCmd", "withdraw-uri")
|
.subcommand("withdrawUriCmd", "withdraw-uri")
|
||||||
.argument("withdrawUri", clk.STRING)
|
.requiredArgument("withdrawUri", clk.STRING)
|
||||||
.action(async args => {
|
.action(async args => {
|
||||||
applyVerbose(args.wallet.verbose);
|
applyVerbose(args.wallet.verbose);
|
||||||
const cmdArgs = args.withdrawUriCmd;
|
const cmdArgs = args.withdrawUriCmd;
|
||||||
@ -318,7 +336,7 @@ walletCli
|
|||||||
|
|
||||||
walletCli
|
walletCli
|
||||||
.subcommand("tipUriCmd", "tip-uri")
|
.subcommand("tipUriCmd", "tip-uri")
|
||||||
.argument("uri", clk.STRING)
|
.requiredArgument("uri", clk.STRING)
|
||||||
.action(async args => {
|
.action(async args => {
|
||||||
applyVerbose(args.wallet.verbose);
|
applyVerbose(args.wallet.verbose);
|
||||||
const tipUri = args.tipUriCmd.uri;
|
const tipUri = args.tipUriCmd.uri;
|
||||||
@ -334,7 +352,7 @@ walletCli
|
|||||||
|
|
||||||
walletCli
|
walletCli
|
||||||
.subcommand("refundUriCmd", "refund-uri")
|
.subcommand("refundUriCmd", "refund-uri")
|
||||||
.argument("uri", clk.STRING)
|
.requiredArgument("uri", clk.STRING)
|
||||||
.action(async args => {
|
.action(async args => {
|
||||||
applyVerbose(args.wallet.verbose);
|
applyVerbose(args.wallet.verbose);
|
||||||
const refundUri = args.refundUriCmd.uri;
|
const refundUri = args.refundUriCmd.uri;
|
||||||
@ -346,20 +364,38 @@ walletCli
|
|||||||
wallet.stop();
|
wallet.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
const exchangesCli = walletCli
|
const exchangesCli = walletCli.subcommand("exchangesCmd", "exchanges", {
|
||||||
.subcommand("exchangesCmd", "exchanges", {
|
help: "Manage exchanges.",
|
||||||
help: "Manage exchanges."
|
|
||||||
});
|
|
||||||
|
|
||||||
exchangesCli.subcommand("exchangesListCmd", "list", {
|
|
||||||
help: "List known exchanges."
|
|
||||||
});
|
});
|
||||||
|
|
||||||
exchangesCli.subcommand("exchangesListCmd", "update");
|
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
|
walletCli
|
||||||
.subcommand("payUriCmd", "pay-uri")
|
.subcommand("payUriCmd", "pay-uri")
|
||||||
.argument("url", clk.STRING)
|
.requiredArgument("url", clk.STRING)
|
||||||
.flag("autoYes", ["-y", "--yes"])
|
.flag("autoYes", ["-y", "--yes"])
|
||||||
.action(async args => {
|
.action(async args => {
|
||||||
applyVerbose(args.wallet.verbose);
|
applyVerbose(args.wallet.verbose);
|
||||||
@ -374,7 +410,7 @@ walletCli
|
|||||||
});
|
});
|
||||||
|
|
||||||
const testCli = walletCli.subcommand("testingArgs", "testing", {
|
const testCli = walletCli.subcommand("testingArgs", "testing", {
|
||||||
help: "Subcommands for testing GNU Taler deployments."
|
help: "Subcommands for testing GNU Taler deployments.",
|
||||||
});
|
});
|
||||||
|
|
||||||
testCli
|
testCli
|
||||||
|
@ -25,6 +25,7 @@ import { AmountJson } from "./amounts";
|
|||||||
import * as Amounts from "./amounts";
|
import * as Amounts from "./amounts";
|
||||||
|
|
||||||
import URI = require("urijs");
|
import URI = require("urijs");
|
||||||
|
import { Timestamp } from "./walletTypes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show an amount in a form suitable for the user.
|
* Show an amount in a form suitable for the user.
|
||||||
@ -125,6 +126,19 @@ export function getTalerStampSec(stamp: string): number | null {
|
|||||||
return parseInt(m[1], 10);
|
return parseInt(m[1], 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a timestamp from a Taler timestamp string.
|
||||||
|
*/
|
||||||
|
export function extractTalerStamp(stamp: string): Timestamp | undefined {
|
||||||
|
const m = stamp.match(/\/?Date\(([0-9]*)\)\/?/);
|
||||||
|
if (!m || !m[1]) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
t_ms: parseInt(m[1], 10) * 1000,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a timestamp is in the right format.
|
* Check if a timestamp is in the right format.
|
||||||
*/
|
*/
|
||||||
|
351
src/logging.ts
351
src/logging.ts
@ -1,351 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of TALER
|
|
||||||
(C) 2016 Inria
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
|
||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configurable logging. Allows to log persistently to a database.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
QueryRoot,
|
|
||||||
Store,
|
|
||||||
} from "./query";
|
|
||||||
import { openPromise } from "./promiseUtils";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supported log levels.
|
|
||||||
*/
|
|
||||||
export type Level = "error" | "debug" | "info" | "warn";
|
|
||||||
|
|
||||||
// Right now, our debug/info/warn/debug loggers just use the console based
|
|
||||||
// loggers. This might change in the future.
|
|
||||||
|
|
||||||
function makeInfo() {
|
|
||||||
return console.info.bind(console, "%o");
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeWarn() {
|
|
||||||
return console.warn.bind(console, "%o");
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeError() {
|
|
||||||
return console.error.bind(console, "%o");
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeDebug() {
|
|
||||||
return console.log.bind(console, "%o");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log a message using the configurable logger.
|
|
||||||
*/
|
|
||||||
export async function log(msg: string, level: Level = "info"): Promise<void> {
|
|
||||||
const ci = getCallInfo(2);
|
|
||||||
return record(level, msg, undefined, ci.file, ci.line, ci.column);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCallInfo(level: number) {
|
|
||||||
// see https://github.com/v8/v8/wiki/Stack-Trace-API
|
|
||||||
const stack = Error().stack;
|
|
||||||
if (!stack) {
|
|
||||||
return unknownFrame;
|
|
||||||
}
|
|
||||||
const lines = stack.split("\n");
|
|
||||||
return parseStackLine(lines[level + 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Frame {
|
|
||||||
column?: number;
|
|
||||||
file?: string;
|
|
||||||
line?: number;
|
|
||||||
method?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unknownFrame: Frame = {
|
|
||||||
column: 0,
|
|
||||||
file: "(unknown)",
|
|
||||||
line: 0,
|
|
||||||
method: "(unknown)",
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapted from https://github.com/errwischt/stacktrace-parser.
|
|
||||||
*/
|
|
||||||
function parseStackLine(stackLine: string): Frame {
|
|
||||||
// tslint:disable-next-line:max-line-length
|
|
||||||
const chrome = /^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
|
|
||||||
const gecko = /^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$/i;
|
|
||||||
const node = /^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$/i;
|
|
||||||
let parts;
|
|
||||||
|
|
||||||
parts = gecko.exec(stackLine);
|
|
||||||
if (parts) {
|
|
||||||
const f: Frame = {
|
|
||||||
column: parts[5] ? +parts[5] : undefined,
|
|
||||||
file: parts[3],
|
|
||||||
line: +parts[4],
|
|
||||||
method: parts[1] || "(unknown)",
|
|
||||||
};
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
parts = chrome.exec(stackLine);
|
|
||||||
if (parts) {
|
|
||||||
const f: Frame = {
|
|
||||||
column: parts[4] ? +parts[4] : undefined,
|
|
||||||
file: parts[2],
|
|
||||||
line: +parts[3],
|
|
||||||
method: parts[1] || "(unknown)",
|
|
||||||
};
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
parts = node.exec(stackLine);
|
|
||||||
if (parts) {
|
|
||||||
const f: Frame = {
|
|
||||||
column: parts[4] ? +parts[4] : undefined,
|
|
||||||
file: parts[2],
|
|
||||||
line: +parts[3],
|
|
||||||
method: parts[1] || "(unknown)",
|
|
||||||
};
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return unknownFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let db: IDBDatabase|undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A structured log entry as stored in the database.
|
|
||||||
*/
|
|
||||||
export interface LogEntry {
|
|
||||||
/**
|
|
||||||
* Soure code column where the error occured.
|
|
||||||
*/
|
|
||||||
col?: number;
|
|
||||||
/**
|
|
||||||
* Additional detail for the log statement.
|
|
||||||
*/
|
|
||||||
detail?: string;
|
|
||||||
/**
|
|
||||||
* Id of the log entry, used as primary
|
|
||||||
* key for the database.
|
|
||||||
*/
|
|
||||||
id?: number;
|
|
||||||
/**
|
|
||||||
* Log level, see [[Level}}.
|
|
||||||
*/
|
|
||||||
level: string;
|
|
||||||
/**
|
|
||||||
* Line where the log was created from.
|
|
||||||
*/
|
|
||||||
line?: number;
|
|
||||||
/**
|
|
||||||
* The actual log message.
|
|
||||||
*/
|
|
||||||
msg: string;
|
|
||||||
/**
|
|
||||||
* The source file where the log enctry
|
|
||||||
* was created from.
|
|
||||||
*/
|
|
||||||
source?: string;
|
|
||||||
/**
|
|
||||||
* Time when the log entry was created.
|
|
||||||
*/
|
|
||||||
timestamp: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all logs. Only use for debugging, since this returns all logs ever made
|
|
||||||
* at once without pagination.
|
|
||||||
*/
|
|
||||||
export async function getLogs(): Promise<LogEntry[]> {
|
|
||||||
if (!db) {
|
|
||||||
db = await openLoggingDb();
|
|
||||||
}
|
|
||||||
return await new QueryRoot(db).iter(logsStore).toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The barrier ensures that only one DB write is scheduled against the log db
|
|
||||||
* at the same time, so that the DB can stay responsive. This is a bit of a
|
|
||||||
* design problem with IndexedDB, it doesn't guarantee fairness.
|
|
||||||
*/
|
|
||||||
let barrier: any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Record an exeption in the log.
|
|
||||||
*/
|
|
||||||
export async function recordException(msg: string, e: any): Promise<void> {
|
|
||||||
let stack: string|undefined;
|
|
||||||
let frame: Frame|undefined;
|
|
||||||
try {
|
|
||||||
stack = e.stack;
|
|
||||||
if (stack) {
|
|
||||||
const lines = stack.split("\n");
|
|
||||||
frame = parseStackLine(lines[1]);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
if (!frame) {
|
|
||||||
frame = unknownFrame;
|
|
||||||
}
|
|
||||||
return record("error", e.toString(), stack, frame.file, frame.line, frame.column);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache for reports. Also used when something is so broken that we can't even
|
|
||||||
* access the database.
|
|
||||||
*/
|
|
||||||
const reportCache: { [reportId: string]: any } = {};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a UUID that does not use cryptographically secure randomness.
|
|
||||||
* Formatted as RFC4122 version 4 UUID.
|
|
||||||
*/
|
|
||||||
function getInsecureUuid() {
|
|
||||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c: string) => {
|
|
||||||
const r = Math.random() * 16 | 0;
|
|
||||||
const v = c === "x" ? r : (r & 0x3 | 0x8);
|
|
||||||
return v.toString(16);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a report and return a unique identifier to retrieve it later.
|
|
||||||
*/
|
|
||||||
export async function storeReport(report: any): Promise<string> {
|
|
||||||
const uid = getInsecureUuid();
|
|
||||||
reportCache[uid] = report;
|
|
||||||
return uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a report by its unique identifier.
|
|
||||||
*/
|
|
||||||
export async function getReport(reportUid: string): Promise<any> {
|
|
||||||
return reportCache[reportUid];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Record a log entry in the database.
|
|
||||||
*/
|
|
||||||
export async function record(level: Level,
|
|
||||||
msg: string,
|
|
||||||
detail?: string,
|
|
||||||
source?: string,
|
|
||||||
line?: number,
|
|
||||||
col?: number): Promise<void> {
|
|
||||||
if (typeof indexedDB === "undefined") {
|
|
||||||
console.log("can't access DB for logging in this context");
|
|
||||||
console.log("log was", { level, msg, detail, source, line, col });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let myBarrier: any;
|
|
||||||
|
|
||||||
if (barrier) {
|
|
||||||
const p = barrier.promise;
|
|
||||||
myBarrier = barrier = openPromise();
|
|
||||||
await p;
|
|
||||||
} else {
|
|
||||||
myBarrier = barrier = openPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!db) {
|
|
||||||
db = await openLoggingDb();
|
|
||||||
}
|
|
||||||
|
|
||||||
const count = await new QueryRoot(db).count(logsStore);
|
|
||||||
|
|
||||||
if (count > 1000) {
|
|
||||||
await new QueryRoot(db).deleteIf(logsStore, (e, i) => (i < 200));
|
|
||||||
}
|
|
||||||
|
|
||||||
const entry: LogEntry = {
|
|
||||||
col,
|
|
||||||
detail,
|
|
||||||
level,
|
|
||||||
line,
|
|
||||||
msg,
|
|
||||||
source,
|
|
||||||
timestamp: new Date().getTime(),
|
|
||||||
};
|
|
||||||
await new QueryRoot(db).put(logsStore, entry);
|
|
||||||
} finally {
|
|
||||||
await Promise.resolve().then(() => myBarrier.resolve());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loggingDbVersion = 2;
|
|
||||||
|
|
||||||
const logsStore: Store<LogEntry> = new Store<LogEntry>("logs");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a handle to the IndexedDB used to store
|
|
||||||
* logs.
|
|
||||||
*/
|
|
||||||
export function openLoggingDb(): Promise<IDBDatabase> {
|
|
||||||
return new Promise<IDBDatabase>((resolve, reject) => {
|
|
||||||
const req = indexedDB.open("taler-logging", loggingDbVersion);
|
|
||||||
req.onerror = (e) => {
|
|
||||||
reject(e);
|
|
||||||
};
|
|
||||||
req.onsuccess = (e) => {
|
|
||||||
resolve(req.result);
|
|
||||||
};
|
|
||||||
req.onupgradeneeded = (e) => {
|
|
||||||
const resDb = req.result;
|
|
||||||
if (e.oldVersion !== 0) {
|
|
||||||
try {
|
|
||||||
resDb.deleteObjectStore("logs");
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resDb.createObjectStore("logs", { keyPath: "id", autoIncrement: true });
|
|
||||||
resDb.createObjectStore("reports", { keyPath: "uid", autoIncrement: false });
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log a message at severity info.
|
|
||||||
*/
|
|
||||||
export const info = makeInfo();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log a message at severity debug.
|
|
||||||
*/
|
|
||||||
export const debug = makeDebug();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log a message at severity warn.
|
|
||||||
*/
|
|
||||||
export const warn = makeWarn();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log a message at severity error.
|
|
||||||
*/
|
|
||||||
export const error = makeError();
|
|
1182
src/query.ts
1182
src/query.ts
File diff suppressed because it is too large
Load Diff
1410
src/wallet.ts
1410
src/wallet.ts
File diff suppressed because it is too large
Load Diff
@ -34,8 +34,7 @@ import {
|
|||||||
CoinRecord,
|
CoinRecord,
|
||||||
DenominationRecord,
|
DenominationRecord,
|
||||||
ExchangeRecord,
|
ExchangeRecord,
|
||||||
ExchangeWireFeesRecord,
|
ExchangeWireInfo,
|
||||||
TipRecord,
|
|
||||||
} from "./dbTypes";
|
} from "./dbTypes";
|
||||||
import { CoinPaySig, ContractTerms, PayReq } from "./talerTypes";
|
import { CoinPaySig, ContractTerms, PayReq } from "./talerTypes";
|
||||||
|
|
||||||
@ -98,7 +97,7 @@ export interface ReserveCreationInfo {
|
|||||||
/**
|
/**
|
||||||
* Wire fees from the exchange.
|
* Wire fees from the exchange.
|
||||||
*/
|
*/
|
||||||
wireFees: ExchangeWireFeesRecord;
|
wireFees: ExchangeWireInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the wallet know about an auditor for
|
* Does the wallet know about an auditor for
|
||||||
@ -475,7 +474,6 @@ export interface PreparePayResultError {
|
|||||||
error: string;
|
error: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface PreparePayResultPaid {
|
export interface PreparePayResultPaid {
|
||||||
status: "paid";
|
status: "paid";
|
||||||
contractTerms: ContractTerms;
|
contractTerms: ContractTerms;
|
||||||
@ -517,18 +515,40 @@ export interface WalletDiagnostics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PendingWithdrawOperation {
|
export interface PendingWithdrawOperation {
|
||||||
type: "withdraw"
|
type: "withdraw";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PendingRefreshOperation {
|
export interface PendingRefreshOperation {
|
||||||
type: "refresh"
|
type: "refresh";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PendingPayOperation {
|
export interface PendingPayOperation {
|
||||||
type: "pay"
|
type: "pay";
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PendingOperationInfo = PendingWithdrawOperation
|
export interface OperationError {
|
||||||
|
type: string;
|
||||||
|
message: string;
|
||||||
|
details: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PendingExchangeUpdateOperation {
|
||||||
|
type: "exchange-update";
|
||||||
|
stage: string;
|
||||||
|
exchangeBaseUrl: string;
|
||||||
|
lastError?: OperationError;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PendingBugOperation {
|
||||||
|
type: "bug";
|
||||||
|
message: string;
|
||||||
|
details: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PendingOperationInfo =
|
||||||
|
| PendingWithdrawOperation
|
||||||
|
| PendingBugOperation
|
||||||
|
| PendingExchangeUpdateOperation;
|
||||||
|
|
||||||
export interface PendingOperationsResponse {
|
export interface PendingOperationsResponse {
|
||||||
pendingOperations: PendingOperationInfo[];
|
pendingOperations: PendingOperationInfo[];
|
||||||
@ -542,3 +562,23 @@ export interface HistoryQuery {
|
|||||||
*/
|
*/
|
||||||
level: number;
|
level: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Timestamp {
|
||||||
|
/**
|
||||||
|
* Timestamp in milliseconds.
|
||||||
|
*/
|
||||||
|
t_ms: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Duration {
|
||||||
|
/**
|
||||||
|
* Duration in milliseconds.
|
||||||
|
*/
|
||||||
|
d_ms: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTimestampNow(): Timestamp {
|
||||||
|
return {
|
||||||
|
t_ms: new Date().getTime(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -73,14 +73,6 @@ export interface MessageMap {
|
|||||||
request: { baseUrl: string };
|
request: { baseUrl: string };
|
||||||
response: dbTypes.ExchangeRecord;
|
response: dbTypes.ExchangeRecord;
|
||||||
};
|
};
|
||||||
"currency-info": {
|
|
||||||
request: { name: string };
|
|
||||||
response: dbTypes.CurrencyRecord;
|
|
||||||
};
|
|
||||||
"hash-contract": {
|
|
||||||
request: { contract: object };
|
|
||||||
response: string;
|
|
||||||
};
|
|
||||||
"reserve-creation-info": {
|
"reserve-creation-info": {
|
||||||
request: { baseUrl: string; amount: AmountJson };
|
request: { baseUrl: string; amount: AmountJson };
|
||||||
response: walletTypes.ReserveCreationInfo;
|
response: walletTypes.ReserveCreationInfo;
|
||||||
@ -145,14 +137,6 @@ export interface MessageMap {
|
|||||||
request: {};
|
request: {};
|
||||||
response: void;
|
response: void;
|
||||||
};
|
};
|
||||||
"log-and-display-error": {
|
|
||||||
request: any;
|
|
||||||
response: void;
|
|
||||||
};
|
|
||||||
"get-report": {
|
|
||||||
request: { reportUid: string };
|
|
||||||
response: void;
|
|
||||||
};
|
|
||||||
"get-purchase-details": {
|
"get-purchase-details": {
|
||||||
request: { contractTermsHash: string };
|
request: { contractTermsHash: string };
|
||||||
response: walletTypes.PurchaseDetails;
|
response: walletTypes.PurchaseDetails;
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Taler Wallet: Error Occured</title>
|
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css">
|
|
||||||
|
|
||||||
<link rel="icon" href="/img/icon.png">
|
|
||||||
|
|
||||||
<script src="/dist/page-common-bundle.js"></script>
|
|
||||||
<script src="/dist/error-bundle.js"></script>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="container"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,129 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of TALER
|
|
||||||
(C) 2015-2016 GNUnet e.V.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
|
||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Page shown to the user to confirm creation
|
|
||||||
* of a reserve, usually requested by the bank.
|
|
||||||
*
|
|
||||||
* @author Florian Dold
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import * as ReactDOM from "react-dom";
|
|
||||||
import URI = require("urijs");
|
|
||||||
|
|
||||||
import * as wxApi from "../wxApi";
|
|
||||||
|
|
||||||
import { Collapsible } from "../renderHtml";
|
|
||||||
|
|
||||||
interface ErrorProps {
|
|
||||||
report: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ErrorView extends React.Component<ErrorProps, { }> {
|
|
||||||
render(): JSX.Element {
|
|
||||||
const report = this.props.report;
|
|
||||||
if (!report) {
|
|
||||||
return (
|
|
||||||
<div id="main">
|
|
||||||
<h1>Error Report Not Found</h1>
|
|
||||||
<p>This page is supposed to display an error reported by the GNU Taler wallet,
|
|
||||||
but the corresponding error report can't be found.</p>
|
|
||||||
<p>Maybe the error occured before the browser was restarted or the wallet was reloaded.</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
switch (report.name) {
|
|
||||||
case "pay-post-failed": {
|
|
||||||
const summary = report.contractTerms.summary || report.contractTerms.order_id;
|
|
||||||
return (
|
|
||||||
<div id="main">
|
|
||||||
<h1>Failed to send payment</h1>
|
|
||||||
<p>
|
|
||||||
Failed to send payment for <strong>{summary}</strong>{" "}
|
|
||||||
to merchant <strong>{report.contractTerms.merchant.name}</strong>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
You can <a href={report.contractTerms.fulfillment_url}>retry</a> the payment.{" "}
|
|
||||||
If this problem persists, please contact the mechant with the error details below.
|
|
||||||
</p>
|
|
||||||
<Collapsible initiallyCollapsed={true} title="Error Details">
|
|
||||||
<pre>
|
|
||||||
{JSON.stringify(report, null, " ")}
|
|
||||||
</pre>
|
|
||||||
</Collapsible>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<div id="main">
|
|
||||||
<h1>Unknown Error</h1>
|
|
||||||
The GNU Taler wallet reported an unknown error. Here are the details:
|
|
||||||
<pre>
|
|
||||||
{JSON.stringify(report, null, " ")}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return (
|
|
||||||
<div id="main">
|
|
||||||
<h1>Error</h1>
|
|
||||||
The GNU Taler wallet reported an error. Here are the details:
|
|
||||||
<pre>
|
|
||||||
{JSON.stringify(report, null, " ")}
|
|
||||||
</pre>
|
|
||||||
A detailed error report could not be generated:
|
|
||||||
<pre>
|
|
||||||
{e.toString()}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const url = new URI(document.location.href);
|
|
||||||
const query: any = URI.parseQuery(url.query());
|
|
||||||
|
|
||||||
const container = document.getElementById("container");
|
|
||||||
if (!container) {
|
|
||||||
console.error("fatal: can't mount component, countainer missing");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// report that we'll render, either looked up from the
|
|
||||||
// logging module or synthesized here for fixed/fatal errors
|
|
||||||
let report;
|
|
||||||
|
|
||||||
const reportUid: string = query.reportUid;
|
|
||||||
if (!reportUid) {
|
|
||||||
report = {
|
|
||||||
name: "missing-error",
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
report = await wxApi.getReport(reportUid);
|
|
||||||
}
|
|
||||||
|
|
||||||
ReactDOM.render(<ErrorView report={report} />, container);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => main());
|
|
@ -1,27 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Taler Wallet: Logs</title>
|
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css">
|
|
||||||
|
|
||||||
<link rel="icon" href="/img/icon.png">
|
|
||||||
|
|
||||||
<script src="/dist/page-common-bundle.js"></script>
|
|
||||||
<script src="/dist/logs-bundle.js"></script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.tree-item {
|
|
||||||
margin: 2em;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid gray;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="container"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of TALER
|
|
||||||
(C) 2016 Inria
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
|
||||||
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show wallet logs.
|
|
||||||
*
|
|
||||||
* @author Florian Dold
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
LogEntry,
|
|
||||||
getLogs,
|
|
||||||
} from "../../logging";
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import * as ReactDOM from "react-dom";
|
|
||||||
|
|
||||||
interface LogViewProps {
|
|
||||||
log: LogEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LogView extends React.Component<LogViewProps, {}> {
|
|
||||||
render(): JSX.Element {
|
|
||||||
const e = this.props.log;
|
|
||||||
return (
|
|
||||||
<div className="tree-item">
|
|
||||||
<ul>
|
|
||||||
<li>level: {e.level}</li>
|
|
||||||
<li>msg: {e.msg}</li>
|
|
||||||
<li>id: {e.id || "unknown"}</li>
|
|
||||||
<li>file: {e.source || "(unknown)"}</li>
|
|
||||||
<li>line: {e.line || "(unknown)"}</li>
|
|
||||||
<li>col: {e.col || "(unknown)"}</li>
|
|
||||||
{(e.detail ? <li> detail: <pre>{e.detail}</pre></li> : [])}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LogsState {
|
|
||||||
logs: LogEntry[]|undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Logs extends React.Component<{}, LogsState> {
|
|
||||||
constructor(props: {}) {
|
|
||||||
super({});
|
|
||||||
this.update();
|
|
||||||
this.state = {} as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
async update() {
|
|
||||||
const logs = await getLogs();
|
|
||||||
this.setState({logs});
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): JSX.Element {
|
|
||||||
const logs = this.state.logs;
|
|
||||||
if (!logs) {
|
|
||||||
return <span>...</span>;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="tree-item">
|
|
||||||
Logs:
|
|
||||||
{logs.map((e) => <LogView log={e} />)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
ReactDOM.render(<Logs />, document.getElementById("container")!);
|
|
||||||
});
|
|
@ -137,12 +137,12 @@ function AuditorDetailsView(props: {
|
|||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (rci.exchangeInfo.auditors.length === 0) {
|
if ((rci.exchangeInfo.details?.auditors ?? []).length === 0) {
|
||||||
return <p>The exchange is not audited by any auditors.</p>;
|
return <p>The exchange is not audited by any auditors.</p>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{rci.exchangeInfo.auditors.map(a => (
|
{(rci.exchangeInfo.details?.auditors ?? []).map(a => (
|
||||||
<div>
|
<div>
|
||||||
<h3>Auditor {a.auditor_url}</h3>
|
<h3>Auditor {a.auditor_url}</h3>
|
||||||
<p>
|
<p>
|
||||||
@ -231,7 +231,7 @@ function FeeDetailsView(props: {
|
|||||||
<div>
|
<div>
|
||||||
<h3>Overview</h3>
|
<h3>Overview</h3>
|
||||||
<p>
|
<p>
|
||||||
Public key: <ExpanderText text={rci.exchangeInfo.masterPublicKey} />
|
Public key: <ExpanderText text={rci.exchangeInfo.details?.masterPublicKey ?? "??"} />
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{i18n.str`Withdrawal fees:`} {withdrawFee}
|
{i18n.str`Withdrawal fees:`} {withdrawFee}
|
||||||
|
@ -123,13 +123,6 @@ export function getCurrencies(): Promise<CurrencyRecord[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get information about a specific currency.
|
|
||||||
*/
|
|
||||||
export function getCurrency(name: string): Promise<CurrencyRecord|null> {
|
|
||||||
return callBackend("currency-info", {name});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get information about a specific exchange.
|
* Get information about a specific exchange.
|
||||||
@ -225,12 +218,6 @@ export function submitPay(contractTermsHash: string, sessionId: string | undefin
|
|||||||
return callBackend("submit-pay", { contractTermsHash, sessionId });
|
return callBackend("submit-pay", { contractTermsHash, sessionId });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Hash a contract. Throws if its not a valid contract.
|
|
||||||
*/
|
|
||||||
export function hashContract(contract: object): Promise<string> {
|
|
||||||
return callBackend("hash-contract", { contract });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark a reserve as confirmed.
|
* Mark a reserve as confirmed.
|
||||||
@ -284,25 +271,6 @@ export function returnCoins(args: { amount: AmountJson, exchange: string, sender
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Record an error report and display it in a tabl.
|
|
||||||
*
|
|
||||||
* If sameTab is set, the error report will be opened in the current tab,
|
|
||||||
* otherwise in a new tab.
|
|
||||||
*/
|
|
||||||
export function logAndDisplayError(args: any): Promise<void> {
|
|
||||||
return callBackend("log-and-display-error", args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an error report from the logging database for the
|
|
||||||
* given report UID.
|
|
||||||
*/
|
|
||||||
export function getReport(reportUid: string): Promise<any> {
|
|
||||||
return callBackend("get-report", { reportUid });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look up a purchase in the wallet database from
|
* Look up a purchase in the wallet database from
|
||||||
* the contract terms hash.
|
* the contract terms hash.
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { BrowserHttpLib } from "../http";
|
import { BrowserHttpLib } from "../http";
|
||||||
import * as logging from "../logging";
|
|
||||||
import { AmountJson } from "../amounts";
|
import { AmountJson } from "../amounts";
|
||||||
import {
|
import {
|
||||||
ConfirmReserveRequest,
|
ConfirmReserveRequest,
|
||||||
@ -138,22 +137,6 @@ async function handleMessage(
|
|||||||
}
|
}
|
||||||
return needsWallet().updateExchangeFromUrl(detail.baseUrl);
|
return needsWallet().updateExchangeFromUrl(detail.baseUrl);
|
||||||
}
|
}
|
||||||
case "currency-info": {
|
|
||||||
if (!detail.name) {
|
|
||||||
return Promise.resolve({ error: "name missing" });
|
|
||||||
}
|
|
||||||
return needsWallet().getCurrencyRecord(detail.name);
|
|
||||||
}
|
|
||||||
case "hash-contract": {
|
|
||||||
if (!detail.contract) {
|
|
||||||
return Promise.resolve({ error: "contract missing" });
|
|
||||||
}
|
|
||||||
return needsWallet()
|
|
||||||
.hashContract(detail.contract)
|
|
||||||
.then(hash => {
|
|
||||||
return hash;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case "reserve-creation-info": {
|
case "reserve-creation-info": {
|
||||||
if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
|
if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
|
||||||
return Promise.resolve({ error: "bad url" });
|
return Promise.resolve({ error: "bad url" });
|
||||||
@ -243,20 +226,6 @@ async function handleMessage(
|
|||||||
};
|
};
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
case "log-and-display-error":
|
|
||||||
logging.storeReport(detail).then(reportUid => {
|
|
||||||
const url = chrome.extension.getURL(
|
|
||||||
`/src/webex/pages/error.html?reportUid=${reportUid}`,
|
|
||||||
);
|
|
||||||
if (detail.sameTab && sender && sender.tab && sender.tab.id) {
|
|
||||||
chrome.tabs.update(detail.tabId, { url });
|
|
||||||
} else {
|
|
||||||
chrome.tabs.create({ url });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
case "get-report":
|
|
||||||
return logging.getReport(detail.reportUid);
|
|
||||||
case "get-purchase-details": {
|
case "get-purchase-details": {
|
||||||
const contractTermsHash = detail.contractTermsHash;
|
const contractTermsHash = detail.contractTermsHash;
|
||||||
if (!contractTermsHash) {
|
if (!contractTermsHash) {
|
||||||
@ -574,17 +543,6 @@ export async function wxMain() {
|
|||||||
chrome.runtime.reload();
|
chrome.runtime.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.onerror = (m, source, lineno, colno, error) => {
|
|
||||||
logging.record(
|
|
||||||
"error",
|
|
||||||
"".concat(m as any, error as any),
|
|
||||||
undefined,
|
|
||||||
source || "(unknown)",
|
|
||||||
lineno || 0,
|
|
||||||
colno || 0,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
chrome.tabs.query({}, tabs => {
|
chrome.tabs.query({}, tabs => {
|
||||||
console.log("got tabs", tabs);
|
console.log("got tabs", tabs);
|
||||||
for (const tab of tabs) {
|
for (const tab of tabs) {
|
||||||
|
@ -53,7 +53,6 @@
|
|||||||
"src/index.ts",
|
"src/index.ts",
|
||||||
"src/libtoolVersion-test.ts",
|
"src/libtoolVersion-test.ts",
|
||||||
"src/libtoolVersion.ts",
|
"src/libtoolVersion.ts",
|
||||||
"src/logging.ts",
|
|
||||||
"src/promiseUtils.ts",
|
"src/promiseUtils.ts",
|
||||||
"src/query.ts",
|
"src/query.ts",
|
||||||
"src/talerTypes.ts",
|
"src/talerTypes.ts",
|
||||||
@ -72,8 +71,6 @@
|
|||||||
"src/webex/pages/add-auditor.tsx",
|
"src/webex/pages/add-auditor.tsx",
|
||||||
"src/webex/pages/auditors.tsx",
|
"src/webex/pages/auditors.tsx",
|
||||||
"src/webex/pages/benchmark.tsx",
|
"src/webex/pages/benchmark.tsx",
|
||||||
"src/webex/pages/error.tsx",
|
|
||||||
"src/webex/pages/logs.tsx",
|
|
||||||
"src/webex/pages/pay.tsx",
|
"src/webex/pages/pay.tsx",
|
||||||
"src/webex/pages/payback.tsx",
|
"src/webex/pages/payback.tsx",
|
||||||
"src/webex/pages/popup.tsx",
|
"src/webex/pages/popup.tsx",
|
||||||
|
@ -80,8 +80,6 @@ module.exports = function (env) {
|
|||||||
"pay": "./src/webex/pages/pay.tsx",
|
"pay": "./src/webex/pages/pay.tsx",
|
||||||
"withdraw": "./src/webex/pages/withdraw.tsx",
|
"withdraw": "./src/webex/pages/withdraw.tsx",
|
||||||
"welcome": "./src/webex/pages/welcome.tsx",
|
"welcome": "./src/webex/pages/welcome.tsx",
|
||||||
"error": "./src/webex/pages/error.tsx",
|
|
||||||
"logs": "./src/webex/pages/logs.tsx",
|
|
||||||
"payback": "./src/webex/pages/payback.tsx",
|
"payback": "./src/webex/pages/payback.tsx",
|
||||||
"popup": "./src/webex/pages/popup.tsx",
|
"popup": "./src/webex/pages/popup.tsx",
|
||||||
"reset-required": "./src/webex/pages/reset-required.tsx",
|
"reset-required": "./src/webex/pages/reset-required.tsx",
|
||||||
|
@ -3378,10 +3378,10 @@ iconv-lite@0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer ">= 2.1.2 < 3"
|
safer-buffer ">= 2.1.2 < 3"
|
||||||
|
|
||||||
idb-bridge@^0.0.10:
|
idb-bridge@^0.0.11:
|
||||||
version "0.0.10"
|
version "0.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/idb-bridge/-/idb-bridge-0.0.10.tgz#69d59550dc722f6bf62cb98a4d4a2f2b9e66653c"
|
resolved "https://registry.yarnpkg.com/idb-bridge/-/idb-bridge-0.0.11.tgz#ba2fbd24b7e6f7f4de8333ed12b0912e64dda308"
|
||||||
integrity sha512-AE8YipDGjnS+APSlupcPNCNslCAErRwfUgJ8aNZigmVIr3xzI0YPGWr6NC6UI6ofpQ1DczUlaKqA86m1R/COGQ==
|
integrity sha512-fLlHce/WwT6eD3sc54gsfvM5fZqrhAPwBNH4uU/y6D0C1+0higH7OgC5/wploMhkmNYkQID3BMNZvSUBr0leSQ==
|
||||||
|
|
||||||
ieee754@^1.1.4:
|
ieee754@^1.1.4:
|
||||||
version "1.1.13"
|
version "1.1.13"
|
||||||
|
Loading…
Reference in New Issue
Block a user