taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit c58349ebaa173881a4201e83e928325707758eb8
parent 97d4a6445ad86ffad842fd763e0ac8053b971e69
Author: Sebastian <sebasjm@gmail.com>
Date:   Mon,  4 Mar 2024 11:52:36 -0300

fix #8371

Diffstat:
Mpackages/taler-util/src/errors.ts | 4+++-
Mpackages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx | 1+
Mpackages/taler-wallet-webextension/src/components/TermsOfService/state.ts | 1+
Mpackages/taler-wallet-webextension/src/context/alert.ts | 48++++++++++++++++++++++++++++++++++++++++--------
Mpackages/taler-wallet-webextension/src/cta/Deposit/state.ts | 1+
Mpackages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts | 2++
Mpackages/taler-wallet-webextension/src/cta/InvoicePay/state.ts | 1+
Mpackages/taler-wallet-webextension/src/cta/Payment/state.ts | 1+
Mpackages/taler-wallet-webextension/src/cta/PaymentTemplate/state.ts | 1+
Mpackages/taler-wallet-webextension/src/cta/Refund/state.ts | 1+
Mpackages/taler-wallet-webextension/src/cta/TransferCreate/state.ts | 1+
Mpackages/taler-wallet-webextension/src/cta/TransferPickup/state.ts | 1+
Mpackages/taler-wallet-webextension/src/cta/Withdraw/state.ts | 3+++
Mpackages/taler-wallet-webextension/src/popup/BalancePage.tsx | 3++-
Mpackages/taler-wallet-webextension/src/wallet/BackupPage.tsx | 1+
Mpackages/taler-wallet-webextension/src/wallet/DepositPage/state.ts | 12+++++++-----
Mpackages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts | 55++++++++++++++++++++++++++++---------------------------
Mpackages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts | 1+
Mpackages/taler-wallet-webextension/src/wallet/History.tsx | 1+
Mpackages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts | 5++++-
Mpackages/taler-wallet-webextension/src/wallet/Notifications/state.ts | 1+
Mpackages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx | 1+
Mpackages/taler-wallet-webextension/src/wallet/Settings.tsx | 16+++-------------
Mpackages/taler-wallet-webextension/src/wallet/Transaction.tsx | 2++
Mpackages/taler-wallet-webextension/src/wxApi.ts | 25+++++++++++++++++--------
Mpackages/taler-wallet-webextension/src/wxBackend.ts | 9+++++++--
26 files changed, 132 insertions(+), 66 deletions(-)

diff --git a/packages/taler-util/src/errors.ts b/packages/taler-util/src/errors.ts @@ -114,7 +114,9 @@ export interface DetailsMap { numErrors: number; errorsPerCoin: Record<number, TalerErrorDetail>; }; - [TalerErrorCode.WALLET_CORE_NOT_AVAILABLE]: empty; + [TalerErrorCode.WALLET_CORE_NOT_AVAILABLE]: { + lastError?: TalerErrorDetail; + }; [TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR]: { httpStatusCode: number; }; diff --git a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx @@ -167,6 +167,7 @@ export function ErrorView({ <Modal title="Full detail" onClose={hideHandler}> <ErrorAlertView error={alertFromError( + i18n, i18n.str`Could not load purchase proposal details`, error, { proposalId }, diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts @@ -74,6 +74,7 @@ export function useComponentState({ showEvenIfaccepted, exchangeUrl, readOnly, c return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load the status of the term of service`, terms, ), diff --git a/packages/taler-wallet-webextension/src/context/alert.ts b/packages/taler-wallet-webextension/src/context/alert.ts @@ -19,13 +19,14 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { TalerErrorDetail, TranslatedString } from "@gnu-taler/taler-util"; +import { TalerError, TalerErrorCode, TalerErrorDetail, TranslatedString } from "@gnu-taler/taler-util"; import { ComponentChildren, createContext, h, VNode } from "preact"; import { useContext, useState } from "preact/hooks"; import { HookError } from "../hooks/useAsyncAsHook.js"; import { SafeHandler, withSafe } from "../mui/handlers.js"; import { BackgroundError } from "../wxApi.js"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { InternationalizationAPI, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { platform } from "../platform/foreground.js"; export type AlertType = "info" | "warning" | "error" | "success"; @@ -102,24 +103,24 @@ export const AlertProvider = ({ children }: Props): VNode => { setAlerts((ns: AlertWithDate[]) => ns.filter((n) => n !== alert)); }; + const { i18n } = useTranslationContext(); + function pushAlertOnError<T>( handler: (p: T) => Promise<void>, ): SafeHandler<T> { return withSafe(handler, (e) => { - const a = alertFromError(e.message as TranslatedString, e); + const a = alertFromError(i18n, e.message as TranslatedString, e); pushAlert(a); }); } - const { i18n } = useTranslationContext(); - function safely<T>( name: string, handler: (p: T) => Promise<void>, ): SafeHandler<T> { const message = i18n.str`Error was thrown trying to: "${name}"`; return withSafe(handler, (e) => { - const a = alertFromError(message, e); + const a = alertFromError(i18n, message, e); pushAlert(a); }); } @@ -133,24 +134,28 @@ export const AlertProvider = ({ children }: Props): VNode => { export const useAlertContext = (): Type => useContext(Context); export function alertFromError( + i18n: InternationalizationAPI, message: TranslatedString, error: HookError, ...context: any[] ): ErrorAlert; export function alertFromError( + i18n: InternationalizationAPI, message: TranslatedString, error: Error, ...context: any[] ): ErrorAlert; export function alertFromError( + i18n: InternationalizationAPI, message: TranslatedString, error: TalerErrorDetail, ...context: any[] ): ErrorAlert; export function alertFromError( + i18n: InternationalizationAPI, message: TranslatedString, error: HookError | TalerErrorDetail | Error, ...context: any[] @@ -170,14 +175,23 @@ export function alertFromError( //HookError description = error.message as TranslatedString; if (error.type === "taler") { + const msg = isWalletNotAvailable(i18n,error.details) + if (msg) { + description = msg + } cause = { details: error.details, }; } } else { if (error instanceof BackgroundError) { - description = (error.errorDetail.hint ?? - `Error code: ${error.errorDetail.code}`) as TranslatedString; + const msg = isWalletNotAvailable(i18n,error.errorDetail) + if (msg) { + description = msg + } else { + description = (error.errorDetail.hint ?? + `Error code: ${error.errorDetail.code}`) as TranslatedString; + } cause = { details: error.errorDetail, stack: error.stack, @@ -202,3 +216,21 @@ export function alertFromError( context, }; } + +function isWalletNotAvailable(i18n: InternationalizationAPI, detail: TalerErrorDetail): TranslatedString | undefined { + if (detail.code === TalerErrorCode.WALLET_CORE_NOT_AVAILABLE + && detail.lastError) { + const le = detail.lastError as TalerErrorDetail + if (le.code === TalerErrorCode.WALLET_DB_UNAVAILABLE) { + if (platform.isFirefox()) { + return i18n.str`Could not open the wallet database. Firefox is known to run into this problem under "permanent private mode".` + } else { + return i18n.str`Could not open the wallet database.` + } + } else { + return (detail.hint ?? `Error code: ${detail.code}`) as TranslatedString; + } + + } + return undefined +} diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/state.ts b/packages/taler-wallet-webextension/src/cta/Deposit/state.ts @@ -48,6 +48,7 @@ export function useComponentState({ return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load the status of deposit`, info, ), diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts @@ -50,6 +50,7 @@ export function useComponentState({ return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load the list of exchanges`, hook, ), @@ -103,6 +104,7 @@ export function useComponentState({ return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load the invoice status`, hook, ), diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts @@ -64,6 +64,7 @@ export function useComponentState({ return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load the transfer payment status`, hook, ), diff --git a/packages/taler-wallet-webextension/src/cta/Payment/state.ts b/packages/taler-wallet-webextension/src/cta/Payment/state.ts @@ -84,6 +84,7 @@ export function useComponentState({ return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load the payment and balance status`, hook, ), diff --git a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/state.ts b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/state.ts @@ -79,6 +79,7 @@ export function useComponentState({ return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load the status of the order template`, hook, ), diff --git a/packages/taler-wallet-webextension/src/cta/Refund/state.ts b/packages/taler-wallet-webextension/src/cta/Refund/state.ts @@ -72,6 +72,7 @@ export function useComponentState({ return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load the refund status`, info, ), diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts @@ -59,6 +59,7 @@ export function useComponentState({ return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load the max amount to transfer`, hook, ), diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts b/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts @@ -50,6 +50,7 @@ export function useComponentState({ return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load the invoice payment status`, hook, ), diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts @@ -79,6 +79,7 @@ export function useComponentStateFromParams({ return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load the list of exchanges`, uriInfoHook, ), @@ -255,6 +256,7 @@ export function useComponentStateFromURI({ return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load info from URI`, uriInfoHook, ), @@ -407,6 +409,7 @@ function exchangeSelectionState( return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load the withdrawal details`, amountHook, ), diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx @@ -105,7 +105,8 @@ function useComponentState({ if (state.hasError) { return { status: "error", - error: alertFromError(i18n.str`Could not load the balance`, state), + error: alertFromError( i18n, + i18n.str`Could not load the balance`, state), }; } if (addingAction) { diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx @@ -121,6 +121,7 @@ export function BackupPage({ onAddProvider }: Props): VNode { return ( <ErrorAlertView error={alertFromError( + i18n, i18n.str`Could not load backup providers`, status, )} diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts @@ -60,8 +60,8 @@ export function useComponentState({ parsed !== undefined ? parsed : currency !== undefined - ? Amounts.zeroOfCurrency(currency) - : undefined; + ? Amounts.zeroOfCurrency(currency) + : undefined; // const [accountIdx, setAccountIdx] = useState<number>(0); const [selectedAccount, setSelectedAccount] = useState<PaytoUri>(); @@ -83,7 +83,8 @@ export function useComponentState({ if (hook.hasError) { return { status: "error", - error: alertFromError(i18n.str`Could not load balance information`, hook), + error: alertFromError(i18n, + i18n.str`Could not load balance information`, hook), }; } const { accounts, balances } = hook.response; @@ -169,6 +170,7 @@ export function useComponentState({ return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load fee for amount ${amountStr}`, hook, ), @@ -193,8 +195,8 @@ export function useComponentState({ const amountError = !isDirty ? undefined : Amounts.cmp(balance, amount) === -1 - ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}` - : undefined; + ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}` + : undefined; const unableToDeposit = Amounts.isZero(totalToDeposit) || //deposit may be zero because of fee diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts @@ -52,22 +52,22 @@ export function useComponentState(props: Props): RecursiveState<State> { const previous: Contact[] = true ? [] : [ - { - name: "International Bank", - icon_type: "bank", - description: "account ending with 3454", - }, - { - name: "Max", - icon_type: "bank", - description: "account ending with 3454", - }, - { - name: "Alex", - icon_type: "bank", - description: "account ending with 3454", - }, - ]; + { + name: "International Bank", + icon_type: "bank", + description: "account ending with 3454", + }, + { + name: "Max", + icon_type: "bank", + description: "account ending with 3454", + }, + { + name: "Alex", + icon_type: "bank", + description: "account ending with 3454", + }, + ]; if (!amount) { return () => { @@ -87,7 +87,8 @@ export function useComponentState(props: Props): RecursiveState<State> { if (hook.hasError) { return { status: "error", - error: alertFromError(i18n.str`Could not load exchanges`, hook), + error: alertFromError(i18n, + i18n.str`Could not load exchanges`, hook), }; } const currencies: Record<string, string> = {}; @@ -127,8 +128,8 @@ export function useComponentState(props: Props): RecursiveState<State> { onClick: invalid ? undefined : pushAlertOnError(async () => { - props.goToWalletBankDeposit(currencyAndAmount); - }), + props.goToWalletBankDeposit(currencyAndAmount); + }), }, selectMax: { onClick: pushAlertOnError(async () => { @@ -145,8 +146,8 @@ export function useComponentState(props: Props): RecursiveState<State> { onClick: invalid ? undefined : pushAlertOnError(async () => { - props.goToWalletWalletSend(currencyAndAmount); - }), + props.goToWalletWalletSend(currencyAndAmount); + }), }, amountHandler: { onInput: pushAlertOnError(async (s) => setAmount(s)), @@ -168,22 +169,22 @@ export function useComponentState(props: Props): RecursiveState<State> { onClick: invalid ? undefined : pushAlertOnError(async () => { - props.goToWalletManualWithdraw(currencyAndAmount); - }), + props.goToWalletManualWithdraw(currencyAndAmount); + }), }, goToBank: { onClick: invalid ? undefined : pushAlertOnError(async () => { - props.goToWalletManualWithdraw(currencyAndAmount); - }), + props.goToWalletManualWithdraw(currencyAndAmount); + }), }, goToWallet: { onClick: invalid ? undefined : pushAlertOnError(async () => { - props.goToWalletWalletInvoice(currencyAndAmount); - }), + props.goToWalletWalletInvoice(currencyAndAmount); + }), }, amountHandler: { onInput: pushAlertOnError(async (s) => setAmount(s)), diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts @@ -90,6 +90,7 @@ export function useComponentState({ return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load exchange details info`, hook, ), diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx @@ -87,6 +87,7 @@ export function HistoryPage({ return ( <ErrorAlertView error={alertFromError( + i18n, i18n.str`Could not load the list of transactions`, state, )} diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts b/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts @@ -61,7 +61,10 @@ export function useComponentState({ if (hook.hasError) { return { status: "error", - error: alertFromError(i18n.str`Could not load known bank accounts`, hook), + error: alertFromError( + i18n, + i18n.str`Could not load known bank accounts`, + hook), }; } diff --git a/packages/taler-wallet-webextension/src/wallet/Notifications/state.ts b/packages/taler-wallet-webextension/src/wallet/Notifications/state.ts @@ -42,6 +42,7 @@ export function useComponentState(p: Props): State { return { status: "error", error: alertFromError( + i18n, i18n.str`Could not load user attention request`, hook, ), diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx @@ -68,6 +68,7 @@ export function ProviderDetailPage({ return ( <ErrorAlertView error={alertFromError( + i18n, i18n.str`There was an error loading the provider detail for &quot;${providerURL}&quot;`, state, )} diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx @@ -15,31 +15,21 @@ */ import { - AbsoluteTime, - ExchangeDetailedResponse, - ExchangeListItem, - ExchangeTosStatus, LibtoolVersion, - ScopeType, TranslatedString, - WalletCoreVersion, + WalletCoreVersion } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { useApiContext, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; -import { Pages } from "../NavigationBar.js"; import { Checkbox } from "../components/Checkbox.js"; import { EnabledBySettings } from "../components/EnabledBySettings.js"; import { Part } from "../components/Part.js"; import { SelectList } from "../components/SelectList.js"; import { - DestructiveText, Input, - LinkPrimary, SubTitle, - SuccessText, - WarningBox, - WarningText, + WarningBox } from "../components/styled/index.js"; import { useAlertContext } from "../context/alert.js"; import { useBackendContext } from "../context/backend.js"; diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -107,6 +107,7 @@ export function TransactionPage({ tid, goToWalletHistory }: Props): VNode { return ( <ErrorAlertView error={alertFromError( + i18n, i18n.str`Could not load transaction information`, state, )} @@ -234,6 +235,7 @@ function TransactionTemplate({ TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED ? ( <ErrorAlertView error={alertFromError( + i18n, i18n.str`There was an error trying to complete the transaction`, transaction.error, )} diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts @@ -97,11 +97,13 @@ export interface BackgroundApiClient { } export class BackgroundError<T = any> extends Error { - public errorDetail: TalerErrorDetail & T; + public readonly errorDetail: TalerErrorDetail & T; + public readonly cause: Error; - constructor(title: string, e: TalerErrorDetail & T) { + constructor(title: string, e: TalerErrorDetail & T, cause: Error) { super(title); this.errorDetail = e; + this.cause = cause; } hasErrorCode<C extends keyof DetailsMap>( @@ -134,7 +136,7 @@ class BackgroundApiClientImpl implements BackgroundApiClient { throw new BackgroundError(operation, { code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, when: AbsoluteTime.now(), - }); + }, error); } throw error; } @@ -142,6 +144,7 @@ class BackgroundApiClientImpl implements BackgroundApiClient { throw new BackgroundError( `Background operation "${operation}" failed`, response.error, + TalerError.fromUncheckedDetail(response.error), ); } logger.trace("response", response); @@ -165,14 +168,20 @@ class WalletApiClientImpl implements WalletCoreApiClient { payload, }; response = await platform.sendMessageToBackground(message); - } catch (e) { - logger.error("Error calling backend", e); - throw new Error(`Error contacting backend: ${e}`); + } catch (error) { + if (error instanceof Error) { + throw new BackgroundError(operation, { + code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, + when: AbsoluteTime.now(), + }, error); + } + throw error; } if (response.type === "error") { throw new BackgroundError( `Wallet operation "${operation}" failed`, response.error, + TalerError.fromUncheckedDetail(response.error) ); } logger.trace("got response", response); @@ -182,7 +191,7 @@ class WalletApiClientImpl implements WalletCoreApiClient { function onUpdateNotification( messageTypes: Array<NotificationType>, - doCallback: undefined | ((n:WalletNotification) => void), + doCallback: undefined | ((n: WalletNotification) => void), ): () => void { //if no callback, then ignore if (!doCallback) @@ -207,7 +216,7 @@ export type WxApiType = { }; }; -function trigger(w:ExtensionNotification) { +function trigger(w: ExtensionNotification) { platform.triggerWalletEvent({ type: "web-extension", notification: w, diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -29,7 +29,9 @@ import { Logger, OpenedPromise, SetTimeoutTimerAPI, + TalerError, TalerErrorCode, + TalerErrorDetail, getErrorDetailFromException, makeErrorDetail, openPromise, @@ -224,14 +226,17 @@ async function dispatch< case "wallet": { const w = currentWallet; if (!w) { + const lastError: TalerErrorDetail = walletInit.lastError instanceof TalerError ? + walletInit.lastError.errorDetail : undefined + return { type: "error", id: req.id, operation: req.operation, error: makeErrorDetail( TalerErrorCode.WALLET_CORE_NOT_AVAILABLE, - {}, - `wallet core not available, last error: ${walletInit.lastError}`, + { lastError }, + `wallet core not available${!lastError ? "": `,last error: ${lastError.hint}`}`, ), }; }