/*
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];
}