This commit is contained in:
Sebastian 2023-04-19 12:42:47 -03:00
parent 7330f0daf9
commit d483a3f557
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
48 changed files with 960 additions and 1011 deletions

View File

@ -725,6 +725,9 @@ export interface WalletCoreVersion {
exchange: string; exchange: string;
merchant: string; merchant: string;
bank: string; bank: string;
/**
* @deprecated will be removed
*/
devMode: boolean; devMode: boolean;
} }
@ -2157,15 +2160,6 @@ export interface ConfirmPeerPullDebitRequest {
peerPullPaymentIncomingId: string; peerPullPaymentIncomingId: string;
} }
export interface SetDevModeRequest {
devModeEnabled: boolean;
}
export const codecForSetDevModeRequest = (): Codec<SetDevModeRequest> =>
buildCodecForObject<SetDevModeRequest>()
.property("devModeEnabled", codecForBoolean())
.build("SetDevModeRequest");
export interface ApplyDevExperimentRequest { export interface ApplyDevExperimentRequest {
devExperimentUri: string; devExperimentUri: string;
} }

View File

@ -27,25 +27,18 @@ import {
CoreApiResponse, CoreApiResponse,
Duration, Duration,
encodeCrock, encodeCrock,
ExtendedStatus,
getErrorDetailFromException, getErrorDetailFromException,
getRandomBytes, getRandomBytes,
j2s, j2s,
Logger, Logger,
parsePaytoUri, parsePaytoUri,
PaymentStatus,
PreparePayResultType, PreparePayResultType,
RecoveryMergeStrategy, RecoveryMergeStrategy,
RefreshReason,
sampleWalletCoreTransactions, sampleWalletCoreTransactions,
setDangerousTimetravel, setDangerousTimetravel,
setGlobalLogLevelFromString, setGlobalLogLevelFromString,
summarizeTalerErrorDetail, summarizeTalerErrorDetail,
TalerUriType, TalerUriType,
Transaction,
TransactionState,
TransactionSubstate,
TransactionType,
WalletNotification, WalletNotification,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { clk } from "@gnu-taler/taler-util/clk"; import { clk } from "@gnu-taler/taler-util/clk";
@ -61,15 +54,9 @@ import { JsonMessage, runRpcServer } from "@gnu-taler/taler-util/twrpc";
import { import {
createNativeWalletHost, createNativeWalletHost,
createNativeWalletHost2, createNativeWalletHost2,
CryptoDispatcher,
nativeCrypto,
//NodeThreadCryptoWorkerFactory,
//SynchronousCryptoWorkerFactoryPlain,
TalerCryptoInterface,
Wallet, Wallet,
WalletApiOperation, WalletApiOperation,
WalletCoreApiClient, WalletCoreApiClient,
walletCoreDebugFlags,
} from "@gnu-taler/taler-wallet-core"; } from "@gnu-taler/taler-wallet-core";
import { import {
createRemoteWallet, createRemoteWallet,
@ -264,12 +251,19 @@ async function createLocalWallet(
} }
}, },
cryptoWorkerType: walletCliArgs.wallet.cryptoWorker as any, cryptoWorkerType: walletCliArgs.wallet.cryptoWorker as any,
config: {
features: {
batchWithdrawal: checkEnvFlag("TALER_WALLET_BATCH_WITHDRAWAL"),
},
testing: {
devModeActive: checkEnvFlag("TALER_WALLET_DEV_MODE"),
denomselAllowLate: checkEnvFlag(
"TALER_WALLET_DEBUG_DENOMSEL_ALLOW_LATE",
),
},
},
}); });
if (checkEnvFlag("TALER_WALLET_BATCH_WITHDRAWAL")) {
wallet.setBatchWithdrawal(true);
}
applyVerbose(walletCliArgs.wallet.verbose); applyVerbose(walletCliArgs.wallet.verbose);
try { try {
await wallet.handleCoreApiRequest("initWallet", "native-init", { await wallet.handleCoreApiRequest("initWallet", "native-init", {
@ -1405,30 +1399,6 @@ advancedCli
}); });
}); });
advancedCli
.subcommand("enableDevMode", "enable-dev-mode", {
help: "Enable developer mode (dangerous!)",
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.client.call(WalletApiOperation.SetDevMode, {
devModeEnabled: true,
});
});
});
advancedCli
.subcommand("disableDevMode", "disable-dev-mode", {
help: "Disable developer mode",
})
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.client.call(WalletApiOperation.SetDevMode, {
devModeEnabled: false,
});
});
});
const coinPubListCodec = codecForList(codecForString()); const coinPubListCodec = codecForList(codecForString());
advancedCli advancedCli
@ -1647,35 +1617,6 @@ async function read(stream: NodeJS.ReadStream) {
return Buffer.concat(chunks).toString("utf8"); return Buffer.concat(chunks).toString("utf8");
} }
// testCli
// .subcommand("cryptoworker", "cryptoworker")
// .maybeOption("impl", ["--impl"], clk.STRING)
// .action(async (args) => {
// let cryptoApi: TalerCryptoInterface;
// if (!args.cryptoworker.impl || args.cryptoworker.impl === "node") {
// const workerFactory = new NodeThreadCryptoWorkerFactory();
// const cryptoDisp = new CryptoDispatcher(workerFactory);
// cryptoApi = cryptoDisp.cryptoApi;
// } else if (args.cryptoworker.impl === "sync") {
// const workerFactory = new SynchronousCryptoWorkerFactoryPlain();
// const cryptoDisp = new CryptoDispatcher(workerFactory);
// cryptoApi = cryptoDisp.cryptoApi;
// } else if (args.cryptoworker.impl === "none") {
// cryptoApi = nativeCrypto;
// } else {
// throw Error(`invalid crypto worker type ${args.cryptoworker.impl}`);
// }
// const input = "foo";
// console.log(`testing crypto worker by hashing string '${input}'`);
// const res = await cryptoApi.hashString({ str: input });
// console.log(res);
// });
export function main() { export function main() {
if (getenv("TALER_WALLET_DEBUG_DENOMSEL_ALLOW_LATE")) {
logger.warn("Allowing withdrawal of late denominations for debugging");
walletCoreDebugFlags.denomselAllowLate = true;
}
walletCli.run(); walletCli.run();
} }

View File

@ -65,7 +65,7 @@ import {
} from "./operations/withdraw.js"; } from "./operations/withdraw.js";
import { ExchangeInfo } from "./operations/exchanges.js"; import { ExchangeInfo } from "./operations/exchanges.js";
import { assembleRefreshRevealRequest } from "./operations/refresh.js"; import { assembleRefreshRevealRequest } from "./operations/refresh.js";
import { isWithdrawableDenom } from "./index.js"; import { isWithdrawableDenom, WalletConfig } from "./index.js";
const logger = new Logger("dbless.ts"); const logger = new Logger("dbless.ts");
@ -206,6 +206,7 @@ export async function withdrawCoin(args: {
} }
export function findDenomOrThrow( export function findDenomOrThrow(
denomselAllowLate: boolean,
exchangeInfo: ExchangeInfo, exchangeInfo: ExchangeInfo,
amount: AmountString, amount: AmountString,
): DenominationRecord { ): DenominationRecord {
@ -215,7 +216,10 @@ export function findDenomOrThrow(
fraction: d.amountFrac, fraction: d.amountFrac,
value: d.amountVal, value: d.amountVal,
}; };
if (Amounts.cmp(value, amount) === 0 && isWithdrawableDenom(d)) { if (
Amounts.cmp(value, amount) === 0 &&
isWithdrawableDenom(d, denomselAllowLate)
) {
return d; return d;
} }
} }

View File

@ -36,35 +36,6 @@ import {
const logger = new Logger("dev-experiments.ts"); const logger = new Logger("dev-experiments.ts");
export async function setDevMode(
ws: InternalWalletState,
enabled: boolean,
): Promise<void> {
if (enabled) {
logger.info("enabling devmode");
await ws.db
.mktx((x) => [x.config])
.runReadWrite(async (tx) => {
tx.config.put({
key: ConfigRecordKey.DevMode,
value: true,
});
});
await maybeInitDevMode(ws);
} else {
logger.info("disabling devmode");
await ws.db
.mktx((x) => [x.config])
.runReadWrite(async (tx) => {
tx.config.put({
key: ConfigRecordKey.DevMode,
value: false,
});
});
await leaveDevMode(ws);
}
}
/** /**
* Apply a dev experiment to the wallet database / state. * Apply a dev experiment to the wallet database / state.
*/ */
@ -78,7 +49,7 @@ export async function applyDevExperiment(
logger.info("unable to parse dev experiment URI"); logger.info("unable to parse dev experiment URI");
return; return;
} }
if (!ws.devModeActive) { if (!ws.config.testing.devModeActive) {
throw Error( throw Error(
"can't handle devmode URI (other than enable-devmode) unless devmode is active", "can't handle devmode URI (other than enable-devmode) unless devmode is active",
); );
@ -86,37 +57,6 @@ export async function applyDevExperiment(
throw Error(`dev-experiment id not understood ${parsedUri.devExperimentId}`); throw Error(`dev-experiment id not understood ${parsedUri.devExperimentId}`);
} }
/**
* Enter dev mode, if the wallet's config entry in the DB demands it.
*/
export async function maybeInitDevMode(ws: InternalWalletState): Promise<void> {
const devMode = await ws.db
.mktx((x) => [x.config])
.runReadOnly(async (tx) => {
const rec = await tx.config.get(ConfigRecordKey.DevMode);
if (!rec || rec.key !== ConfigRecordKey.DevMode) {
return false;
}
return rec.value;
});
if (!devMode) {
ws.devModeActive = false;
return;
}
ws.devModeActive = true;
if (ws.http instanceof DevExperimentHttpLib) {
return;
}
ws.http = new DevExperimentHttpLib(ws.http);
}
export async function leaveDevMode(ws: InternalWalletState): Promise<void> {
if (ws.http instanceof DevExperimentHttpLib) {
ws.http = ws.http.underlyingLib;
}
ws.devModeActive = false;
}
export class DevExperimentHttpLib implements HttpRequestLibrary { export class DevExperimentHttpLib implements HttpRequestLibrary {
_isDevExperimentLib = true; _isDevExperimentLib = true;
underlyingLib: HttpRequestLibrary; underlyingLib: HttpRequestLibrary;

View File

@ -16,6 +16,7 @@
import { WalletNotification } from "@gnu-taler/taler-util"; import { WalletNotification } from "@gnu-taler/taler-util";
import { HttpRequestLibrary } from "@gnu-taler/taler-util/http"; import { HttpRequestLibrary } from "@gnu-taler/taler-util/http";
import { WalletConfig, WalletConfigParameter } from "./index.js";
/** /**
* Helpers to initiate a wallet in a host environment. * Helpers to initiate a wallet in a host environment.
@ -44,6 +45,11 @@ export interface DefaultNodeWalletArgs {
httpLib?: HttpRequestLibrary; httpLib?: HttpRequestLibrary;
cryptoWorkerType?: "sync" | "node-worker-thread"; cryptoWorkerType?: "sync" | "node-worker-thread";
/**
* Config parameters
*/
config?: WalletConfigParameter;
} }
/** /**

View File

@ -144,7 +144,13 @@ export async function createNativeWalletHost2(
const timer = new SetTimeoutTimerAPI(); const timer = new SetTimeoutTimerAPI();
const w = await Wallet.create(myDb, myHttpLib, timer, workerFactory); const w = await Wallet.create(
myDb,
myHttpLib,
timer,
workerFactory,
args.config,
);
if (args.notifyHandler) { if (args.notifyHandler) {
w.addNotificationListener(args.notifyHandler); w.addNotificationListener(args.notifyHandler);

View File

@ -108,7 +108,13 @@ export async function createNativeWalletHost2(
const timer = new SetTimeoutTimerAPI(); const timer = new SetTimeoutTimerAPI();
const w = await Wallet.create(myDb, myHttpLib, timer, workerFactory); const w = await Wallet.create(
myDb,
myHttpLib,
timer,
workerFactory,
args.config,
);
if (args.notifyHandler) { if (args.notifyHandler) {
w.addNotificationListener(args.notifyHandler); w.addNotificationListener(args.notifyHandler);

View File

@ -36,7 +36,6 @@ export {
export * from "./pending-types.js"; export * from "./pending-types.js";
export * from "./util/debugFlags.js";
export { InternalWalletState } from "./internal-wallet-state.js"; export { InternalWalletState } from "./internal-wallet-state.js";
export * from "./wallet-api-types.js"; export * from "./wallet-api-types.js";
export * from "./wallet.js"; export * from "./wallet.js";

View File

@ -52,6 +52,7 @@ import {
GetReadWriteAccess, GetReadWriteAccess,
} from "./util/query.js"; } from "./util/query.js";
import { TimerGroup } from "./util/timer.js"; import { TimerGroup } from "./util/timer.js";
import { WalletConfig } from "./wallet-api-types.js";
export const EXCHANGE_COINS_LOCK = "exchange-coins-lock"; export const EXCHANGE_COINS_LOCK = "exchange-coins-lock";
export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock"; export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock";
@ -168,9 +169,7 @@ export interface InternalWalletState {
timerGroup: TimerGroup; timerGroup: TimerGroup;
stopped: boolean; stopped: boolean;
insecureTrustExchange: boolean; config: Readonly<WalletConfig>;
batchWithdrawal: boolean;
/** /**
* Asynchronous condition to interrupt the sleep of the * Asynchronous condition to interrupt the sleep of the
@ -191,8 +190,6 @@ export interface InternalWalletState {
merchantOps: MerchantOperations; merchantOps: MerchantOperations;
refreshOps: RefreshOperations; refreshOps: RefreshOperations;
devModeActive: boolean;
getDenomInfo( getDenomInfo(
ws: InternalWalletState, ws: InternalWalletState,
tx: GetReadOnlyAccess<{ tx: GetReadOnlyAccess<{

View File

@ -840,7 +840,12 @@ export async function getTotalFeesForDepositAmount(
denom.value, denom.value,
pcs.coinContributions[i], pcs.coinContributions[i],
).amount; ).amount;
const refreshCost = getTotalRefreshCost(allDenoms, denom, amountLeft); const refreshCost = getTotalRefreshCost(
allDenoms,
denom,
amountLeft,
ws.config.testing.denomselAllowLate,
);
refreshFee.push(refreshCost); refreshFee.push(refreshCost);
} }

View File

@ -245,7 +245,7 @@ async function validateWireInfo(
for (const a of wireInfo.accounts) { for (const a of wireInfo.accounts) {
logger.trace("validating exchange acct"); logger.trace("validating exchange acct");
let isValid = false; let isValid = false;
if (ws.insecureTrustExchange) { if (ws.config.testing.insecureTrustExchange) {
isValid = true; isValid = true;
} else { } else {
const { valid: v } = await ws.cryptoApi.isValidWireAccount({ const { valid: v } = await ws.cryptoApi.isValidWireAccount({
@ -275,7 +275,7 @@ async function validateWireInfo(
wireFee: Amounts.stringify(x.wire_fee), wireFee: Amounts.stringify(x.wire_fee),
}; };
let isValid = false; let isValid = false;
if (ws.insecureTrustExchange) { if (ws.config.testing.insecureTrustExchange) {
isValid = true; isValid = true;
} else { } else {
const { valid: v } = await ws.cryptoApi.isValidWireFee({ const { valid: v } = await ws.cryptoApi.isValidWireFee({
@ -308,7 +308,7 @@ async function validateGlobalFees(
for (const gf of fees) { for (const gf of fees) {
logger.trace("validating exchange global fees"); logger.trace("validating exchange global fees");
let isValid = false; let isValid = false;
if (ws.insecureTrustExchange) { if (ws.config.testing.insecureTrustExchange) {
isValid = true; isValid = true;
} else { } else {
const { valid: v } = await ws.cryptoApi.isValidGlobalFees({ const { valid: v } = await ws.cryptoApi.isValidGlobalFees({
@ -665,7 +665,10 @@ export async function updateExchangeFromUrlHandler(
let ageMask = 0; let ageMask = 0;
for (const x of keysInfo.currentDenominations) { for (const x of keysInfo.currentDenominations) {
if (isWithdrawableDenom(x) && x.denomPub.age_mask != 0) { if (
isWithdrawableDenom(x, ws.config.testing.denomselAllowLate) &&
x.denomPub.age_mask != 0
) {
ageMask = x.denomPub.age_mask; ageMask = x.denomPub.age_mask;
break; break;
} }

View File

@ -168,6 +168,7 @@ export async function getTotalPaymentCost(
allDenoms, allDenoms,
DenominationRecord.toDenomInfo(denom), DenominationRecord.toDenomInfo(denom),
amountLeft, amountLeft,
ws.config.testing.denomselAllowLate,
); );
costs.push(Amounts.parseOrThrow(pcs.coinContributions[i])); costs.push(Amounts.parseOrThrow(pcs.coinContributions[i]));
costs.push(refreshCost); costs.push(refreshCost);
@ -1659,6 +1660,7 @@ async function applySuccessfulRefund(
p: PurchaseRecord, p: PurchaseRecord,
refreshCoinsMap: Record<string, CoinRefreshRequest>, refreshCoinsMap: Record<string, CoinRefreshRequest>,
r: MerchantCoinRefundSuccessStatus, r: MerchantCoinRefundSuccessStatus,
denomselAllowLate: boolean,
): Promise<void> { ): Promise<void> {
// FIXME: check signature before storing it as valid! // FIXME: check signature before storing it as valid!
@ -1688,6 +1690,7 @@ async function applySuccessfulRefund(
allDenoms, allDenoms,
DenominationRecord.toDenomInfo(denom), DenominationRecord.toDenomInfo(denom),
amountLeft, amountLeft,
denomselAllowLate,
); );
refreshCoinsMap[coin.coinPub] = { refreshCoinsMap[coin.coinPub] = {
@ -1714,6 +1717,7 @@ async function storePendingRefund(
}>, }>,
p: PurchaseRecord, p: PurchaseRecord,
r: MerchantCoinRefundFailureStatus, r: MerchantCoinRefundFailureStatus,
denomselAllowLate: boolean,
): Promise<void> { ): Promise<void> {
const refundKey = getRefundKey(r); const refundKey = getRefundKey(r);
@ -1745,6 +1749,7 @@ async function storePendingRefund(
allDenoms, allDenoms,
DenominationRecord.toDenomInfo(denom), DenominationRecord.toDenomInfo(denom),
amountLeft, amountLeft,
denomselAllowLate,
); );
p.refunds[refundKey] = { p.refunds[refundKey] = {
@ -1767,6 +1772,7 @@ async function storeFailedRefund(
p: PurchaseRecord, p: PurchaseRecord,
refreshCoinsMap: Record<string, CoinRefreshRequest>, refreshCoinsMap: Record<string, CoinRefreshRequest>,
r: MerchantCoinRefundFailureStatus, r: MerchantCoinRefundFailureStatus,
denomselAllowLate: boolean,
): Promise<void> { ): Promise<void> {
const refundKey = getRefundKey(r); const refundKey = getRefundKey(r);
@ -1797,6 +1803,7 @@ async function storeFailedRefund(
allDenoms, allDenoms,
DenominationRecord.toDenomInfo(denom), DenominationRecord.toDenomInfo(denom),
amountLeft, amountLeft,
denomselAllowLate,
); );
p.refunds[refundKey] = { p.refunds[refundKey] = {
@ -1905,11 +1912,28 @@ async function acceptRefunds(
// Invariant: (!existingRefundInfo) || (existingRefundInfo === Pending) // Invariant: (!existingRefundInfo) || (existingRefundInfo === Pending)
if (refundStatus.type === "success") { if (refundStatus.type === "success") {
await applySuccessfulRefund(tx, p, refreshCoinsMap, refundStatus); await applySuccessfulRefund(
tx,
p,
refreshCoinsMap,
refundStatus,
ws.config.testing.denomselAllowLate,
);
} else if (isPermanentFailure) { } else if (isPermanentFailure) {
await storeFailedRefund(tx, p, refreshCoinsMap, refundStatus); await storeFailedRefund(
tx,
p,
refreshCoinsMap,
refundStatus,
ws.config.testing.denomselAllowLate,
);
} else { } else {
await storePendingRefund(tx, p, refundStatus); await storePendingRefund(
tx,
p,
refundStatus,
ws.config.testing.denomselAllowLate,
);
} }
} }

View File

@ -417,6 +417,7 @@ export async function getTotalPeerPaymentCost(
allDenoms, allDenoms,
DenominationRecord.toDenomInfo(denom), DenominationRecord.toDenomInfo(denom),
amountLeft, amountLeft,
ws.config.testing.denomselAllowLate,
); );
costs.push(Amounts.parseOrThrow(pcs[i].contribution)); costs.push(Amounts.parseOrThrow(pcs[i].contribution));
costs.push(refreshCost); costs.push(refreshCost);

View File

@ -86,7 +86,7 @@ import {
import { makeCoinAvailable } from "./common.js"; import { makeCoinAvailable } from "./common.js";
import { updateExchangeFromUrl } from "./exchanges.js"; import { updateExchangeFromUrl } from "./exchanges.js";
import { selectWithdrawalDenominations } from "../util/coinSelection.js"; import { selectWithdrawalDenominations } from "../util/coinSelection.js";
import { isWithdrawableDenom } from "../index.js"; import { isWithdrawableDenom, WalletConfig } from "../index.js";
const logger = new Logger("refresh.ts"); const logger = new Logger("refresh.ts");
@ -105,13 +105,18 @@ export function getTotalRefreshCost(
denoms: DenominationRecord[], denoms: DenominationRecord[],
refreshedDenom: DenominationInfo, refreshedDenom: DenominationInfo,
amountLeft: AmountJson, amountLeft: AmountJson,
denomselAllowLate: boolean,
): AmountJson { ): AmountJson {
const withdrawAmount = Amounts.sub( const withdrawAmount = Amounts.sub(
amountLeft, amountLeft,
refreshedDenom.feeRefresh, refreshedDenom.feeRefresh,
).amount; ).amount;
const denomMap = Object.fromEntries(denoms.map((x) => [x.denomPubHash, x])); const denomMap = Object.fromEntries(denoms.map((x) => [x.denomPubHash, x]));
const withdrawDenoms = selectWithdrawalDenominations(withdrawAmount, denoms); const withdrawDenoms = selectWithdrawalDenominations(
withdrawAmount,
denoms,
denomselAllowLate,
);
const resultingAmount = Amounts.add( const resultingAmount = Amounts.add(
Amounts.zeroOfCurrency(withdrawAmount.currency), Amounts.zeroOfCurrency(withdrawAmount.currency),
...withdrawDenoms.selectedDenoms.map( ...withdrawDenoms.selectedDenoms.map(
@ -232,6 +237,7 @@ async function refreshCreateSession(
const newCoinDenoms = selectWithdrawalDenominations( const newCoinDenoms = selectWithdrawalDenominations(
availableAmount, availableAmount,
availableDenoms, availableDenoms,
ws.config.testing.denomselAllowLate,
); );
if (newCoinDenoms.selectedDenoms.length === 0) { if (newCoinDenoms.selectedDenoms.length === 0) {
@ -897,7 +903,7 @@ export async function createRefreshGroup(
const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
.iter(exchangeBaseUrl) .iter(exchangeBaseUrl)
.filter((x) => { .filter((x) => {
return isWithdrawableDenom(x); return isWithdrawableDenom(x, ws.config.testing.denomselAllowLate);
}); });
denomsPerExchange[exchangeBaseUrl] = allDenoms; denomsPerExchange[exchangeBaseUrl] = allDenoms;
return allDenoms; return allDenoms;
@ -955,6 +961,7 @@ export async function createRefreshGroup(
denoms, denoms,
denom, denom,
Amounts.parseOrThrow(refreshAmount), Amounts.parseOrThrow(refreshAmount),
ws.config.testing.denomselAllowLate,
); );
const output = Amounts.sub(refreshAmount, cost).amount; const output = Amounts.sub(refreshAmount, cost).amount;
estimatedOutputPerCoin.push(output); estimatedOutputPerCoin.push(output);

View File

@ -210,7 +210,9 @@ export async function getCandidateWithdrawalDenoms(
const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl.getAll( const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl.getAll(
exchangeBaseUrl, exchangeBaseUrl,
); );
return allDenoms.filter(isWithdrawableDenom); return allDenoms.filter((d) =>
isWithdrawableDenom(d, ws.config.testing.denomselAllowLate),
);
}); });
} }
@ -719,7 +721,7 @@ export async function updateWithdrawalDenoms(
}) signature of ${denom.denomPubHash}`, }) signature of ${denom.denomPubHash}`,
); );
let valid = false; let valid = false;
if (ws.insecureTrustExchange) { if (ws.config.testing.insecureTrustExchange) {
valid = true; valid = true;
} else { } else {
const res = await ws.cryptoApi.isValidDenom({ const res = await ws.cryptoApi.isValidDenom({
@ -1003,7 +1005,7 @@ export async function processWithdrawalGroup(
const resp = await processPlanchetExchangeBatchRequest(ws, wgContext, { const resp = await processPlanchetExchangeBatchRequest(ws, wgContext, {
batchSize: maxBatchSize, batchSize: maxBatchSize,
coinStartIndex: i, coinStartIndex: i,
useBatchRequest: ws.batchWithdrawal, useBatchRequest: ws.config.features.batchWithdrawal,
}); });
let work: Promise<void>[] = []; let work: Promise<void>[] = [];
work = []; work = [];
@ -1180,6 +1182,7 @@ export async function getExchangeWithdrawalInfo(
const selectedDenoms = selectWithdrawalDenominations( const selectedDenoms = selectWithdrawalDenominations(
instructedAmount, instructedAmount,
denoms, denoms,
ws.config.testing.denomselAllowLate,
); );
if (selectedDenoms.selectedDenoms.length === 0) { if (selectedDenoms.selectedDenoms.length === 0) {
@ -1710,9 +1713,14 @@ export async function internalCreateWithdrawalGroup(
amount, amount,
denoms, denoms,
args.forcedDenomSel, args.forcedDenomSel,
ws.config.testing.denomselAllowLate,
); );
} else { } else {
initialDenomSel = selectWithdrawalDenominations(amount, denoms); initialDenomSel = selectWithdrawalDenominations(
amount,
denoms,
ws.config.testing.denomselAllowLate,
);
} }
const withdrawalGroup: WithdrawalGroupRecord = { const withdrawalGroup: WithdrawalGroupRecord = {

View File

@ -48,7 +48,11 @@ import {
AllowedExchangeInfo, AllowedExchangeInfo,
DenominationRecord, DenominationRecord,
} from "../db.js"; } from "../db.js";
import { getExchangeDetails, isWithdrawableDenom } from "../index.js"; import {
getExchangeDetails,
isWithdrawableDenom,
WalletConfig,
} from "../index.js";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
import { getMerchantPaymentBalanceDetails } from "../operations/balance.js"; import { getMerchantPaymentBalanceDetails } from "../operations/balance.js";
import { checkDbInvariant, checkLogicInvariant } from "./invariants.js"; import { checkDbInvariant, checkLogicInvariant } from "./invariants.js";
@ -664,6 +668,7 @@ export async function selectCandidates(
export function selectWithdrawalDenominations( export function selectWithdrawalDenominations(
amountAvailable: AmountJson, amountAvailable: AmountJson,
denoms: DenominationRecord[], denoms: DenominationRecord[],
denomselAllowLate: boolean = false,
): DenomSelectionState { ): DenomSelectionState {
let remaining = Amounts.copy(amountAvailable); let remaining = Amounts.copy(amountAvailable);
@ -675,7 +680,7 @@ export function selectWithdrawalDenominations(
let totalCoinValue = Amounts.zeroOfCurrency(amountAvailable.currency); let totalCoinValue = Amounts.zeroOfCurrency(amountAvailable.currency);
let totalWithdrawCost = Amounts.zeroOfCurrency(amountAvailable.currency); let totalWithdrawCost = Amounts.zeroOfCurrency(amountAvailable.currency);
denoms = denoms.filter(isWithdrawableDenom); denoms = denoms.filter((d) => isWithdrawableDenom(d, denomselAllowLate));
denoms.sort((d1, d2) => denoms.sort((d1, d2) =>
Amounts.cmp( Amounts.cmp(
DenominationRecord.getValue(d2), DenominationRecord.getValue(d2),
@ -737,6 +742,7 @@ export function selectForcedWithdrawalDenominations(
amountAvailable: AmountJson, amountAvailable: AmountJson,
denoms: DenominationRecord[], denoms: DenominationRecord[],
forcedDenomSel: ForcedDenomSel, forcedDenomSel: ForcedDenomSel,
denomselAllowLate: boolean,
): DenomSelectionState { ): DenomSelectionState {
const selectedDenoms: { const selectedDenoms: {
count: number; count: number;
@ -746,7 +752,7 @@ export function selectForcedWithdrawalDenominations(
let totalCoinValue = Amounts.zeroOfCurrency(amountAvailable.currency); let totalCoinValue = Amounts.zeroOfCurrency(amountAvailable.currency);
let totalWithdrawCost = Amounts.zeroOfCurrency(amountAvailable.currency); let totalWithdrawCost = Amounts.zeroOfCurrency(amountAvailable.currency);
denoms = denoms.filter(isWithdrawableDenom); denoms = denoms.filter((d) => isWithdrawableDenom(d, denomselAllowLate));
denoms.sort((d1, d2) => denoms.sort((d1, d2) =>
Amounts.cmp( Amounts.cmp(
DenominationRecord.getValue(d2), DenominationRecord.getValue(d2),

View File

@ -1,32 +0,0 @@
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Debug flags for wallet-core.
*
* @author Florian Dold
*/
export interface WalletCoreDebugFlags {
/**
* Allow withdrawal of denominations even though they are about to expire.
*/
denomselAllowLate: boolean;
}
export const walletCoreDebugFlags: WalletCoreDebugFlags = {
denomselAllowLate: false,
};

View File

@ -29,7 +29,7 @@ import {
WireFee, WireFee,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { DenominationRecord } from "../db.js"; import { DenominationRecord } from "../db.js";
import { walletCoreDebugFlags } from "./debugFlags.js"; import { WalletConfig } from "../index.js";
/** /**
* Given a list of denominations with the same value and same period of time: * Given a list of denominations with the same value and same period of time:
@ -452,13 +452,16 @@ export function createTimeline<Type extends object>(
* Check if a denom is withdrawable based on the expiration time, * Check if a denom is withdrawable based on the expiration time,
* revocation and offered state. * revocation and offered state.
*/ */
export function isWithdrawableDenom(d: DenominationRecord): boolean { export function isWithdrawableDenom(
d: DenominationRecord,
denomselAllowLate?: boolean,
): boolean {
const now = AbsoluteTime.now(); const now = AbsoluteTime.now();
const start = AbsoluteTime.fromTimestamp(d.stampStart); const start = AbsoluteTime.fromTimestamp(d.stampStart);
const withdrawExpire = AbsoluteTime.fromTimestamp(d.stampExpireWithdraw); const withdrawExpire = AbsoluteTime.fromTimestamp(d.stampExpireWithdraw);
const started = AbsoluteTime.cmp(now, start) >= 0; const started = AbsoluteTime.cmp(now, start) >= 0;
let lastPossibleWithdraw: AbsoluteTime; let lastPossibleWithdraw: AbsoluteTime;
if (walletCoreDebugFlags.denomselAllowLate) { if (denomselAllowLate) {
lastPossibleWithdraw = start; lastPossibleWithdraw = start;
} else { } else {
lastPossibleWithdraw = AbsoluteTime.subtractDuraction( lastPossibleWithdraw = AbsoluteTime.subtractDuraction(

View File

@ -29,8 +29,6 @@ import {
AcceptExchangeTosRequest, AcceptExchangeTosRequest,
AcceptManualWithdrawalRequest, AcceptManualWithdrawalRequest,
AcceptManualWithdrawalResult, AcceptManualWithdrawalResult,
ConfirmPeerPullDebitRequest,
ConfirmPeerPushCreditRequest,
AcceptTipRequest, AcceptTipRequest,
AcceptTipResponse, AcceptTipResponse,
AcceptWithdrawalResponse, AcceptWithdrawalResponse,
@ -42,17 +40,18 @@ import {
ApplyRefundResponse, ApplyRefundResponse,
BackupRecovery, BackupRecovery,
BalancesResponse, BalancesResponse,
PreparePeerPullDebitRequest, CheckPeerPullCreditRequest,
PreparePeerPullDebitResponse, CheckPeerPullCreditResponse,
PreparePeerPushCredit, CheckPeerPushDebitRequest,
PreparePeerPushCreditResponse, CheckPeerPushDebitResponse,
CoinDumpJson, CoinDumpJson,
ConfirmPayRequest, ConfirmPayRequest,
ConfirmPayResult, ConfirmPayResult,
ConfirmPeerPullDebitRequest,
ConfirmPeerPushCreditRequest,
CreateDepositGroupRequest, CreateDepositGroupRequest,
CreateDepositGroupResponse, CreateDepositGroupResponse,
DeleteTransactionRequest, DeleteTransactionRequest,
DepositGroupFees,
ExchangeDetailedResponse, ExchangeDetailedResponse,
ExchangesListResponse, ExchangesListResponse,
ForceRefreshRequest, ForceRefreshRequest,
@ -63,12 +62,12 @@ import {
GetExchangeTosResult, GetExchangeTosResult,
GetWithdrawalDetailsForAmountRequest, GetWithdrawalDetailsForAmountRequest,
GetWithdrawalDetailsForUriRequest, GetWithdrawalDetailsForUriRequest,
InitRequest,
InitResponse,
InitiatePeerPullCreditRequest, InitiatePeerPullCreditRequest,
InitiatePeerPullCreditResponse, InitiatePeerPullCreditResponse,
InitiatePeerPushPaymentRequest, InitiatePeerPushPaymentRequest,
InitiatePeerPushPaymentResponse, InitiatePeerPushPaymentResponse,
InitRequest,
InitResponse,
IntegrationTestArgs, IntegrationTestArgs,
KnownBankAccounts, KnownBankAccounts,
ListKnownBankAccountsRequest, ListKnownBankAccountsRequest,
@ -78,10 +77,10 @@ import {
PreparePayRequest, PreparePayRequest,
PreparePayResult, PreparePayResult,
PreparePayTemplateRequest, PreparePayTemplateRequest,
CheckPeerPullCreditRequest, PreparePeerPullDebitRequest,
CheckPeerPullCreditResponse, PreparePeerPullDebitResponse,
CheckPeerPushDebitRequest, PreparePeerPushCredit,
CheckPeerPushDebitResponse, PreparePeerPushCreditResponse,
PrepareRefundRequest, PrepareRefundRequest,
PrepareRefundResult, PrepareRefundResult,
PrepareTipRequest, PrepareTipRequest,
@ -89,7 +88,6 @@ import {
RecoveryLoadRequest, RecoveryLoadRequest,
RetryTransactionRequest, RetryTransactionRequest,
SetCoinSuspendedRequest, SetCoinSuspendedRequest,
SetDevModeRequest,
SetWalletDeviceIdRequest, SetWalletDeviceIdRequest,
TestPayArgs, TestPayArgs,
TestPayResult, TestPayResult,
@ -97,21 +95,21 @@ import {
TransactionByIdRequest, TransactionByIdRequest,
TransactionsRequest, TransactionsRequest,
TransactionsResponse, TransactionsResponse,
TxIdResponse,
UserAttentionByIdRequest, UserAttentionByIdRequest,
UserAttentionsCountResponse, UserAttentionsCountResponse,
UserAttentionsRequest, UserAttentionsRequest,
UserAttentionsResponse, UserAttentionsResponse,
ValidateIbanRequest,
ValidateIbanResponse,
WalletBackupContentV1, WalletBackupContentV1,
WalletCoreVersion, WalletCoreVersion,
WalletCurrencyInfo, WalletCurrencyInfo,
WithdrawFakebankRequest, WithdrawFakebankRequest,
WithdrawTestBalanceRequest, WithdrawTestBalanceRequest,
WithdrawUriInfoResponse, WithdrawUriInfoResponse,
ValidateIbanRequest,
ValidateIbanResponse,
TxIdResponse,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletContractData } from "./db.js"; import { AuditorTrustRecord, WalletContractData } from "./db.js";
import { import {
AddBackupProviderRequest, AddBackupProviderRequest,
AddBackupProviderResponse, AddBackupProviderResponse,
@ -195,13 +193,11 @@ export enum WalletApiOperation {
ConfirmPeerPullDebit = "confirmPeerPullDebit", ConfirmPeerPullDebit = "confirmPeerPullDebit",
ClearDb = "clearDb", ClearDb = "clearDb",
Recycle = "recycle", Recycle = "recycle",
SetDevMode = "setDevMode",
ApplyDevExperiment = "applyDevExperiment", ApplyDevExperiment = "applyDevExperiment",
ValidateIban = "validateIban", ValidateIban = "validateIban",
} }
// group: Initialization // group: Initialization
type EmptyObject = Record<string, never>; type EmptyObject = Record<string, never>;
/** /**
* Initialize wallet-core. * Initialize wallet-core.
@ -739,12 +735,6 @@ export type ApplyDevExperimentOp = {
response: EmptyObject; response: EmptyObject;
}; };
export type SetDevModeOp = {
op: WalletApiOperation.SetDevMode;
request: SetDevModeRequest;
response: EmptyObject;
};
/** /**
* Run a simple integration test on a test deployment * Run a simple integration test on a test deployment
* of the exchange and merchant. * of the exchange and merchant.
@ -953,7 +943,6 @@ export type WalletOperations = {
[WalletApiOperation.ClearDb]: ClearDbOp; [WalletApiOperation.ClearDb]: ClearDbOp;
[WalletApiOperation.Recycle]: RecycleOp; [WalletApiOperation.Recycle]: RecycleOp;
[WalletApiOperation.ApplyDevExperiment]: ApplyDevExperimentOp; [WalletApiOperation.ApplyDevExperiment]: ApplyDevExperimentOp;
[WalletApiOperation.SetDevMode]: SetDevModeOp;
[WalletApiOperation.ValidateIban]: ValidateIbanOp; [WalletApiOperation.ValidateIban]: ValidateIbanOp;
}; };
@ -973,3 +962,50 @@ export interface WalletCoreApiClient {
payload: WalletCoreRequestType<Op>, payload: WalletCoreRequestType<Op>,
): Promise<WalletCoreResponseType<Op>>; ): Promise<WalletCoreResponseType<Op>>;
} }
type Primitives = string | number | boolean;
export type RecursivePartial<T extends object> = {
[P in keyof T]?: T[P] extends Array<infer U extends object>
? Array<RecursivePartial<U>>
: T[P] extends Array<infer J extends Primitives>
? Array<J>
: T[P] extends object
? RecursivePartial<T[P]>
: T[P];
} & object;
export type WalletConfigParameter = RecursivePartial<WalletConfig>;
export interface WalletConfig {
/**
* Initialization values useful for a complete startup.
*
* These are values may be overridden by different wallets
*/
builtin: {
exchanges: string[];
auditors: AuditorTrustRecord[];
};
/**
* Unsafe options which it should only be used to create
* testing environment.
*/
testing: {
/**
* Allow withdrawal of denominations even though they are about to expire.
*/
denomselAllowLate: boolean;
devModeActive: boolean;
insecureTrustExchange: boolean;
};
/**
* Configurations values that may be safe to show to the user
*/
features: {
batchWithdrawal: boolean;
allowHttp: boolean;
};
}

View File

@ -25,12 +25,38 @@
import { import {
AbsoluteTime, AbsoluteTime,
Amounts, Amounts,
CoinDumpJson,
CoinRefreshRequest,
CoinStatus,
CoreApiResponse,
DenomOperationMap,
DenominationInfo,
Duration,
ExchangeDetailedResponse,
ExchangeListItem,
ExchangeTosStatusDetails,
ExchangesListResponse,
FeeDescription,
GetExchangeTosResult,
InitResponse,
KnownBankAccounts,
KnownBankAccountsInfo,
Logger,
ManualWithdrawalDetails,
MerchantUsingTemplateDetails,
NotificationType,
RefreshReason,
TalerError,
TalerErrorCode,
URL,
ValidateIbanResponse,
WalletCoreVersion,
WalletNotification,
codecForAbortTransaction, codecForAbortTransaction,
codecForAcceptBankIntegratedWithdrawalRequest, codecForAcceptBankIntegratedWithdrawalRequest,
codecForAcceptExchangeTosRequest, codecForAcceptExchangeTosRequest,
codecForAcceptManualWithdrawalRequet, codecForAcceptManualWithdrawalRequet,
codecForAcceptPeerPullPaymentRequest, codecForAcceptPeerPullPaymentRequest,
codecForConfirmPeerPushPaymentRequest,
codecForAcceptTipRequest, codecForAcceptTipRequest,
codecForAddExchangeRequest, codecForAddExchangeRequest,
codecForAddKnownBankAccounts, codecForAddKnownBankAccounts,
@ -39,8 +65,9 @@ import {
codecForApplyRefundFromPurchaseIdRequest, codecForApplyRefundFromPurchaseIdRequest,
codecForApplyRefundRequest, codecForApplyRefundRequest,
codecForCheckPeerPullPaymentRequest, codecForCheckPeerPullPaymentRequest,
codecForPreparePeerPushCreditRequest, codecForCheckPeerPushDebitRequest,
codecForConfirmPayRequest, codecForConfirmPayRequest,
codecForConfirmPeerPushPaymentRequest,
codecForCreateDepositGroupRequest, codecForCreateDepositGroupRequest,
codecForDeleteTransactionRequest, codecForDeleteTransactionRequest,
codecForForceRefreshRequest, codecForForceRefreshRequest,
@ -54,86 +81,58 @@ import {
codecForInitiatePeerPullPaymentRequest, codecForInitiatePeerPullPaymentRequest,
codecForInitiatePeerPushPaymentRequest, codecForInitiatePeerPushPaymentRequest,
codecForIntegrationTestArgs, codecForIntegrationTestArgs,
codecForIntegrationTestV2Args,
codecForListKnownBankAccounts, codecForListKnownBankAccounts,
codecForMerchantPostOrderResponse, codecForMerchantPostOrderResponse,
codecForPrepareDepositRequest, codecForPrepareDepositRequest,
codecForPreparePayRequest, codecForPreparePayRequest,
codecForPreparePayTemplateRequest, codecForPreparePayTemplateRequest,
codecForPreparePeerPullPaymentRequest, codecForPreparePeerPullPaymentRequest,
codecForCheckPeerPushDebitRequest, codecForPreparePeerPushCreditRequest,
codecForPrepareRefundRequest, codecForPrepareRefundRequest,
codecForPrepareTipRequest, codecForPrepareTipRequest,
codecForResumeTransaction,
codecForRetryTransactionRequest, codecForRetryTransactionRequest,
codecForSetCoinSuspendedRequest, codecForSetCoinSuspendedRequest,
codecForSetDevModeRequest,
codecForSetWalletDeviceIdRequest, codecForSetWalletDeviceIdRequest,
codecForSuspendTransaction,
codecForTestPayArgs, codecForTestPayArgs,
codecForTransactionByIdRequest, codecForTransactionByIdRequest,
codecForTransactionsRequest, codecForTransactionsRequest,
codecForUserAttentionByIdRequest, codecForUserAttentionByIdRequest,
codecForUserAttentionsRequest, codecForUserAttentionsRequest,
codecForValidateIbanRequest,
codecForWithdrawFakebankRequest, codecForWithdrawFakebankRequest,
codecForWithdrawTestBalance, codecForWithdrawTestBalance,
CoinDumpJson,
CoinRefreshRequest,
CoinStatus,
constructPayUri, constructPayUri,
CoreApiResponse,
DenominationInfo,
DenomOperationMap,
Duration,
durationFromSpec, durationFromSpec,
durationMin, durationMin,
ExchangeDetailedResponse, getErrorDetailFromException,
ExchangeListItem,
ExchangesListResponse,
ExchangeTosStatusDetails,
FeeDescription,
GetExchangeTosResult,
InitResponse,
j2s, j2s,
KnownBankAccounts,
KnownBankAccountsInfo,
Logger,
ManualWithdrawalDetails,
MerchantUsingTemplateDetails,
NotificationType,
parsePayTemplateUri, parsePayTemplateUri,
parsePaytoUri, parsePaytoUri,
RefreshReason,
TalerErrorCode,
URL,
WalletCoreVersion,
WalletNotification,
codecForSuspendTransaction,
codecForResumeTransaction,
validateIban, validateIban,
codecForValidateIbanRequest,
ValidateIbanResponse,
codecForIntegrationTestV2Args,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import {
HttpRequestLibrary,
readSuccessResponseJsonOrThrow,
} from "@gnu-taler/taler-util/http";
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js"; import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
import { import {
CryptoDispatcher, CryptoDispatcher,
CryptoWorkerFactory, CryptoWorkerFactory,
} from "./crypto/workers/crypto-dispatcher.js"; } from "./crypto/workers/crypto-dispatcher.js";
import { import {
AuditorTrustRecord,
clearDatabase,
CoinSourceType, CoinSourceType,
ConfigRecordKey, ConfigRecordKey,
DenominationRecord, DenominationRecord,
ExchangeDetailsRecord, ExchangeDetailsRecord,
WalletStoresV1,
clearDatabase,
exportDb, exportDb,
importDb, importDb,
WalletStoresV1,
} from "./db.js"; } from "./db.js";
import { import { DevExperimentHttpLib, applyDevExperiment } from "./dev-experiments.js";
applyDevExperiment,
maybeInitDevMode,
setDevMode,
} from "./dev-experiments.js";
import { getErrorDetailFromException, TalerError } from "@gnu-taler/taler-util";
import { import {
ActiveLongpollInfo, ActiveLongpollInfo,
ExchangeOperations, ExchangeOperations,
@ -198,18 +197,18 @@ import {
processPurchase, processPurchase,
} from "./operations/pay-merchant.js"; } from "./operations/pay-merchant.js";
import { import {
confirmPeerPullDebit,
confirmPeerPushCredit,
preparePeerPullDebit,
preparePeerPushCredit,
initiatePeerPullPayment,
initiatePeerPushPayment,
checkPeerPullPaymentInitiation, checkPeerPullPaymentInitiation,
checkPeerPushDebit, checkPeerPushDebit,
confirmPeerPullDebit,
confirmPeerPushCredit,
initiatePeerPullPayment,
initiatePeerPushPayment,
preparePeerPullDebit,
preparePeerPushCredit,
processPeerPullCredit, processPeerPullCredit,
processPeerPushInitiation,
processPeerPullDebit, processPeerPullDebit,
processPeerPushCredit, processPeerPushCredit,
processPeerPushInitiation,
} from "./operations/pay-peer.js"; } from "./operations/pay-peer.js";
import { getPendingOperations } from "./operations/pending.js"; import { getPendingOperations } from "./operations/pending.js";
import { import {
@ -252,10 +251,6 @@ import {
selectBestForOverlappingDenominations, selectBestForOverlappingDenominations,
selectMinimumFee, selectMinimumFee,
} from "./util/denominations.js"; } from "./util/denominations.js";
import {
HttpRequestLibrary,
readSuccessResponseJsonOrThrow,
} from "@gnu-taler/taler-util/http";
import { checkDbInvariant } from "./util/invariants.js"; import { checkDbInvariant } from "./util/invariants.js";
import { import {
AsyncCondition, AsyncCondition,
@ -275,22 +270,14 @@ import {
WALLET_MERCHANT_PROTOCOL_VERSION, WALLET_MERCHANT_PROTOCOL_VERSION,
} from "./versions.js"; } from "./versions.js";
import { import {
RecursivePartial,
WalletApiOperation, WalletApiOperation,
WalletConfig,
WalletConfigParameter,
WalletCoreApiClient, WalletCoreApiClient,
WalletCoreResponseType, WalletCoreResponseType,
} from "./wallet-api-types.js"; } from "./wallet-api-types.js";
const builtinAuditors: AuditorTrustRecord[] = [
{
currency: "KUDOS",
auditorPub: "BW9DC48PHQY4NH011SHHX36DZZ3Q22Y6X7FZ1VD1CMZ2PTFZ6PN0",
auditorBaseUrl: "https://auditor.demo.taler.net/",
uids: ["5P25XF8TVQP9AW6VYGY2KV47WT5Y3ZXFSJAA570GJPX5SVJXKBVG"],
},
];
const builtinExchanges: string[] = ["https://exchange.demo.taler.net/"];
const logger = new Logger("wallet.ts"); const logger = new Logger("wallet.ts");
/** /**
@ -495,10 +482,10 @@ async function fillDefaults(ws: InternalWalletState): Promise<void> {
return; return;
} }
logger.info("importing default exchanges and auditors"); logger.info("importing default exchanges and auditors");
for (const c of builtinAuditors) { for (const c of ws.config.builtin.auditors) {
await tx.auditorTrust.put(c); await tx.auditorTrust.put(c);
} }
for (const baseUrl of builtinExchanges) { for (const baseUrl of ws.config.builtin.exchanges) {
const now = AbsoluteTime.now(); const now = AbsoluteTime.now();
provideExchangeRecordInTx(ws, tx, baseUrl, now); provideExchangeRecordInTx(ws, tx, baseUrl, now);
} }
@ -1018,7 +1005,6 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
logger.trace("filling defaults"); logger.trace("filling defaults");
await fillDefaults(ws); await fillDefaults(ws);
} }
await maybeInitDevMode(ws);
const resp: InitResponse = { const resp: InitResponse = {
versionInfo: getVersion(ws), versionInfo: getVersion(ws),
}; };
@ -1482,15 +1468,10 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
await applyDevExperiment(ws, req.devExperimentUri); await applyDevExperiment(ws, req.devExperimentUri);
return {}; return {};
} }
case WalletApiOperation.SetDevMode: {
const req = codecForSetDevModeRequest().decode(payload);
await setDevMode(ws, req.devModeEnabled);
return {};
}
case WalletApiOperation.GetVersion: { case WalletApiOperation.GetVersion: {
return getVersion(ws); return getVersion(ws);
} }
//default: // default:
// assertUnreachable(operation); // assertUnreachable(operation);
} }
throw TalerError.fromDetail( throw TalerError.fromDetail(
@ -1509,7 +1490,7 @@ export function getVersion(ws: InternalWalletState): WalletCoreVersion {
exchange: WALLET_EXCHANGE_PROTOCOL_VERSION, exchange: WALLET_EXCHANGE_PROTOCOL_VERSION,
merchant: WALLET_MERCHANT_PROTOCOL_VERSION, merchant: WALLET_MERCHANT_PROTOCOL_VERSION,
bank: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, bank: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
devMode: ws.devModeActive, devMode: false,
}; };
return version; return version;
} }
@ -1575,8 +1556,15 @@ export class Wallet {
http: HttpRequestLibrary, http: HttpRequestLibrary,
timer: TimerAPI, timer: TimerAPI,
cryptoWorkerFactory: CryptoWorkerFactory, cryptoWorkerFactory: CryptoWorkerFactory,
config?: WalletConfigParameter,
) { ) {
this.ws = new InternalWalletStateImpl(db, http, timer, cryptoWorkerFactory); this.ws = new InternalWalletStateImpl(
db,
http,
timer,
cryptoWorkerFactory,
config ?? {},
);
} }
get client(): WalletCoreApiClient { get client(): WalletCoreApiClient {
@ -1586,29 +1574,22 @@ export class Wallet {
return this._client; return this._client;
} }
/**
* Trust the exchange, do not validate signatures.
* Only used to benchmark the exchange.
*/
setInsecureTrustExchange(): void {
this.ws.insecureTrustExchange = true;
}
setBatchWithdrawal(enable: boolean): void {
this.ws.batchWithdrawal = enable;
}
static async create( static async create(
db: DbAccess<typeof WalletStoresV1>, db: DbAccess<typeof WalletStoresV1>,
http: HttpRequestLibrary, http: HttpRequestLibrary,
timer: TimerAPI, timer: TimerAPI,
cryptoWorkerFactory: CryptoWorkerFactory, cryptoWorkerFactory: CryptoWorkerFactory,
config?: WalletConfigParameter,
): Promise<Wallet> { ): Promise<Wallet> {
const w = new Wallet(db, http, timer, cryptoWorkerFactory); const w = new Wallet(db, http, timer, cryptoWorkerFactory, config);
w._client = await getClientFromWalletState(w.ws); w._client = await getClientFromWalletState(w.ws);
return w; return w;
} }
static getDefaultConfig(): Readonly<WalletConfig> {
return InternalWalletStateImpl.defaultConfig;
}
addNotificationListener(f: (n: WalletNotification) => void): void { addNotificationListener(f: (n: WalletNotification) => void): void {
return this.ws.addNotificationListener(f); return this.ws.addNotificationListener(f);
} }
@ -1650,10 +1631,6 @@ class InternalWalletStateImpl implements InternalWalletState {
merchantInfoCache: Record<string, MerchantInfo> = {}; merchantInfoCache: Record<string, MerchantInfo> = {};
insecureTrustExchange = false;
batchWithdrawal = false;
readonly timerGroup: TimerGroup; readonly timerGroup: TimerGroup;
latch = new AsyncCondition(); latch = new AsyncCondition();
stopped = false; stopped = false;
@ -1662,8 +1639,6 @@ class InternalWalletStateImpl implements InternalWalletState {
initCalled = false; initCalled = false;
devModeActive = false;
exchangeOps: ExchangeOperations = { exchangeOps: ExchangeOperations = {
getExchangeDetails, getExchangeDetails,
getExchangeTrust, getExchangeTrust,
@ -1696,6 +1671,31 @@ class InternalWalletStateImpl implements InternalWalletState {
*/ */
private resourceLocks: Set<string> = new Set(); private resourceLocks: Set<string> = new Set();
config: Readonly<WalletConfig>;
public static defaultConfig: Readonly<WalletConfig> = {
builtin: {
exchanges: ["https://exchange.demo.taler.net/"],
auditors: [
{
currency: "KUDOS",
auditorPub: "BW9DC48PHQY4NH011SHHX36DZZ3Q22Y6X7FZ1VD1CMZ2PTFZ6PN0",
auditorBaseUrl: "https://auditor.demo.taler.net/",
uids: ["5P25XF8TVQP9AW6VYGY2KV47WT5Y3ZXFSJAA570GJPX5SVJXKBVG"],
},
],
},
features: {
batchWithdrawal: false,
allowHttp: false,
},
testing: {
devModeActive: false,
insecureTrustExchange: false,
denomselAllowLate: false,
},
};
constructor( constructor(
// FIXME: Make this a getter and make // FIXME: Make this a getter and make
// the actual value nullable. // the actual value nullable.
@ -1705,10 +1705,15 @@ class InternalWalletStateImpl implements InternalWalletState {
public http: HttpRequestLibrary, public http: HttpRequestLibrary,
public timer: TimerAPI, public timer: TimerAPI,
cryptoWorkerFactory: CryptoWorkerFactory, cryptoWorkerFactory: CryptoWorkerFactory,
config: WalletConfigParameter,
) { ) {
this.cryptoDispatcher = new CryptoDispatcher(cryptoWorkerFactory); this.cryptoDispatcher = new CryptoDispatcher(cryptoWorkerFactory);
this.cryptoApi = this.cryptoDispatcher.cryptoApi; this.cryptoApi = this.cryptoDispatcher.cryptoApi;
this.timerGroup = new TimerGroup(timer); this.timerGroup = new TimerGroup(timer);
this.config = deepMerge(InternalWalletStateImpl.defaultConfig, config);
if (this.config.testing.devModeActive) {
this.http = new DevExperimentHttpLib(this.http);
}
} }
async getDenomInfo( async getDenomInfo(
@ -1808,3 +1813,29 @@ class InternalWalletStateImpl implements InternalWalletState {
} }
} }
} }
/**
* Take the full object as template, create a new result with all the values.
* Use the override object to change the values in the result
* return result
* @param full
* @param override
* @returns
*/
function deepMerge<T extends object>(
full: T,
override: RecursivePartial<T>,
): T {
const keys = Object.keys(full);
const result = { ...full };
for (const k of keys) {
// @ts-ignore
const newVal = override[k];
if (newVal === undefined) continue;
// @ts-ignore
result[k] =
// @ts-ignore
typeof newVal === "object" ? deepMerge(full[k], newVal) : newVal;
}
return result;
}

View File

@ -26,7 +26,7 @@
*/ */
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { JustInDevMode } from "./components/JustInDevMode.js"; import { EnabledBySettings } from "./components/EnabledBySettings.js";
import { import {
NavigationHeader, NavigationHeader,
NavigationHeaderHolder, NavigationHeaderHolder,
@ -183,11 +183,11 @@ export function PopupNavBar({ path }: { path?: PopupNavBarOptions }): VNode {
<a href={Pages.balance} class={path === "balance" ? "active" : ""}> <a href={Pages.balance} class={path === "balance" ? "active" : ""}>
<i18n.Translate>Balance</i18n.Translate> <i18n.Translate>Balance</i18n.Translate>
</a> </a>
<JustInDevMode> <EnabledBySettings name="backup">
<a href={Pages.backup} class={path === "backup" ? "active" : ""}> <a href={Pages.backup} class={path === "backup" ? "active" : ""}>
<i18n.Translate>Backup</i18n.Translate> <i18n.Translate>Backup</i18n.Translate>
</a> </a>
</JustInDevMode> </EnabledBySettings>
<div style={{ display: "flex", paddingTop: 4, justifyContent: "right" }}> <div style={{ display: "flex", paddingTop: 4, justifyContent: "right" }}>
{attentionCount > 0 ? ( {attentionCount > 0 ? (
<a href={Pages.notifications}> <a href={Pages.notifications}>
@ -238,11 +238,11 @@ export function WalletNavBar({ path }: { path?: WalletNavBarOptions }): VNode {
<a href={Pages.balance} class={path === "balance" ? "active" : ""}> <a href={Pages.balance} class={path === "balance" ? "active" : ""}>
<i18n.Translate>Balance</i18n.Translate> <i18n.Translate>Balance</i18n.Translate>
</a> </a>
<JustInDevMode> <EnabledBySettings name="backup">
<a href={Pages.backup} class={path === "backup" ? "active" : ""}> <a href={Pages.backup} class={path === "backup" ? "active" : ""}>
<i18n.Translate>Backup</i18n.Translate> <i18n.Translate>Backup</i18n.Translate>
</a> </a>
</JustInDevMode> </EnabledBySettings>
{attentionCount > 0 ? ( {attentionCount > 0 ? (
<a href={Pages.notifications}> <a href={Pages.notifications}>
@ -252,11 +252,11 @@ export function WalletNavBar({ path }: { path?: WalletNavBarOptions }): VNode {
<Fragment /> <Fragment />
)} )}
<JustInDevMode> <EnabledBySettings name="advanceMode">
<a href={Pages.dev} class={path === "dev" ? "active" : ""}> <a href={Pages.dev} class={path === "dev" ? "active" : ""}>
<i18n.Translate>Dev</i18n.Translate> <i18n.Translate>Dev tools</i18n.Translate>
</a> </a>
</JustInDevMode> </EnabledBySettings>
<div <div
style={{ display: "flex", paddingTop: 4, justifyContent: "right" }} style={{ display: "flex", paddingTop: 4, justifyContent: "right" }}

View File

@ -27,7 +27,6 @@ import { platform, setupPlatform } from "./platform/background.js";
import devAPI from "./platform/dev.js"; import devAPI from "./platform/dev.js";
import { wxMain } from "./wxBackend.js"; import { wxMain } from "./wxBackend.js";
console.log("Wallet setup for Dev API");
setupPlatform(devAPI); setupPlatform(devAPI);
async function start() { async function start() {

View File

@ -35,10 +35,8 @@ const isFirefox =
// FIXME: create different entry point for any platform instead of // FIXME: create different entry point for any platform instead of
// switching in runtime // switching in runtime
if (isFirefox) { if (isFirefox) {
console.log("Wallet setup for Firefox API");
setupPlatform(firefoxAPI); setupPlatform(firefoxAPI);
} else { } else {
console.log("Wallet setup for Chrome API");
setupPlatform(chromeAPI); setupPlatform(chromeAPI);
} }

View File

@ -14,14 +14,25 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { ComponentChildren, Fragment, h, VNode } from "preact"; import { ComponentChildren, Fragment, h, VNode } from "preact";
import { useDevContext } from "../context/devContext.js"; import { useSettings } from "../hooks/useSettings.js";
import { Settings } from "../platform/api.js";
export function JustInDevMode({ export function EnabledBySettings<K extends keyof Settings>({
children, children,
value,
name,
}: { }: {
name: K;
value?: Settings[K];
children: ComponentChildren; children: ComponentChildren;
}): VNode { }): VNode {
const { devMode } = useDevContext(); const [settings] = useSettings();
if (!devMode) return <Fragment />; if (value === undefined) {
if (!settings[name]) return <Fragment />;
return <Fragment>{children}</Fragment>;
}
if (settings[name] !== value) {
return <Fragment />;
}
return <Fragment>{children}</Fragment>; return <Fragment>{children}</Fragment>;
} }

View File

@ -17,8 +17,8 @@ import { TalerErrorDetail, TranslatedString } from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import arrowDown from "../svg/chevron-down.svg"; import arrowDown from "../svg/chevron-down.svg";
import { useDevContext } from "../context/devContext.js";
import { ErrorBox } from "./styled/index.js"; import { ErrorBox } from "./styled/index.js";
import { EnabledBySettings } from "./EnabledBySettings.js";
export function ErrorTalerOperation({ export function ErrorTalerOperation({
title, title,
@ -27,7 +27,6 @@ export function ErrorTalerOperation({
title?: TranslatedString; title?: TranslatedString;
error?: TalerErrorDetail; error?: TalerErrorDetail;
}): VNode | null { }): VNode | null {
const { devMode } = useDevContext();
const [showErrorDetail, setShowErrorDetail] = useState(false); const [showErrorDetail, setShowErrorDetail] = useState(false);
if (!title || !error) return null; if (!title || !error) return null;
@ -62,11 +61,11 @@ export function ErrorTalerOperation({
<b>{error.hint}</b> {!errorHint ? "" : `: ${errorHint}`}{" "} <b>{error.hint}</b> {!errorHint ? "" : `: ${errorHint}`}{" "}
</div> </div>
</div> </div>
{devMode && ( <EnabledBySettings name="showJsonOnError">
<div style={{ textAlign: "left", overflowX: "auto" }}> <div style={{ textAlign: "left", overflowX: "auto" }}>
<pre>{JSON.stringify(error, undefined, 2)}</pre> <pre>{JSON.stringify(error, undefined, 2)}</pre>
</div> </div>
)} </EnabledBySettings>
</Fragment> </Fragment>
)} )}
</ErrorBox> </ErrorBox>

View File

@ -14,7 +14,11 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { ExchangeTosStatus, GetExchangeTosResult } from "@gnu-taler/taler-util"; import {
ExchangeTosStatus,
GetExchangeTosResult,
Logger,
} from "@gnu-taler/taler-util";
export function buildTermsOfServiceState( export function buildTermsOfServiceState(
tos: GetExchangeTosResult, tos: GetExchangeTosResult,
@ -27,6 +31,8 @@ export function buildTermsOfServiceState(
return { content, status: tos.tosStatus, version: tos.currentEtag }; return { content, status: tos.tosStatus, version: tos.currentEtag };
} }
const logger = new Logger("termsofservice");
function parseTermsOfServiceContent( function parseTermsOfServiceContent(
type: string, type: string,
text: string, text: string,
@ -36,28 +42,28 @@ function parseTermsOfServiceContent(
const document = new DOMParser().parseFromString(text, "text/xml"); const document = new DOMParser().parseFromString(text, "text/xml");
return { type: "xml", document }; return { type: "xml", document };
} catch (e) { } catch (e) {
console.log(e); logger.error("error parsing xml", e);
} }
} else if (type === "text/html") { } else if (type === "text/html") {
try { try {
const href = new URL(text); const href = new URL(text);
return { type: "html", href }; return { type: "html", href };
} catch (e) { } catch (e) {
console.log(e); logger.error("error parsing url", e);
} }
} else if (type === "text/json") { } else if (type === "text/json") {
try { try {
const data = JSON.parse(text); const data = JSON.parse(text);
return { type: "json", data }; return { type: "json", data };
} catch (e) { } catch (e) {
console.log(e); logger.error("error parsing json", e);
} }
} else if (type === "text/pdf") { } else if (type === "text/pdf") {
try { try {
const location = new URL(text); const location = new URL(text);
return { type: "pdf", location }; return { type: "pdf", location };
} catch (e) { } catch (e) {
console.log(e); logger.error("error parsing url", e);
} }
} }
const content = text; const content = text;

View File

@ -1,70 +0,0 @@
/*
This file is part of GNU Taler
(C) 2022 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { createContext, h, VNode } from "preact";
import { useContext } from "preact/hooks";
import { useWalletDevMode } from "../hooks/useWalletDevMode.js";
interface Type {
devMode: boolean;
toggle: () => Promise<void>;
}
const Context = createContext<Type>({
devMode: false,
toggle: async () => {
null;
},
});
export const useDevContext = (): Type => useContext(Context);
export const DevContextProviderForTesting = ({
value,
children,
}: {
value?: boolean;
children: any;
}): VNode => {
return h(Context.Provider, {
value: {
devMode: !!value,
toggle: async () => {
null;
},
},
children,
});
};
export const DevContextProvider = ({ children }: { children: any }): VNode => {
const devModeToggle = useWalletDevMode();
const value: Type = {
devMode: !!devModeToggle.value,
toggle: devModeToggle.toggle,
};
//support for function as children, useful for getting the value right away
children =
children.length === 1 && typeof children === "function"
? children(value)
: children;
return h(Context.Provider, { value, children });
};

View File

@ -167,8 +167,6 @@ export function useComponentState({
if (fu) { if (fu) {
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
document.location.href = fu; document.location.href = fu;
} else {
console.log(`should d to ${fu}`);
} }
} }
onSuccess(res.transactionId); onSuccess(res.transactionId);

View File

@ -30,7 +30,6 @@ export function ReadyView({
}: State.FillTemplate): VNode { }: State.FillTemplate): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
console.log("is summary", !!summary);
return ( return (
<Fragment> <Fragment>
<section style={{ textAlign: "left" }}> <section style={{ textAlign: "left" }}>

View File

@ -51,7 +51,6 @@ export function useClipboardPermissions(): ToggleHandler {
// .call("toggleHeaderListener", false) // .call("toggleHeaderListener", false)
// .then((r) => setEnabled(r.newValue)); // .then((r) => setEnabled(r.newValue));
// } catch (e) { // } catch (e) {
// console.log(e);
// } // }
} }
return; return;

View File

@ -36,7 +36,6 @@ export function useSettings(): [
function updateField<T extends keyof Settings>(k: T, v: Settings[T]) { function updateField<T extends keyof Settings>(k: T, v: Settings[T]) {
const newValue = { ...parsed, [k]: v }; const newValue = { ...parsed, [k]: v };
const json = JSON.stringify(newValue); const json = JSON.stringify(newValue);
console.log(json);
update(json); update(json);
} }
return [parsed, updateField]; return [parsed, updateField];

View File

@ -1,52 +0,0 @@
/*
This file is part of GNU Taler
(C) 2022 Taler Systems S.A.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks";
import { useBackendContext } from "../context/backend.js";
type Result = {
value: boolean | undefined;
toggle: () => Promise<void>;
};
export function useWalletDevMode(): Result {
const [enabled, setEnabled] = useState<undefined | boolean>(undefined);
const api = useBackendContext();
// const { pushAlertOnError } = useAlertContext();
async function handleOpen(): Promise<void> {
const nextValue = !enabled;
await api.wallet.call(WalletApiOperation.SetDevMode, {
devModeEnabled: nextValue,
});
setEnabled(nextValue);
return;
}
useEffect(() => {
async function getValue(): Promise<void> {
const res = await api.wallet.call(WalletApiOperation.GetVersion, {});
setEnabled(res.devMode);
}
getValue();
}, []);
return {
value: enabled,
toggle: handleOpen,
};
}

View File

@ -15,7 +15,11 @@
*/ */
import { CoreApiResponse, NotificationType } from "@gnu-taler/taler-util"; import { CoreApiResponse, NotificationType } from "@gnu-taler/taler-util";
import { WalletOperations } from "@gnu-taler/taler-wallet-core"; import {
WalletConfig,
WalletConfigParameter,
WalletOperations,
} from "@gnu-taler/taler-wallet-core";
import { BackgroundOperations } from "../wxApi.js"; import { BackgroundOperations } from "../wxApi.js";
import { import {
ExtensionOperations, ExtensionOperations,
@ -87,12 +91,35 @@ export interface WalletWebExVersion {
version: string; version: string;
} }
export interface Settings { type F = WalletConfig["features"];
type kf = keyof F;
type WebexWalletConfig = {
[P in keyof F as `wallet${Capitalize<P>}`]: F[P];
};
export interface Settings extends WebexWalletConfig {
injectTalerSupport: boolean; injectTalerSupport: boolean;
advanceMode: boolean;
backup: boolean;
langSelector: boolean;
showJsonOnError: boolean;
extendedAccountTypes: boolean;
//WORKAROUND
//Able to delete tx in dev mode
//FIXME: remove this when DD37 is implemented
deleteActiveTransactions: boolean;
} }
export const defaultSettings: Settings = { export const defaultSettings: Settings = {
injectTalerSupport: false, injectTalerSupport: false,
advanceMode: false,
backup: false,
langSelector: false,
deleteActiveTransactions: false,
showJsonOnError: false,
extendedAccountTypes: false,
walletAllowHttp: false,
walletBatchWithdrawal: false,
}; };
/** /**

View File

@ -45,7 +45,7 @@ function isFirefox(): boolean {
} }
function addPermissionsListener(callback: (p: Permissions) => void): void { function addPermissionsListener(callback: (p: Permissions) => void): void {
console.log("addPermissionListener is not supported for Firefox"); throw Error("addPermissionListener is not supported for Firefox");
} }
function getPermissionsApi(): CrossBrowserPermissionsApi { function getPermissionsApi(): CrossBrowserPermissionsApi {

View File

@ -20,6 +20,10 @@
* @author sebasjm * @author sebasjm
*/ */
import {
TranslationProvider,
useTranslationContext,
} from "@gnu-taler/web-util/lib/index.browser";
import { createHashHistory } from "history"; import { createHashHistory } from "history";
import { ComponentChildren, Fragment, h, VNode } from "preact"; import { ComponentChildren, Fragment, h, VNode } from "preact";
import { route, Route, Router } from "preact-router"; import { route, Route, Router } from "preact-router";
@ -27,29 +31,22 @@ import { useEffect, useState } from "preact/hooks";
import PendingTransactions from "../components/PendingTransactions.js"; import PendingTransactions from "../components/PendingTransactions.js";
import { PopupBox } from "../components/styled/index.js"; import { PopupBox } from "../components/styled/index.js";
import { AlertProvider } from "../context/alert.js"; import { AlertProvider } from "../context/alert.js";
import { DevContextProvider } from "../context/devContext.js";
import { IoCProviderForRuntime } from "../context/iocContext.js"; import { IoCProviderForRuntime } from "../context/iocContext.js";
import {
TranslationProvider,
useTranslationContext,
} from "@gnu-taler/web-util/lib/index.browser";
import { useTalerActionURL } from "../hooks/useTalerActionURL.js"; import { useTalerActionURL } from "../hooks/useTalerActionURL.js";
import { strings } from "../i18n/strings.js";
import { Pages, PopupNavBar, PopupNavBarOptions } from "../NavigationBar.js"; import { Pages, PopupNavBar, PopupNavBarOptions } from "../NavigationBar.js";
import { platform } from "../platform/foreground.js"; import { platform } from "../platform/foreground.js";
import { BackupPage } from "../wallet/BackupPage.js"; import { BackupPage } from "../wallet/BackupPage.js";
import { ProviderDetailPage } from "../wallet/ProviderDetailPage.js"; import { ProviderDetailPage } from "../wallet/ProviderDetailPage.js";
import { BalancePage } from "./BalancePage.js"; import { BalancePage } from "./BalancePage.js";
import { TalerActionFound } from "./TalerActionFound.js"; import { TalerActionFound } from "./TalerActionFound.js";
import { strings } from "../i18n/strings.js";
export function Application(): VNode { export function Application(): VNode {
return ( return (
<TranslationProvider source={strings}> <TranslationProvider source={strings}>
<DevContextProvider> <IoCProviderForRuntime>
<IoCProviderForRuntime> <ApplicationView />
<ApplicationView /> </IoCProviderForRuntime>
</IoCProviderForRuntime>
</DevContextProvider>
</TranslationProvider> </TranslationProvider>
); );
} }

View File

@ -27,7 +27,6 @@ import { setupPlatform } from "./platform/foreground.js";
import devAPI from "./platform/dev.js"; import devAPI from "./platform/dev.js";
import { Application } from "./popup/Application.js"; import { Application } from "./popup/Application.js";
console.log("Wallet setup for Dev API");
setupPlatform(devAPI); setupPlatform(devAPI);
function main(): void { function main(): void {

View File

@ -32,10 +32,8 @@ import { Application } from "./popup/Application.js";
//switching in runtime //switching in runtime
const isFirefox = typeof (window as any)["InstallTrigger"] !== "undefined"; const isFirefox = typeof (window as any)["InstallTrigger"] !== "undefined";
if (isFirefox) { if (isFirefox) {
console.log("Wallet setup for Firefox API");
setupPlatform(firefoxAPI); setupPlatform(firefoxAPI);
} else { } else {
console.log("Wallet setup for Chrome API");
setupPlatform(chromeAPI); setupPlatform(chromeAPI);
} }

View File

@ -41,7 +41,6 @@ import { strings } from "./i18n/strings.js";
// When doing tests we want the requestAnimationFrame to be as fast as possible. // When doing tests we want the requestAnimationFrame to be as fast as possible.
// without this option the RAF will timeout after 100ms making the tests slower // without this option the RAF will timeout after 100ms making the tests slower
options.requestAnimationFrame = (fn: () => void) => { options.requestAnimationFrame = (fn: () => void) => {
// console.log("RAF called")
return fn(); return fn();
}; };

View File

@ -20,15 +20,21 @@
* @author sebasjm * @author sebasjm
*/ */
import { TranslatedString } from "@gnu-taler/taler-util";
import { import {
TalerUriAction, TranslationProvider,
TranslatedString, useTranslationContext,
parseTalerUri, } from "@gnu-taler/web-util/lib/index.browser";
} from "@gnu-taler/taler-util";
import { createHashHistory } from "history"; import { createHashHistory } from "history";
import { ComponentChildren, Fragment, h, VNode } from "preact"; import { ComponentChildren, Fragment, VNode, h } from "preact";
import { route, Route, Router } from "preact-router"; import { Route, Router, route } from "preact-router";
import { useEffect } from "preact/hooks"; import { useEffect } from "preact/hooks";
import {
Pages,
WalletNavBar,
WalletNavBarOptions,
getPathnameForTalerURI,
} from "../NavigationBar.js";
import { AlertView, CurrentAlerts } from "../components/CurrentAlerts.js"; import { AlertView, CurrentAlerts } from "../components/CurrentAlerts.js";
import { LogoHeader } from "../components/LogoHeader.js"; import { LogoHeader } from "../components/LogoHeader.js";
import PendingTransactions from "../components/PendingTransactions.js"; import PendingTransactions from "../components/PendingTransactions.js";
@ -39,12 +45,7 @@ import {
WalletBox, WalletBox,
} from "../components/styled/index.js"; } from "../components/styled/index.js";
import { AlertProvider } from "../context/alert.js"; import { AlertProvider } from "../context/alert.js";
import { DevContextProvider } from "../context/devContext.js";
import { IoCProviderForRuntime } from "../context/iocContext.js"; import { IoCProviderForRuntime } from "../context/iocContext.js";
import {
TranslationProvider,
useTranslationContext,
} from "@gnu-taler/web-util/lib/index.browser";
import { DepositPage as DepositPageCTA } from "../cta/Deposit/index.js"; import { DepositPage as DepositPageCTA } from "../cta/Deposit/index.js";
import { InvoiceCreatePage } from "../cta/InvoiceCreate/index.js"; import { InvoiceCreatePage } from "../cta/InvoiceCreate/index.js";
import { InvoicePayPage } from "../cta/InvoicePay/index.js"; import { InvoicePayPage } from "../cta/InvoicePay/index.js";
@ -59,12 +60,7 @@ import {
WithdrawPageFromParams, WithdrawPageFromParams,
WithdrawPageFromURI, WithdrawPageFromURI,
} from "../cta/Withdraw/index.js"; } from "../cta/Withdraw/index.js";
import { import { strings } from "../i18n/strings.js";
Pages,
WalletNavBar,
WalletNavBarOptions,
getPathnameForTalerURI,
} from "../NavigationBar.js";
import { platform } from "../platform/foreground.js"; import { platform } from "../platform/foreground.js";
import CloseIcon from "../svg/close_24px.svg"; import CloseIcon from "../svg/close_24px.svg";
import { AddBackupProviderPage } from "./AddBackupProvider/index.js"; import { AddBackupProviderPage } from "./AddBackupProvider/index.js";
@ -80,7 +76,6 @@ import { QrReaderPage } from "./QrReader.js";
import { SettingsPage } from "./Settings.js"; import { SettingsPage } from "./Settings.js";
import { TransactionPage } from "./Transaction.js"; import { TransactionPage } from "./Transaction.js";
import { WelcomePage } from "./Welcome.js"; import { WelcomePage } from "./Welcome.js";
import { strings } from "../i18n/strings.js";
export function Application(): VNode { export function Application(): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
@ -91,433 +86,421 @@ export function Application(): VNode {
} }
return ( return (
<TranslationProvider source={strings}> <TranslationProvider source={strings}>
<DevContextProvider> <IoCProviderForRuntime>
<IoCProviderForRuntime> <Router history={hash_history}>
<Router history={hash_history}> <Route
<Route path={Pages.welcome}
path={Pages.welcome} component={() => (
component={() => ( <WalletTemplate>
<WalletTemplate> <WelcomePage />
<WelcomePage /> </WalletTemplate>
</WalletTemplate> )}
)} />
/>
<Route <Route
path={Pages.qr} path={Pages.qr}
component={() => ( component={() => (
<WalletTemplate goToTransaction={redirectToTxInfo}> <WalletTemplate goToTransaction={redirectToTxInfo}>
<QrReaderPage <QrReaderPage
onDetected={(talerActionUrl: string) => { onDetected={(talerActionUrl: string) => {
platform.openWalletURIFromPopup(talerActionUrl); platform.openWalletURIFromPopup(talerActionUrl);
}} }}
/> />
</WalletTemplate> </WalletTemplate>
)} )}
/> />
<Route <Route
path={Pages.settings} path={Pages.settings}
component={() => ( component={() => (
<WalletTemplate goToTransaction={redirectToTxInfo}> <WalletTemplate goToTransaction={redirectToTxInfo}>
<SettingsPage /> <SettingsPage />
</WalletTemplate> </WalletTemplate>
)} )}
/> />
<Route <Route
path={Pages.notifications} path={Pages.notifications}
component={() => ( component={() => (
<WalletTemplate> <WalletTemplate>
<NotificationsPage /> <NotificationsPage />
</WalletTemplate> </WalletTemplate>
)} )}
/> />
{/** {/**
* SETTINGS * SETTINGS
*/} */}
<Route <Route
path={Pages.settingsExchangeAdd.pattern} path={Pages.settingsExchangeAdd.pattern}
component={() => ( component={() => (
<WalletTemplate> <WalletTemplate>
<ExchangeAddPage onBack={() => redirectTo(Pages.balance)} /> <ExchangeAddPage onBack={() => redirectTo(Pages.balance)} />
</WalletTemplate> </WalletTemplate>
)} )}
/> />
<Route <Route
path={Pages.balanceHistory.pattern} path={Pages.balanceHistory.pattern}
component={({ currency }: { currency?: string }) => ( component={({ currency }: { currency?: string }) => (
<WalletTemplate <WalletTemplate path="balance" goToTransaction={redirectToTxInfo}>
path="balance" <HistoryPage
goToTransaction={redirectToTxInfo} currency={currency}
> goToWalletDeposit={(currency: string) =>
<HistoryPage redirectTo(Pages.sendCash({ amount: `${currency}:0` }))
currency={currency} }
goToWalletDeposit={(currency: string) => goToWalletManualWithdraw={(currency?: string) =>
redirectTo(Pages.sendCash({ amount: `${currency}:0` })) redirectTo(
} Pages.receiveCash({
goToWalletManualWithdraw={(currency?: string) => amount: !currency ? undefined : `${currency}:0`,
redirectTo( }),
Pages.receiveCash({ )
amount: !currency ? undefined : `${currency}:0`, }
}), />
) </WalletTemplate>
} )}
/> />
</WalletTemplate> <Route
)} path={Pages.sendCash.pattern}
/> component={({ amount }: { amount?: string }) => (
<Route <WalletTemplate path="balance">
path={Pages.sendCash.pattern} <DestinationSelectionPage
component={({ amount }: { amount?: string }) => ( type="send"
<WalletTemplate path="balance"> amount={amount}
<DestinationSelectionPage goToWalletBankDeposit={(amount: string) =>
type="send" redirectTo(Pages.balanceDeposit({ amount }))
amount={amount} }
goToWalletBankDeposit={(amount: string) => goToWalletWalletSend={(amount: string) =>
redirectTo(Pages.balanceDeposit({ amount })) redirectTo(Pages.ctaTransferCreate({ amount }))
} }
goToWalletWalletSend={(amount: string) => />
redirectTo(Pages.ctaTransferCreate({ amount })) </WalletTemplate>
} )}
/> />
</WalletTemplate> <Route
)} path={Pages.receiveCash.pattern}
/> component={({ amount }: { amount?: string }) => (
<Route <WalletTemplate path="balance">
path={Pages.receiveCash.pattern} <DestinationSelectionPage
component={({ amount }: { amount?: string }) => ( type="get"
<WalletTemplate path="balance"> amount={amount}
<DestinationSelectionPage goToWalletManualWithdraw={(amount?: string) =>
type="get" redirectTo(Pages.ctaWithdrawManual({ amount }))
amount={amount} }
goToWalletManualWithdraw={(amount?: string) => goToWalletWalletInvoice={(amount?: string) =>
redirectTo(Pages.ctaWithdrawManual({ amount })) redirectTo(Pages.ctaInvoiceCreate({ amount }))
} }
goToWalletWalletInvoice={(amount?: string) => />
redirectTo(Pages.ctaInvoiceCreate({ amount })) </WalletTemplate>
} )}
/> />
</WalletTemplate>
)}
/>
<Route <Route
path={Pages.balanceTransaction.pattern} path={Pages.balanceTransaction.pattern}
component={({ tid }: { tid: string }) => ( component={({ tid }: { tid: string }) => (
<WalletTemplate path="balance"> <WalletTemplate path="balance">
<TransactionPage <TransactionPage
tid={tid} tid={tid}
goToWalletHistory={(currency?: string) => goToWalletHistory={(currency?: string) =>
redirectTo(Pages.balanceHistory({ currency })) redirectTo(Pages.balanceHistory({ currency }))
} }
/> />
</WalletTemplate> </WalletTemplate>
)} )}
/> />
<Route <Route
path={Pages.balanceDeposit.pattern} path={Pages.balanceDeposit.pattern}
component={({ amount }: { amount: string }) => ( component={({ amount }: { amount: string }) => (
<WalletTemplate path="balance"> <WalletTemplate path="balance">
<DepositPage <DepositPage
amount={amount} amount={amount}
onCancel={(currency: string) => { onCancel={(currency: string) => {
redirectTo(Pages.balanceHistory({ currency })); redirectTo(Pages.balanceHistory({ currency }));
}} }}
onSuccess={(currency: string) => { onSuccess={(currency: string) => {
redirectTo(Pages.balanceHistory({ currency })); redirectTo(Pages.balanceHistory({ currency }));
}} }}
/> />
</WalletTemplate> </WalletTemplate>
)} )}
/> />
<Route <Route
path={Pages.backup} path={Pages.backup}
component={() => ( component={() => (
<WalletTemplate <WalletTemplate path="backup" goToTransaction={redirectToTxInfo}>
path="backup" <BackupPage
goToTransaction={redirectToTxInfo} onAddProvider={() => redirectTo(Pages.backupProviderAdd)}
> />
<BackupPage </WalletTemplate>
onAddProvider={() => redirectTo(Pages.backupProviderAdd)} )}
/> />
</WalletTemplate> <Route
)} path={Pages.backupProviderDetail.pattern}
/> component={({ pid }: { pid: string }) => (
<Route <WalletTemplate>
path={Pages.backupProviderDetail.pattern} <ProviderDetailPage
component={({ pid }: { pid: string }) => ( pid={pid}
<WalletTemplate> onPayProvider={(uri: string) =>
<ProviderDetailPage redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`)
pid={pid} }
onPayProvider={(uri: string) => onWithdraw={(amount: string) =>
redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`) redirectTo(Pages.receiveCash({ amount }))
} }
onWithdraw={(amount: string) => onBack={() => redirectTo(Pages.backup)}
redirectTo(Pages.receiveCash({ amount })) />
} </WalletTemplate>
onBack={() => redirectTo(Pages.backup)} )}
/> />
</WalletTemplate> <Route
)} path={Pages.backupProviderAdd}
/> component={() => (
<Route <WalletTemplate>
path={Pages.backupProviderAdd} <AddBackupProviderPage
component={() => ( onPaymentRequired={(uri: string) =>
<WalletTemplate> redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`)
<AddBackupProviderPage }
onPaymentRequired={(uri: string) => onComplete={(pid: string) =>
redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`) redirectTo(Pages.backupProviderDetail({ pid }))
} }
onComplete={(pid: string) => onBack={() => redirectTo(Pages.backup)}
redirectTo(Pages.backupProviderDetail({ pid })) />
} </WalletTemplate>
onBack={() => redirectTo(Pages.backup)} )}
/> />
</WalletTemplate>
)}
/>
{/** {/**
* DEV * DEV
*/} */}
<Route <Route
path={Pages.dev} path={Pages.dev}
component={() => ( component={() => (
<WalletTemplate path="dev" goToTransaction={redirectToTxInfo}> <WalletTemplate path="dev" goToTransaction={redirectToTxInfo}>
<DeveloperPage /> <DeveloperPage />
</WalletTemplate> </WalletTemplate>
)} )}
/> />
{/** {/**
* CALL TO ACTION * CALL TO ACTION
*/} */}
<Route <Route
path={Pages.defaultCta.pattern} path={Pages.defaultCta.pattern}
component={({ uri }: { uri: string }) => { component={({ uri }: { uri: string }) => {
const path = getPathnameForTalerURI(uri); const path = getPathnameForTalerURI(uri);
if (!path) { if (!path) {
return ( return (
<CallToActionTemplate title={i18n.str`Taler URI handler`}> <CallToActionTemplate title={i18n.str`Taler URI handler`}>
<AlertView <AlertView
alert={{ alert={{
type: "warning", type: "warning",
message: i18n.str`Could not found a handler for the Taler URI`, message: i18n.str`Could not found a handler for the Taler URI`,
description: i18n.str`The uri read in the path parameter is not valid: "${uri}"`, description: i18n.str`The uri read in the path parameter is not valid: "${uri}"`,
}} }}
/> />
</CallToActionTemplate> </CallToActionTemplate>
); );
} }
return <Redirect to={path} />; return <Redirect to={path} />;
}} }}
/> />
<Route <Route
path={Pages.ctaPay} path={Pages.ctaPay}
component={({ talerUri }: { talerUri: string }) => ( component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash payment`}> <CallToActionTemplate title={i18n.str`Digital cash payment`}>
<PaymentPage <PaymentPage
talerPayUri={decodeURIComponent(talerUri)} talerPayUri={decodeURIComponent(talerUri)}
goToWalletManualWithdraw={(amount?: string) => goToWalletManualWithdraw={(amount?: string) =>
redirectTo(Pages.receiveCash({ amount })) redirectTo(Pages.receiveCash({ amount }))
} }
cancel={() => redirectTo(Pages.balance)} cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) => onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid })) redirectTo(Pages.balanceTransaction({ tid }))
} }
/> />
</CallToActionTemplate> </CallToActionTemplate>
)} )}
/> />
<Route <Route
path={Pages.ctaPayTemplate} path={Pages.ctaPayTemplate}
component={({ talerUri }: { talerUri: string }) => ( component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash payment`}> <CallToActionTemplate title={i18n.str`Digital cash payment`}>
<PaymentTemplatePage <PaymentTemplatePage
talerTemplateUri={decodeURIComponent(talerUri)} talerTemplateUri={decodeURIComponent(talerUri)}
goToWalletManualWithdraw={(amount?: string) => goToWalletManualWithdraw={(amount?: string) =>
redirectTo(Pages.receiveCash({ amount })) redirectTo(Pages.receiveCash({ amount }))
} }
cancel={() => redirectTo(Pages.balance)} cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) => onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid })) redirectTo(Pages.balanceTransaction({ tid }))
} }
/> />
</CallToActionTemplate> </CallToActionTemplate>
)} )}
/> />
<Route <Route
path={Pages.ctaRefund} path={Pages.ctaRefund}
component={({ talerUri }: { talerUri: string }) => ( component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash refund`}> <CallToActionTemplate title={i18n.str`Digital cash refund`}>
<RefundPage <RefundPage
talerRefundUri={decodeURIComponent(talerUri)} talerRefundUri={decodeURIComponent(talerUri)}
cancel={() => redirectTo(Pages.balance)} cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) => onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid })) redirectTo(Pages.balanceTransaction({ tid }))
} }
/> />
</CallToActionTemplate> </CallToActionTemplate>
)} )}
/> />
<Route <Route
path={Pages.ctaTips} path={Pages.ctaTips}
component={({ talerUri }: { talerUri: string }) => ( component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash tip`}> <CallToActionTemplate title={i18n.str`Digital cash tip`}>
<TipPage <TipPage
talerTipUri={decodeURIComponent(talerUri)} talerTipUri={decodeURIComponent(talerUri)}
onCancel={() => redirectTo(Pages.balance)} onCancel={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) => onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid })) redirectTo(Pages.balanceTransaction({ tid }))
} }
/> />
</CallToActionTemplate> </CallToActionTemplate>
)} )}
/> />
<Route <Route
path={Pages.ctaWithdraw} path={Pages.ctaWithdraw}
component={({ talerUri }: { talerUri: string }) => ( component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash withdrawal`}> <CallToActionTemplate title={i18n.str`Digital cash withdrawal`}>
<WithdrawPageFromURI <WithdrawPageFromURI
talerWithdrawUri={decodeURIComponent(talerUri)} talerWithdrawUri={decodeURIComponent(talerUri)}
cancel={() => redirectTo(Pages.balance)} cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) => onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid })) redirectTo(Pages.balanceTransaction({ tid }))
} }
/> />
</CallToActionTemplate> </CallToActionTemplate>
)} )}
/> />
<Route <Route
path={Pages.ctaWithdrawManual.pattern} path={Pages.ctaWithdrawManual.pattern}
component={({ amount }: { amount: string }) => ( component={({ amount }: { amount: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash withdrawal`}> <CallToActionTemplate title={i18n.str`Digital cash withdrawal`}>
<WithdrawPageFromParams <WithdrawPageFromParams
amount={amount} amount={amount}
cancel={() => redirectTo(Pages.balance)} cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) => onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid })) redirectTo(Pages.balanceTransaction({ tid }))
} }
/> />
</CallToActionTemplate> </CallToActionTemplate>
)} )}
/> />
<Route <Route
path={Pages.ctaDeposit} path={Pages.ctaDeposit}
component={({ component={({
amount, amount,
talerUri, talerUri,
}: { }: {
amount: string; amount: string;
talerUri: string; talerUri: string;
}) => ( }) => (
<CallToActionTemplate title={i18n.str`Digital cash deposit`}> <CallToActionTemplate title={i18n.str`Digital cash deposit`}>
<DepositPageCTA <DepositPageCTA
amountStr={amount} amountStr={amount}
talerDepositUri={decodeURIComponent(talerUri)} talerDepositUri={decodeURIComponent(talerUri)}
cancel={() => redirectTo(Pages.balance)} cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) => onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid })) redirectTo(Pages.balanceTransaction({ tid }))
} }
/> />
</CallToActionTemplate> </CallToActionTemplate>
)} )}
/> />
<Route <Route
path={Pages.ctaInvoiceCreate.pattern} path={Pages.ctaInvoiceCreate.pattern}
component={({ amount }: { amount: string }) => ( component={({ amount }: { amount: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash invoice`}> <CallToActionTemplate title={i18n.str`Digital cash invoice`}>
<InvoiceCreatePage <InvoiceCreatePage
amount={amount} amount={amount}
onClose={() => redirectTo(Pages.balance)} onClose={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) => onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid })) redirectTo(Pages.balanceTransaction({ tid }))
} }
/> />
</CallToActionTemplate> </CallToActionTemplate>
)} )}
/> />
<Route <Route
path={Pages.ctaTransferCreate.pattern} path={Pages.ctaTransferCreate.pattern}
component={({ amount }: { amount: string }) => ( component={({ amount }: { amount: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash transfer`}> <CallToActionTemplate title={i18n.str`Digital cash transfer`}>
<TransferCreatePage <TransferCreatePage
amount={amount} amount={amount}
onClose={() => redirectTo(Pages.balance)} onClose={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) => onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid })) redirectTo(Pages.balanceTransaction({ tid }))
} }
/> />
</CallToActionTemplate> </CallToActionTemplate>
)} )}
/> />
<Route <Route
path={Pages.ctaInvoicePay} path={Pages.ctaInvoicePay}
component={({ talerUri }: { talerUri: string }) => ( component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash invoice`}> <CallToActionTemplate title={i18n.str`Digital cash invoice`}>
<InvoicePayPage <InvoicePayPage
talerPayPullUri={decodeURIComponent(talerUri)} talerPayPullUri={decodeURIComponent(talerUri)}
goToWalletManualWithdraw={(amount?: string) => goToWalletManualWithdraw={(amount?: string) =>
redirectTo(Pages.receiveCash({ amount })) redirectTo(Pages.receiveCash({ amount }))
} }
onClose={() => redirectTo(Pages.balance)} onClose={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) => onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid })) redirectTo(Pages.balanceTransaction({ tid }))
} }
/> />
</CallToActionTemplate> </CallToActionTemplate>
)} )}
/> />
<Route <Route
path={Pages.ctaTransferPickup} path={Pages.ctaTransferPickup}
component={({ talerUri }: { talerUri: string }) => ( component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash transfer`}> <CallToActionTemplate title={i18n.str`Digital cash transfer`}>
<TransferPickupPage <TransferPickupPage
talerPayPushUri={decodeURIComponent(talerUri)} talerPayPushUri={decodeURIComponent(talerUri)}
onClose={() => redirectTo(Pages.balance)} onClose={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) => onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid })) redirectTo(Pages.balanceTransaction({ tid }))
} }
/> />
</CallToActionTemplate> </CallToActionTemplate>
)} )}
/> />
<Route <Route
path={Pages.ctaRecovery} path={Pages.ctaRecovery}
component={({ component={({ talerRecoveryUri }: { talerRecoveryUri: string }) => (
talerRecoveryUri, <CallToActionTemplate title={i18n.str`Digital cash recovery`}>
}: { <RecoveryPage
talerRecoveryUri: string; talerRecoveryUri={decodeURIComponent(talerRecoveryUri)}
}) => ( onCancel={() => redirectTo(Pages.balance)}
<CallToActionTemplate title={i18n.str`Digital cash recovery`}> onSuccess={() => redirectTo(Pages.backup)}
<RecoveryPage />
talerRecoveryUri={decodeURIComponent(talerRecoveryUri)} </CallToActionTemplate>
onCancel={() => redirectTo(Pages.balance)} )}
onSuccess={() => redirectTo(Pages.backup)} />
/>
</CallToActionTemplate>
)}
/>
{/** {/**
* NOT FOUND * NOT FOUND
* all redirects should be at the end * all redirects should be at the end
*/} */}
<Route <Route
path={Pages.balance} path={Pages.balance}
component={() => <Redirect to={Pages.balanceHistory({})} />} component={() => <Redirect to={Pages.balanceHistory({})} />}
/> />
<Route <Route
default default
component={() => <Redirect to={Pages.balanceHistory({})} />} component={() => <Redirect to={Pages.balanceHistory({})} />}
/> />
</Router> </Router>
</IoCProviderForRuntime> </IoCProviderForRuntime>
</DevContextProvider>
</TranslationProvider> </TranslationProvider>
); );
} }

View File

@ -17,7 +17,7 @@
import { styled } from "@linaria/react"; import { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact"; import { Fragment, h, VNode } from "preact";
import { AmountField } from "../../components/AmountField.js"; import { AmountField } from "../../components/AmountField.js";
import { JustInDevMode } from "../../components/JustInDevMode.js"; import { EnabledBySettings } from "../../components/EnabledBySettings.js";
import { SelectList } from "../../components/SelectList.js"; import { SelectList } from "../../components/SelectList.js";
import { import {
Input, Input,
@ -300,11 +300,11 @@ export function ReadySendView({
required required
handler={amountHandler} handler={amountHandler}
/> />
<JustInDevMode> <EnabledBySettings name="advanceMode">
<Button onClick={sendAll.onClick}> <Button onClick={sendAll.onClick}>
<i18n.Translate>Send all</i18n.Translate> <i18n.Translate>Send all</i18n.Translate>
</Button> </Button>
</JustInDevMode> </EnabledBySettings>
</Grid> </Grid>
<Grid container spacing={1} columns={1}> <Grid container spacing={1} columns={1}>

View File

@ -23,10 +23,10 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { alertFromError, useAlertContext } from "../../context/alert.js"; import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js"; import { useBackendContext } from "../../context/backend.js";
import { useDevContext } from "../../context/devContext.js";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { AccountByType, Props, State } from "./index.js"; import { AccountByType, Props, State } from "./index.js";
import { useSettings } from "../../hooks/useSettings.js";
export function useComponentState({ export function useComponentState({
currency, currency,
@ -39,12 +39,11 @@ export function useComponentState({
const hook = useAsyncAsHook(() => const hook = useAsyncAsHook(() =>
api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }), api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }),
); );
const { devMode } = useDevContext();
const accountType: Record<string, string> = { const accountType: Record<string, string> = {
iban: "IBAN", iban: "IBAN",
// "x-taler-bank": "Taler Bank",
}; };
if (devMode) { const [settings] = useSettings();
if (settings.extendedAccountTypes) {
accountType["bitcoin"] = "Bitcoin"; accountType["bitcoin"] = "Bitcoin";
accountType["x-taler-bank"] = "Taler Bank"; accountType["x-taler-bank"] = "Taler Bank";
} }

View File

@ -47,27 +47,27 @@ const version = {
export const AllOff = tests.createExample(TestedComponent, { export const AllOff = tests.createExample(TestedComponent, {
deviceName: "this-is-the-device-name", deviceName: "this-is-the-device-name",
devModeToggle: { value: false, button: {} }, advanceToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} }, autoOpenToggle: { value: false, button: {} },
clipboardToggle: { value: false, button: {} }, langToggle: { value: false, button: {} },
setDeviceName: () => Promise.resolve(), setDeviceName: () => Promise.resolve(),
...version, ...version,
}); });
export const OneChecked = tests.createExample(TestedComponent, { export const OneChecked = tests.createExample(TestedComponent, {
deviceName: "this-is-the-device-name", deviceName: "this-is-the-device-name",
devModeToggle: { value: false, button: {} }, advanceToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} }, autoOpenToggle: { value: false, button: {} },
clipboardToggle: { value: false, button: {} }, langToggle: { value: false, button: {} },
setDeviceName: () => Promise.resolve(), setDeviceName: () => Promise.resolve(),
...version, ...version,
}); });
export const WithOneExchange = tests.createExample(TestedComponent, { export const WithOneExchange = tests.createExample(TestedComponent, {
deviceName: "this-is-the-device-name", deviceName: "this-is-the-device-name",
devModeToggle: { value: false, button: {} }, advanceToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} }, autoOpenToggle: { value: false, button: {} },
clipboardToggle: { value: false, button: {} }, langToggle: { value: false, button: {} },
setDeviceName: () => Promise.resolve(), setDeviceName: () => Promise.resolve(),
knownExchanges: [ knownExchanges: [
{ {
@ -89,9 +89,9 @@ export const WithExchangeInDifferentState = tests.createExample(
TestedComponent, TestedComponent,
{ {
deviceName: "this-is-the-device-name", deviceName: "this-is-the-device-name",
devModeToggle: { value: false, button: {} }, advanceToggle: { value: false, button: {} },
autoOpenToggle: { value: false, button: {} }, autoOpenToggle: { value: false, button: {} },
clipboardToggle: { value: false, button: {} }, langToggle: { value: false, button: {} },
setDeviceName: () => Promise.resolve(), setDeviceName: () => Promise.resolve(),
knownExchanges: [ knownExchanges: [
{ {

View File

@ -17,12 +17,15 @@
import { import {
ExchangeListItem, ExchangeListItem,
ExchangeTosStatus, ExchangeTosStatus,
TranslatedString,
WalletCoreVersion, WalletCoreVersion,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { Fragment, h, VNode } from "preact"; import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, VNode, h } from "preact";
import { Pages } from "../NavigationBar.js";
import { Checkbox } from "../components/Checkbox.js"; import { Checkbox } from "../components/Checkbox.js";
import { JustInDevMode } from "../components/JustInDevMode.js"; import { EnabledBySettings } from "../components/EnabledBySettings.js";
import { Part } from "../components/Part.js"; import { Part } from "../components/Part.js";
import { SelectList } from "../components/SelectList.js"; import { SelectList } from "../components/SelectList.js";
import { import {
@ -35,25 +38,20 @@ import {
} from "../components/styled/index.js"; } from "../components/styled/index.js";
import { useAlertContext } from "../context/alert.js"; import { useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useDevContext } from "../context/devContext.js";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { useSettings } from "../hooks/useSettings.js";
import { useBackupDeviceName } from "../hooks/useBackupDeviceName.js"; import { useBackupDeviceName } from "../hooks/useBackupDeviceName.js";
import { useClipboardPermissions } from "../hooks/useClipboardPermissions.js"; import { useClipboardPermissions } from "../hooks/useClipboardPermissions.js";
import { useSettings } from "../hooks/useSettings.js";
import { ToggleHandler } from "../mui/handlers.js"; import { ToggleHandler } from "../mui/handlers.js";
import { Pages } from "../NavigationBar.js";
import { platform } from "../platform/foreground.js"; import { platform } from "../platform/foreground.js";
import { Settings } from "../platform/api.js";
const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
export function SettingsPage(): VNode { export function SettingsPage(): VNode {
const [settings, updateSettings] = useSettings(); const [settings, updateSettings] = useSettings();
const clipboardToggle = useClipboardPermissions();
const { devMode, toggle } = useDevContext();
const { safely } = useAlertContext(); const { safely } = useAlertContext();
const { name, update } = useBackupDeviceName(); const { name, update } = useBackupDeviceName();
const { pushAlertOnError } = useAlertContext();
const webex = platform.getWalletWebExVersion(); const webex = platform.getWalletWebExVersion();
const api = useBackendContext(); const api = useBackendContext();
@ -76,16 +74,24 @@ export function SettingsPage(): VNode {
value: settings.injectTalerSupport, value: settings.injectTalerSupport,
button: { button: {
onClick: safely("update support injection", async () => { onClick: safely("update support injection", async () => {
console.log("click", settings.injectTalerSupport);
updateSettings("injectTalerSupport", !settings.injectTalerSupport); updateSettings("injectTalerSupport", !settings.injectTalerSupport);
}), }),
}, },
}} }}
clipboardToggle={clipboardToggle} advanceToggle={{
devModeToggle={{ value: settings.advanceMode,
value: devMode,
button: { button: {
onClick: pushAlertOnError(toggle), onClick: safely("update advance mode", async () => {
updateSettings("advanceMode", !settings.advanceMode);
}),
},
}}
langToggle={{
value: settings.langSelector,
button: {
onClick: safely("update lang selector", async () => {
updateSettings("langSelector", !settings.langSelector);
}),
}, },
}} }}
webexVersion={{ webexVersion={{
@ -101,8 +107,8 @@ export interface ViewProps {
deviceName: string; deviceName: string;
setDeviceName: (s: string) => Promise<void>; setDeviceName: (s: string) => Promise<void>;
autoOpenToggle: ToggleHandler; autoOpenToggle: ToggleHandler;
clipboardToggle: ToggleHandler; advanceToggle: ToggleHandler;
devModeToggle: ToggleHandler; langToggle: ToggleHandler;
knownExchanges: Array<ExchangeListItem>; knownExchanges: Array<ExchangeListItem>;
coreVersion: WalletCoreVersion | undefined; coreVersion: WalletCoreVersion | undefined;
webexVersion: { webexVersion: {
@ -114,8 +120,8 @@ export interface ViewProps {
export function SettingsView({ export function SettingsView({
knownExchanges, knownExchanges,
autoOpenToggle, autoOpenToggle,
clipboardToggle, advanceToggle,
devModeToggle, langToggle,
coreVersion, coreVersion,
webexVersion, webexVersion,
}: ViewProps): VNode { }: ViewProps): VNode {
@ -204,45 +210,19 @@ export function SettingsView({
</LinkPrimary> </LinkPrimary>
</div> </div>
<JustInDevMode>
<SubTitle>
<i18n.Translate>Display</i18n.Translate>
</SubTitle>
<Input>
<SelectList
label={<i18n.Translate>Current Language</i18n.Translate>}
list={supportedLang}
name="lang"
value={lang}
onChange={(v) => changeLanguage(v)}
/>
</Input>
</JustInDevMode>
<SubTitle>
<i18n.Translate>Version</i18n.Translate>
</SubTitle>
{coreVersion && (
<Part
title={i18n.str`Wallet Core`}
text={
<span>
{coreVersion.version}{" "}
<JustInDevMode>{coreVersion.hash}</JustInDevMode>
</span>
}
/>
)}
<Part <Part
title={i18n.str`Web Extension`} title={i18n.str`Web Extension`}
text={ text={
<span> <span>
{webexVersion.version}{" "} {webexVersion.version}{" "}
<JustInDevMode>{webexVersion.hash}</JustInDevMode> <EnabledBySettings name="advanceMode">
{webexVersion.hash}
</EnabledBySettings>
</span> </span>
} }
/> />
{coreVersion && ( {coreVersion && (
<JustInDevMode> <EnabledBySettings name="advanceMode">
<Part <Part
title={i18n.str`Exchange compatibility`} title={i18n.str`Exchange compatibility`}
text={<span>{coreVersion.exchange}</span>} text={<span>{coreVersion.exchange}</span>}
@ -255,18 +235,42 @@ export function SettingsView({
title={i18n.str`Bank compatibility`} title={i18n.str`Bank compatibility`}
text={<span>{coreVersion.bank}</span>} text={<span>{coreVersion.bank}</span>}
/> />
</JustInDevMode> </EnabledBySettings>
)} )}
<SubTitle> <SubTitle>
<i18n.Translate>Troubleshooting</i18n.Translate> <i18n.Translate>Advance mode</i18n.Translate>
</SubTitle> </SubTitle>
<Checkbox <Checkbox
label={i18n.str`Developer mode`} label={i18n.str`Enable advance mode`}
name="devMode" name="devMode"
description={i18n.str`More options and information useful for debugging`} description={i18n.str`Show more information and options in the UI`}
enabled={devModeToggle.value!} enabled={advanceToggle.value!}
onToggle={devModeToggle.button.onClick!} onToggle={advanceToggle.button.onClick!}
/> />
<EnabledBySettings name="advanceMode">
<AdvanceSettings />
</EnabledBySettings>
<Checkbox
label={i18n.str`Lang selector`}
name="langSelector"
description={i18n.str`Allows to manually change the language of the UI. Otherwise it will be automatically selected by your browser configuration.`}
enabled={langToggle.value!}
onToggle={langToggle.button.onClick!}
/>
<EnabledBySettings name="langSelector">
<SubTitle>
<i18n.Translate>Display</i18n.Translate>
</SubTitle>
<Input>
<SelectList
label={<i18n.Translate>Current Language</i18n.Translate>}
list={supportedLang}
name="lang"
value={lang}
onChange={(v) => changeLanguage(v)}
/>
</Input>
</EnabledBySettings>
<SubTitle> <SubTitle>
<i18n.Translate>Navigator</i18n.Translate> <i18n.Translate>Navigator</i18n.Translate>
</SubTitle> </SubTitle>
@ -283,6 +287,77 @@ export function SettingsView({
enabled={autoOpenToggle.value!} enabled={autoOpenToggle.value!}
onToggle={autoOpenToggle.button.onClick!} onToggle={autoOpenToggle.button.onClick!}
/> />
<SubTitle>
<i18n.Translate>Version</i18n.Translate>
</SubTitle>
{coreVersion && (
<Part
title={i18n.str`Wallet Core`}
text={
<span>
{coreVersion.version}{" "}
<EnabledBySettings name="advanceMode">
{coreVersion.hash}
</EnabledBySettings>
</span>
}
/>
)}
</section>
</Fragment>
);
}
type Info = { label: TranslatedString; description: TranslatedString };
type Options = {
[k in keyof Settings]?: Info;
};
function AdvanceSettings(): VNode {
const [settings, updateSettings] = useSettings();
const { i18n } = useTranslationContext();
const o: Options = {
backup: {
label: i18n.str`Show backup feature`,
description: i18n.str`Backup integration still in beta.`,
},
deleteActiveTransactions: {
label: i18n.str`Show delete active transaction`,
description: i18n.str`Deleting active transaction is not safe and you may loose your coins.`,
},
extendedAccountTypes: {
label: i18n.str`Show more account types on deposit`,
description: i18n.str`Extends the UI to more payment target types.`,
},
showJsonOnError: {
label: i18n.str`Show JSON on error`,
description: i18n.str`Print more information about the error. Useful for debugging.`,
},
walletAllowHttp: {
label: i18n.str`Allow HTTP connections`,
description: i18n.str`Using HTTP connection may be faster but unsafe (wallet restart required)`,
},
walletBatchWithdrawal: {
label: i18n.str`Allow batch withdrawals`,
description: i18n.str`Using the batch withdrawal API allows faster withdrawals (wallet restart required)`,
},
};
return (
<Fragment>
<section>
{Object.entries(o).map(([name, { label, description }]) => {
const settingsName = name as keyof Settings;
return (
<Checkbox
label={label}
name={name}
description={description}
enabled={settings[settingsName]}
onToggle={async () => {
updateSettings(settingsName, !settings[settingsName]);
}}
/>
);
})}
</section> </section>
</Fragment> </Fragment>
); );

View File

@ -34,6 +34,7 @@ import {
WithdrawalType, WithdrawalType,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { styled } from "@linaria/react"; import { styled } from "@linaria/react";
import { differenceInSeconds, isPast } from "date-fns"; import { differenceInSeconds, isPast } from "date-fns";
import { ComponentChildren, Fragment, h, VNode } from "preact"; import { ComponentChildren, Fragment, h, VNode } from "preact";
@ -43,7 +44,6 @@ import { Amount } from "../components/Amount.js";
import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js"; import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType.js";
import { CopyButton } from "../components/CopyButton.js"; import { CopyButton } from "../components/CopyButton.js";
import { AlertView, ErrorAlertView } from "../components/CurrentAlerts.js"; import { AlertView, ErrorAlertView } from "../components/CurrentAlerts.js";
import { JustInDevMode } from "../components/JustInDevMode.js";
import { Loading } from "../components/Loading.js"; import { Loading } from "../components/Loading.js";
import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js"; import { Kind, Part, PartCollapsible, PartPayto } from "../components/Part.js";
import { QR } from "../components/QR.js"; import { QR } from "../components/QR.js";
@ -61,12 +61,12 @@ import {
import { Time } from "../components/Time.js"; import { Time } from "../components/Time.js";
import { alertFromError, useAlertContext } from "../context/alert.js"; import { alertFromError, useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Button } from "../mui/Button.js"; import { Button } from "../mui/Button.js";
import { SafeHandler } from "../mui/handlers.js"; import { SafeHandler } from "../mui/handlers.js";
import { Pages } from "../NavigationBar.js"; import { Pages } from "../NavigationBar.js";
import { assertUnreachable } from "../utils/index.js"; import { assertUnreachable } from "../utils/index.js";
import { EnabledBySettings } from "../components/EnabledBySettings.js";
interface Props { interface Props {
tid: string; tid: string;
@ -359,18 +359,15 @@ function TransactionTemplate({
<i18n.Translate>Cancel</i18n.Translate> <i18n.Translate>Cancel</i18n.Translate>
</Button> </Button>
) : ( ) : (
//WORKAROUND <EnabledBySettings name="deleteActiveTransactions">
//Able to delete tx in dev mode
//FIXME: remove this when DD37 is implemented
<JustInDevMode>
<Button <Button
variant="contained" variant="contained"
color="error" color="error"
onClick={doCheckBeforeForget as SafeHandler<void>} onClick={doCheckBeforeForget as SafeHandler<void>}
> >
<i18n.Translate>Forget</i18n.Translate> <i18n.Translate>Delete</i18n.Translate>
</Button> </Button>
</JustInDevMode> </EnabledBySettings>
) )
) : ( ) : (
<Button <Button
@ -378,7 +375,7 @@ function TransactionTemplate({
color="error" color="error"
onClick={doCheckBeforeForget as SafeHandler<void>} onClick={doCheckBeforeForget as SafeHandler<void>}
> >
<i18n.Translate>Forget</i18n.Translate> <i18n.Translate>Delete</i18n.Translate>
</Button> </Button>
)} )}
</div> </div>

View File

@ -27,7 +27,6 @@ import { setupPlatform } from "./platform/foreground.js";
import devAPI from "./platform/dev.js"; import devAPI from "./platform/dev.js";
import { Application } from "./wallet/Application.js"; import { Application } from "./wallet/Application.js";
console.log("Wallet setup for Dev API");
setupPlatform(devAPI); setupPlatform(devAPI);
function main(): void { function main(): void {

View File

@ -33,10 +33,8 @@ const isFirefox = typeof (window as any)["InstallTrigger"] !== "undefined";
//FIXME: create different entry point for any platform instead of //FIXME: create different entry point for any platform instead of
//switching in runtime //switching in runtime
if (isFirefox) { if (isFirefox) {
console.log("Wallet setup for Firefox API");
setupPlatform(firefoxAPI); setupPlatform(firefoxAPI);
} else { } else {
console.log("Wallet setup for Chrome API");
setupPlatform(chromeAPI); setupPlatform(chromeAPI);
} }

View File

@ -156,7 +156,7 @@ class WalletApiClientImpl implements WalletCoreApiClient {
}; };
response = await platform.sendMessageToBackground(message); response = await platform.sendMessageToBackground(message);
} catch (e) { } catch (e) {
console.log("Error calling backend"); logger.error("Error calling backend", e);
throw new Error(`Error contacting backend: ${e}`); throw new Error(`Error contacting backend: ${e}`);
} }
if (response.type === "error") { if (response.type === "error") {

View File

@ -319,12 +319,19 @@ async function reinitWallet(): Promise<void> {
timer = new SetTimeoutTimerAPI(); timer = new SetTimeoutTimerAPI();
} }
const settings = await platform.getSettingsFromStorage();
logger.info("Setting up wallet"); logger.info("Setting up wallet");
const wallet = await Wallet.create( const wallet = await Wallet.create(
currentDatabase, currentDatabase,
httpLib, httpLib,
timer, timer,
cryptoWorker, cryptoWorker,
{
features: {
allowHttp: settings.walletAllowHttp,
batchWithdrawal: settings.walletBatchWithdrawal,
},
},
); );
try { try {
await wallet.handleCoreApiRequest("initWallet", "native-init", {}); await wallet.handleCoreApiRequest("initWallet", "native-init", {});