/* This file is part of GNU Taler (C) 2022 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 { AccessToken, Codec, buildCodecForObject, buildCodecForUnion, canonicalizeBaseUrl, codecForBoolean, codecForConstString, codecForString, } from "@gnu-taler/taler-util"; import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser"; import { useSWRConfig } from "swr"; /** * Has the information to reach and * authenticate at the bank's backend. */ export type BackendState = LoggedIn | LoggedOut | Expired; interface LoggedIn { status: "loggedIn"; isUserAdministrator: boolean; username: string; token: AccessToken; } interface Expired { status: "expired"; isUserAdministrator: boolean; username: string; } interface LoggedOut { status: "loggedOut"; } export const codecForBackendStateLoggedIn = (): Codec => buildCodecForObject() .property("status", codecForConstString("loggedIn")) .property("username", codecForString()) .property("token", codecForString() as Codec) .property("isUserAdministrator", codecForBoolean()) .build("BackendState.LoggedIn"); export const codecForBackendStateExpired = (): Codec => buildCodecForObject() .property("status", codecForConstString("expired")) .property("username", codecForString()) .property("isUserAdministrator", codecForBoolean()) .build("BackendState.Expired"); export const codecForBackendStateLoggedOut = (): Codec => buildCodecForObject() .property("status", codecForConstString("loggedOut")) .build("BackendState.LoggedOut"); export const codecForBackendState = (): Codec => buildCodecForUnion() .discriminateOn("status") .alternative("loggedIn", codecForBackendStateLoggedIn()) .alternative("loggedOut", codecForBackendStateLoggedOut()) .alternative("expired", codecForBackendStateExpired()) .build("BackendState"); export const defaultState: BackendState = { status: "loggedOut", }; export interface BackendStateHandler { state: BackendState; logOut(): void; expired(): void; logIn(info: { username: string, token: AccessToken }): void; } const BACKEND_STATE_KEY = buildStorageKey( "bank-state", codecForBackendState(), ); /** * Return getters and setters for * login credentials and backend's * base URL. */ export function useBackendState(): BackendStateHandler { const { value: state, update } = useLocalStorage( BACKEND_STATE_KEY, defaultState, ); const mutateAll = useMatchMutate(); return { state, logOut() { update(defaultState); }, expired() { if (state.status === "loggedOut") return; const nextState: BackendState = { status: "expired", username: state.username, isUserAdministrator: state.username === "admin", }; update(nextState); }, logIn(info) { //admin is defined by the username const nextState: BackendState = { status: "loggedIn", ...info, isUserAdministrator: info.username === "admin", }; update(nextState); mutateAll(/.*/) }, }; } export function useMatchMutate(): ( re: RegExp, value?: unknown, ) => Promise { const { cache, mutate } = useSWRConfig(); if (!(cache instanceof Map)) { throw new Error( "matchMutate requires the cache provider to be a Map instance", ); } return function matchRegexMutate(re: RegExp, value?: unknown) { const allKeys = Array.from(cache.keys()); const keys = allKeys.filter((key) => re.test(key)); const mutations = keys.map((key) => { return mutate(key, value, true); }); return Promise.all(mutations); }; }