/* This file is part of GNU Taler (C) 2022-2024 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 */ import { AbsoluteTime, Codec, TalerCorebankApi, buildCodecForObject, buildCodecForUnion, codecForAbsoluteTime, codecForAny, codecForConstString, codecForString, codecForTanTransmission, codecOptional, } from "@gnu-taler/taler-util"; import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser"; import { AppLocation } from "../route.js"; export type ChallengeInProgess = | DeleteAccountChallenge | UpdateAccountChallenge | UpdatePasswordChallenge | CreateTransactionChallenge | ConfirmWithdrawalChallenge | CashoutChallenge; type BaseChallenge = { id: string; operation: OpType; sent: AbsoluteTime; location: AppLocation; info?: TalerCorebankApi.TanTransmission; request: ReqType; }; type DeleteAccountChallenge = BaseChallenge<"delete-account", string>; type UpdateAccountChallenge = BaseChallenge< "update-account", TalerCorebankApi.AccountReconfiguration >; type UpdatePasswordChallenge = BaseChallenge< "update-password", TalerCorebankApi.AccountPasswordChange >; type CreateTransactionChallenge = BaseChallenge< "create-transaction", TalerCorebankApi.CreateTransactionRequest >; type ConfirmWithdrawalChallenge = BaseChallenge<"confirm-withdrawal", string>; type CashoutChallenge = BaseChallenge< "create-cashout", TalerCorebankApi.CashoutRequest >; const codecForChallengeUpdatePassword = (): Codec => buildCodecForObject() .property("operation", codecForConstString("update-password")) .property("id", codecForString()) .property("location", codecForAppLocation()) .property("sent", codecForAbsoluteTime) .property("info", codecOptional(codecForTanTransmission())) .property("request", codecForAny()) .build("UpdatePasswordChallenge"); const codecForChallengeDeleteAccount = (): Codec => buildCodecForObject() .property("operation", codecForConstString("delete-account")) .property("id", codecForString()) .property("location", codecForAppLocation()) .property("sent", codecForAbsoluteTime) .property("request", codecForString()) .property("info", codecOptional(codecForTanTransmission())) .build("DeleteAccountChallenge"); const codecForChallengeUpdateAccount = (): Codec => buildCodecForObject() .property("operation", codecForConstString("update-account")) .property("id", codecForString()) .property("location", codecForAppLocation()) .property("sent", codecForAbsoluteTime) .property("info", codecOptional(codecForTanTransmission())) .property("request", codecForAny()) .build("UpdateAccountChallenge"); const codecForChallengeCreateTransaction = (): Codec => buildCodecForObject() .property("operation", codecForConstString("create-transaction")) .property("id", codecForString()) .property("location", codecForAppLocation()) .property("sent", codecForAbsoluteTime) .property("info", codecOptional(codecForTanTransmission())) .property("request", codecForAny()) .build("CreateTransactionChallenge"); const codecForChallengeConfirmWithdrawal = (): Codec => buildCodecForObject() .property("operation", codecForConstString("confirm-withdrawal")) .property("id", codecForString()) .property("location", codecForAppLocation()) .property("sent", codecForAbsoluteTime) .property("info", codecOptional(codecForTanTransmission())) .property("request", codecForString()) .build("ConfirmWithdrawalChallenge"); const codecForAppLocation = codecForString as () => Codec; const codecForChallengeCashout = (): Codec => buildCodecForObject() .property("operation", codecForConstString("create-cashout")) .property("id", codecForString()) .property("location", codecForAppLocation()) .property("sent", codecForAbsoluteTime) .property("info", codecOptional(codecForTanTransmission())) .property("request", codecForAny()) .build("CashoutChallenge"); const codecForChallenge = (): Codec => buildCodecForUnion() .discriminateOn("operation") .alternative("confirm-withdrawal", codecForChallengeConfirmWithdrawal()) .alternative("create-cashout", codecForChallengeCashout()) .alternative("create-transaction", codecForChallengeCreateTransaction()) .alternative("delete-account", codecForChallengeDeleteAccount()) .alternative("update-account", codecForChallengeUpdateAccount()) .alternative("update-password", codecForChallengeUpdatePassword()) .build("ChallengeInProgess"); interface BankState { currentWithdrawalOperationId: string | undefined; currentChallenge: ChallengeInProgess | undefined; } export const codecForBankState = (): Codec => buildCodecForObject() .property("currentWithdrawalOperationId", codecOptional(codecForString())) .property("currentChallenge", codecOptional(codecForChallenge())) .build("BankState"); const defaultBankState: BankState = { currentWithdrawalOperationId: undefined, currentChallenge: undefined, }; const BANK_STATE_KEY = buildStorageKey("bank-app-state", codecForBankState()); /** * Client state saved in local storage. * * This information is saved in the client because * the backend server session API is not enough. * * @returns tuple of [state, update(), reset()] */ export function useBankState(): [ Readonly, (key: T, value: BankState[T]) => void, () => void, ] { const { value, update } = useLocalStorage(BANK_STATE_KEY, defaultBankState); function updateField(k: T, v: BankState[T]) { const newValue = { ...value, [k]: v }; update(newValue); } function reset() { update(defaultBankState); } return [value, updateField, reset]; }