/*
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 {
ChallengerApi,
Codec,
buildCodecForObject,
codecForBoolean,
codecForChallengeStatus,
codecForNumber,
codecForString,
codecForStringURL,
codecOptional,
} from "@gnu-taler/taler-util";
import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
import { mutate } from "swr";
/**
* Has the information to reach and
* authenticate at the bank's backend.
*/
export type SessionId = {
clientId: string;
redirectURL: string;
state: string;
};
export type LastChallengeResponse = {
attemptsLeft: number;
nextSend: string;
transmitted: boolean;
};
export type SessionState = SessionId & {
email: string | undefined;
lastTry: LastChallengeResponse | undefined;
challengeStatus: ChallengerApi.ChallengeStatus | undefined;
completedURL: string | undefined;
};
export const codecForLastChallengeResponse = (): Codec =>
buildCodecForObject()
.property("attemptsLeft", codecForNumber())
.property("nextSend", codecForString())
.property("transmitted", codecForBoolean())
.build("LastChallengeResponse");
export const codecForSessionState = (): Codec =>
buildCodecForObject()
.property("clientId", codecForString())
.property("redirectURL", codecForStringURL())
.property("completedURL", codecOptional(codecForStringURL()))
.property("state", codecForString())
.property("challengeStatus", codecOptional(codecForChallengeStatus()))
.property("lastTry", codecOptional(codecForLastChallengeResponse()))
.property("email", codecOptional(codecForString()))
.build("SessionState");
export interface SessionStateHandler {
state: SessionState | undefined;
start(s: SessionId): void;
accepted(e: string, l: LastChallengeResponse): void;
completed(e: URL): void;
updateStatus(s: ChallengerApi.ChallengeStatus): void;
}
const SESSION_STATE_KEY = buildStorageKey(
"challenger-session",
codecForSessionState(),
);
/**
* Return getters and setters for
* login credentials and backend's
* base URL.
*/
export function useSessionState(): SessionStateHandler {
const { value: state, update } = useLocalStorage(SESSION_STATE_KEY);
return {
state,
start(info) {
update({
...info,
lastTry: undefined,
completedURL: undefined,
challengeStatus: undefined,
email: undefined,
});
cleanAllCache();
},
accepted(email, lastTry) {
if (!state) return;
update({
...state,
email,
lastTry,
});
},
completed(url) {
if (!state) return;
update({
...state,
completedURL: url.href,
});
},
updateStatus(st: ChallengerApi.ChallengeStatus) {
if (!state) return;
if (!state.challengeStatus) {
update({
...state,
challengeStatus: st,
});
return;
}
// current status
const cu = state.challengeStatus;
if (
cu.changes_left !== st.changes_left ||
cu.fix_address !== st.fix_address ||
cu.last_address !== st.last_address
) {
update({
...state,
challengeStatus: st,
});
return;
}
},
};
}
function cleanAllCache(): void {
mutate(() => true, undefined, { revalidate: false });
}