taler-typescript-core

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

commit a0c06b5a89bd61a30f24055681c16e64b11ea225
parent 8a0eb7fefc4ee30df6ab15d3609058e4ee524979
Author: Sebastian <sebasjm@gmail.com>
Date:   Mon,  3 Nov 2025 11:25:53 -0300

better error msg

Diffstat:
Mpackages/bank-ui/src/hooks/preferences.ts | 8++------
Mpackages/bank-ui/src/pages/BankFrame.tsx | 10+++++-----
Mpackages/bank-ui/src/pages/ConversionRateClassDetails.tsx | 19++++++++-----------
Mpackages/bank-ui/src/pages/LoginForm.tsx | 8++------
Mpackages/bank-ui/src/pages/NewConversionRateClass.tsx | 2+-
Mpackages/bank-ui/src/pages/SolveMFA.tsx | 4++--
Mpackages/bank-ui/src/pages/account/ShowAccountDetails.tsx | 2+-
Mpackages/bank-ui/src/pages/regional/ConversionConfig.tsx | 18++++++++----------
Mpackages/web-util/src/hooks/useNotifications.ts | 78+++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
9 files changed, 84 insertions(+), 65 deletions(-)

diff --git a/packages/bank-ui/src/hooks/preferences.ts b/packages/bank-ui/src/hooks/preferences.ts @@ -30,7 +30,6 @@ import { codecOptionalDefault } from "@gnu-taler/taler-util"; const TALER_SCREEN_ID = 102; - interface Preferences { showWithdrawalSuccess: boolean; hideDemo: boolean; @@ -40,6 +39,7 @@ interface Preferences { export const codecForPreferences = (): Codec<Preferences> => buildCodecForObject<Preferences>() + .allowExtra() .property("showWithdrawalSuccess", codecForBoolean()) .property("hideDemo", codecOptionalDefault(codecForBoolean(), false)) .property("showInstallWallet", codecForBoolean()) @@ -89,11 +89,7 @@ export function getAllBooleanPreferences( "fastWithdrawalForm", ]; } - return [ - "showInstallWallet", - "showWithdrawalSuccess", - "fastWithdrawalForm", - ]; + return ["showInstallWallet", "showWithdrawalSuccess", "fastWithdrawalForm"]; } export function getLabelForPreferences( diff --git a/packages/bank-ui/src/pages/BankFrame.tsx b/packages/bank-ui/src/pages/BankFrame.tsx @@ -28,6 +28,7 @@ import { Loading, RouteDefinition, ToastBanner, + logBugForDevelopers, notifyError, notifyException, useBankCoreApiContext, @@ -67,7 +68,7 @@ export function BankFrame({ const { i18n } = useTranslationContext(); const session = useSessionState(); const settings = useSettingsContext(); - const {showDebugInfo} = useCommonPreference(); + const { showDebugInfo } = useCommonPreference(); const [preferences, updatePreferences] = usePreferences(); const [, , resetBankState] = useBankState(); const d = useBankCoreApiContext(); @@ -77,11 +78,10 @@ export function BankFrame({ useEffect(() => { if (error) { + logBugForDevelopers(error); if (error instanceof Error) { - console.log("Internal error, please report", error); - notifyException(i18n.str`Internal error, please report.`, error); + notifyException(i18n.str`Internal error, please report. There should be more information in the console.`, error); } else { - console.log("Internal error, please report", error); notifyError( i18n.str`Internal error, please report.`, String(error) as TranslatedString, @@ -251,7 +251,7 @@ function AppActivity(): VNode { const d = useBankCoreApiContext(); const onBackendActivity = !d ? undefined : d.onActivity; const cancelRequest = !d ? undefined : d.cancelRequest; - const {showDebugInfo} = useCommonPreference(); + const { showDebugInfo } = useCommonPreference(); useEffect(() => { // console.log("ASDASDS", onBackendActivity) if (!showDebugInfo) return; diff --git a/packages/bank-ui/src/pages/ConversionRateClassDetails.tsx b/packages/bank-ui/src/pages/ConversionRateClassDetails.tsx @@ -134,10 +134,7 @@ function Form({ const { i18n } = useTranslationContext(); const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials; - const { - lib: { bank }, - config, - } = useBankCoreApiContext(); + const { lib, config } = useBankCoreApiContext(); const [notification, safeFunctionHandler] = useLocalNotificationBetter(); const [section, setSection] = useState< "detail" | "cashout" | "cashin" | "users" | "test" | "delete" @@ -168,7 +165,7 @@ function Form({ ); const deleteClass = safeFunctionHandler( - (token: AccessToken) => bank.deleteConversionRateClass(token, classId), + (token: AccessToken) => lib.bank.deleteConversionRateClass(token, classId), !creds || section !== "delete" || detailsResult.num_users > 0 ? undefined : [creds.token], @@ -206,7 +203,7 @@ function Form({ }; const updateClass = safeFunctionHandler( - bank.updateConversionRateClass, + lib.bank.updateConversionRateClass.bind(lib.bank), !creds || !input ? undefined : [creds.token, classId, input], ); updateClass.onSuccess = () => { @@ -215,15 +212,15 @@ function Form({ updateClass.onFail = (fail) => { switch (fail.case) { case HttpStatusCode.Unauthorized: - return i18n.str``; + return i18n.str`Unauthorized`; case HttpStatusCode.Forbidden: - return i18n.str``; + return i18n.str`Forbidden`; case HttpStatusCode.NotFound: - return i18n.str``; + return i18n.str`Not Found`; case HttpStatusCode.NotImplemented: - return i18n.str``; + return i18n.str`Not implemented`; case TalerErrorCode.BANK_NAME_REUSE: - return i18n.str``; + return i18n.str`The name of the conversion is already used.`; } }; diff --git a/packages/bank-ui/src/pages/LoginForm.tsx b/packages/bank-ui/src/pages/LoginForm.tsx @@ -58,17 +58,13 @@ export function LoginForm({ currentUser, fixedUser, routeRegister, - // }: { fixedUser?: boolean; currentUser?: string; routeRegister?: RouteDefinition; - // }): VNode { const session = useSessionState(); - const sessionState = - session.state.status === "loggedIn" ? session.state : undefined; const sessionUser = session.state.status !== "loggedOut" ? session.state.username : undefined; const [username, setUsername] = useState<string | undefined>( @@ -193,7 +189,7 @@ export function LoginForm({ autocomplete="username" title={i18n.str`Username of the account`} required - onInput={(e): void => { + onChange={(e): void => { setUsername(e.currentTarget.value); }} /> @@ -225,7 +221,7 @@ export function LoginForm({ placeholder="Password" title={i18n.str`Password of the account`} required - onInput={(e): void => { + onChange={(e): void => { setPassword(e.currentTarget.value); }} /> diff --git a/packages/bank-ui/src/pages/NewConversionRateClass.tsx b/packages/bank-ui/src/pages/NewConversionRateClass.tsx @@ -60,7 +60,7 @@ export function NewConversionRateClass({ case HttpStatusCode.NotImplemented: return i18n.str`Not implemented`; case TalerErrorCode.BANK_NAME_REUSE: - return i18n.str`The name is already used`; + return i18n.str`The name of the conversion is already used.`; } }; diff --git a/packages/bank-ui/src/pages/SolveMFA.tsx b/packages/bank-ui/src/pages/SolveMFA.tsx @@ -300,7 +300,7 @@ export function SolveMFAChallenges({ } }; - const doComplete = onCompleted.withArgs(solved); + const complete = onCompleted.withArgs(solved); const selectChallenge = safeFunctionHandler(async (ch: Challenge) => { setSelected({ @@ -457,7 +457,7 @@ export function SolveMFAChallenges({ type="submit" name="send again" class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" - onClick={doComplete} + onClick={complete} > <i18n.Translate>Complete</i18n.Translate> </ButtonBetter> diff --git a/packages/bank-ui/src/pages/account/ShowAccountDetails.tsx b/packages/bank-ui/src/pages/account/ShowAccountDetails.tsx @@ -155,7 +155,7 @@ export function ShowAccountDetails({ }; const repeatUpdate = update.lambda((ids: string[]) => { - return [update.args![0], update.args![1], []]; + return [update.args![0], update.args![1], ids]; }); const url = bank.getRevenueAPI(account); diff --git a/packages/bank-ui/src/pages/regional/ConversionConfig.tsx b/packages/bank-ui/src/pages/regional/ConversionConfig.tsx @@ -583,16 +583,14 @@ function useComponentState({ <i18n.Translate>Cancel</i18n.Translate> </a> {section == "cashin" || section == "cashout" ? ( - <Fragment> - <ButtonBetter - type="submit" - name="update conversion" - class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" - onClick={update} - > - <i18n.Translate>Update</i18n.Translate> - </ButtonBetter> - </Fragment> + <ButtonBetter + type="submit" + name="update conversion" + class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + onClick={update} + > + <i18n.Translate>Update</i18n.Translate> + </ButtonBetter> ) : ( <div /> )} diff --git a/packages/web-util/src/hooks/useNotifications.ts b/packages/web-util/src/hooks/useNotifications.ts @@ -239,28 +239,27 @@ export function useLocalNotificationBetter(): [ const thiz: SafeHandlerTemplate<Args, R> = { args: a, withArgs: (...newArgs) => { - const r = buildSafeHandler(newArgs, doAction) - r.onSuccess = thiz.onSuccess - r.onFail = thiz.onFail - return r + const r = buildSafeHandler(newArgs, doAction); + r.onSuccess = thiz.onSuccess; + r.onFail = thiz.onFail; + return r; }, lambda: (converter, init) => { type D = Parameters<typeof converter>; type SH = SafeHandlerTemplate<D, R>; const r = buildSafeHandler( - // @ts-expect-error - init ? converter(init) : undefined, + init ? converter(...init) : undefined, doAction, ); // @ts-expect-error r.withArgs = (...args: D) => { const d = converter(...args); - const e = r.withArgs(...(d as any)); + const e = thiz.withArgs(...d); return e; }; - r.onSuccess = thiz.onSuccess - r.onFail = thiz.onFail + r.onSuccess = thiz.onSuccess; + r.onFail = thiz.onFail; return r as any as SH; }, call: async (): Promise<void> => { @@ -278,7 +277,7 @@ export function useLocalNotificationBetter(): [ case "fail": { const error = thiz.onFail(resp as any, ...thiz.args); if (error) { - save(failWithTitle(resp, error)); + save(failWithTitle(i18n, resp, error)); } return; } @@ -288,8 +287,8 @@ export function useLocalNotificationBetter(): [ } } catch (error: unknown) { // This functions should not throw, this is a problem. - console.error(`Error: `, error); - onUnexpected(i18n, save)(error); + logBugForDevelopers(error); + onUnexpected(i18n, save)(error, thiz.args); return; } }, @@ -305,11 +304,18 @@ export function useLocalNotificationBetter(): [ return [notif, safeFunctionHandler]; } +export function logBugForDevelopers(error: unknown) { + console.error( + `Internal error, this is mostly a bug in the application. Please report: `, + error, + ); +} + function onUnexpected( i18n: InternationalizationAPI, save: (m: NotificationMessage) => void, -): (cause: unknown) => void { - return (error) => { +): (cause: unknown, args: any[]) => void { + return (error, args) => { if (error instanceof TalerError) { save({ title: translateTalerError(error, i18n), @@ -318,7 +324,12 @@ function onUnexpected( error && error.errorDetail.hint ? (error.errorDetail.hint as TranslatedString) : undefined, - debug: error, + debug: { + error, + stack: error instanceof Error ? error.stack : undefined, + args: sanitizeFunctionArguments(args), + when: AbsoluteTime.now(), + }, when: AbsoluteTime.now(), }); } else { @@ -327,16 +338,33 @@ function onUnexpected( ) as TranslatedString; save({ - title: i18n.str`Operation failed`, + title: i18n.str`Operation failed.`, type: "error", - description, - debug: error, + description: i18n.str`Unexpected error, this is likely a bug. Please report `, + debug: { + error: String(error), + stack: error instanceof Error ? error.stack : undefined, + args: sanitizeFunctionArguments(args), + when: AbsoluteTime.now(), + }, when: AbsoluteTime.now(), }); } }; } +function sanitizeFunctionArguments(args: any[]): string { + return args + .map((d) => + typeof d === "string" && d.startsWith("secret-token:") + ? "<session>" + : typeof d === "object" + ? JSON.stringify(d, undefined, 2) + : d, + ) + .join(", "); +} + /** * A function converted into a safe handler. * @@ -375,15 +403,19 @@ function successWithTitle(title: TranslatedString): NotificationMessage { } function failWithTitle( + i18n: InternationalizationAPI, fail: OperationFail<any>, - title: TranslatedString, + description: TranslatedString, ): NotificationMessage { - const d = fail.detail; return { - title, + title: i18n.str`The operation failed.`, type: "error", - description: d && d.hint ? (d.hint as TranslatedString) : undefined, - debug: d, + description, + debug: { + detail: fail.detail, + case: fail.case, + when: AbsoluteTime.now(), + }, when: AbsoluteTime.now(), }; }