diff options
Diffstat (limited to 'packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts')
-rw-r--r-- | packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts | 213 |
1 files changed, 165 insertions, 48 deletions
diff --git a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts index 72594749d..fcc380775 100644 --- a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts +++ b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts @@ -1,5 +1,36 @@ +/* + This file is part of GNU Anastasis + (C) 2021-2022 Anastasis SARL + + GNU Anastasis is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Anastasis 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Imports. + */ import { TalerErrorCode } from "@gnu-taler/taler-util"; -import { BackupStates, getBackupStartState, getRecoveryStartState, RecoveryStates, reduceAction, ReducerState } from "anastasis-core"; +import { + AggregatedPolicyMetaInfo, + BackupStates, + completeProviderStatus, + discoverPolicies, + DiscoveryCursor, + getBackupStartState, + getRecoveryStartState, + mergeDiscoveryAggregate, + RecoveryStates, + reduceAction, + ReducerState, +} from "@gnu-taler/anastasis-core"; import { useState } from "preact/hooks"; const reducerBaseUrl = "http://localhost:5000/"; @@ -8,6 +39,7 @@ const remoteReducer = false; interface AnastasisState { reducerState: ReducerState | undefined; currentError: any; + discoveryState: DiscoveryUiState; } async function getBackupStartStateRemote(): Promise<ReducerState> { @@ -91,20 +123,39 @@ export interface ReducerTransactionHandle { transition(action: string, args: any): Promise<ReducerState>; } +/** + * UI-relevant state of the policy discovery process. + */ +export interface DiscoveryUiState { + state: "none" | "active" | "finished"; + + aggregatedPolicies?: AggregatedPolicyMetaInfo[]; + + cursor?: DiscoveryCursor; +} + export interface AnastasisReducerApi { currentReducerState: ReducerState | undefined; + // FIXME: Explain better! currentError: any; + discoveryState: DiscoveryUiState; dismissError: () => void; startBackup: () => void; startRecover: () => void; reset: () => void; - back: () => void; - transition(action: string, args: any): void; + back: () => Promise<void>; + transition(action: string, args: any): Promise<void>; + exportState: () => string; + importState: (s: string) => void; + discoverStart(): Promise<void>; + discoverMore(): Promise<void>; /** * Run multiple reducer steps in a transaction without * affecting the UI-visible transition state in-between. */ - runTransaction(f: (h: ReducerTransactionHandle) => Promise<void>): void; + runTransaction( + f: (h: ReducerTransactionHandle) => Promise<void>, + ): Promise<void>; } function storageGet(key: string): string | null { @@ -120,18 +171,17 @@ function storageSet(key: string, value: any): void { } } -function restoreState(): any { +function getStateFromStorage(): any { let state: any; try { const s = storageGet("anastasisReducerState"); if (s === "undefined") { state = undefined; } else if (s) { - console.log("restoring state from", s); state = JSON.parse(s); } } catch (e) { - console.log(e); + console.log("ERROR: getStateFromStorage ", e); } return state ?? undefined; } @@ -139,8 +189,11 @@ function restoreState(): any { export function useAnastasisReducer(): AnastasisReducerApi { const [anastasisState, setAnastasisStateInternal] = useState<AnastasisState>( () => ({ - reducerState: restoreState(), + reducerState: getStateFromStorage(), currentError: undefined, + discoveryState: { + state: "none", + }, }), ); @@ -151,25 +204,58 @@ export function useAnastasisReducer(): AnastasisReducerApi { JSON.stringify(newState.reducerState), ); } catch (e) { - console.log(e); + console.log("ERROR setAnastasisState", e); } setAnastasisStateInternal(newState); + + const tryUpdateProviders = () => { + const reducerState = newState.reducerState; + if ( + reducerState?.reducer_type !== "backup" && + reducerState?.reducer_type !== "recovery" + ) { + return; + } + const provMap = reducerState.authentication_providers; + if (!provMap) { + return; + } + const doUpdate = async () => { + const updates = await completeProviderStatus(provMap); + if (Object.keys(updates).length === 0) { + return; + } + const rs2 = reducerState; + if (rs2.reducer_type !== "backup" && rs2.reducer_type !== "recovery") { + return; + } + setAnastasisState({ + ...anastasisState, + reducerState: { + ...rs2, + authentication_providers: { + ...rs2.authentication_providers, + ...updates, + }, + }, + }); + }; + doUpdate().catch((e) => console.log("ERROR doUpdate", e)); + }; + + tryUpdateProviders(); }; - async function doTransition(action: string, args: any) { - console.log("reducing with", action, args); + async function doTransition(action: string, args: any): Promise<void> { let s: ReducerState; if (remoteReducer) { s = await reduceStateRemote(anastasisState.reducerState, action, args); } else { s = await reduceAction(anastasisState.reducerState!, action, args); } - console.log("got response from reducer", s); - if (s.code) { - console.log("response is an error"); + if (s.reducer_type === "error") { setAnastasisState({ ...anastasisState, currentError: s }); } else { - console.log("response is a new state"); setAnastasisState({ ...anastasisState, currentError: undefined, @@ -181,6 +267,7 @@ export function useAnastasisReducer(): AnastasisReducerApi { return { currentReducerState: anastasisState.reducerState, currentError: anastasisState.currentError, + discoveryState: anastasisState.discoveryState, async startBackup() { let s: ReducerState; if (remoteReducer) { @@ -188,7 +275,7 @@ export function useAnastasisReducer(): AnastasisReducerApi { } else { s = await getBackupStartState(); } - if (s.code !== undefined) { + if (s.reducer_type === "error") { setAnastasisState({ ...anastasisState, currentError: s, @@ -201,6 +288,39 @@ export function useAnastasisReducer(): AnastasisReducerApi { }); } }, + exportState() { + const state = getStateFromStorage(); + return JSON.stringify(state); + }, + importState(s: string) { + try { + const state = JSON.parse(s); + setAnastasisState({ + reducerState: state, + currentError: undefined, + discoveryState: { + state: "none", + }, + }); + } catch (e) { + throw new Error("could not restore the state"); + } + }, + async discoverStart(): Promise<void> { + const res = await discoverPolicies(this.currentReducerState!, undefined); + const aggregatedPolicies = mergeDiscoveryAggregate(res.policies, []); + setAnastasisState({ + ...anastasisState, + discoveryState: { + state: "finished", + aggregatedPolicies, + cursor: res.cursor, + }, + }); + }, + async discoverMore(): Promise<void> { + return; + }, async startRecover() { let s: ReducerState; if (remoteReducer) { @@ -208,7 +328,7 @@ export function useAnastasisReducer(): AnastasisReducerApi { } else { s = await getRecoveryStartState(); } - if (s.code !== undefined) { + if (s.reducer_type === "error") { setAnastasisState({ ...anastasisState, currentError: s, @@ -222,16 +342,18 @@ export function useAnastasisReducer(): AnastasisReducerApi { } }, transition(action: string, args: any) { - doTransition(action, args); + return doTransition(action, args); }, - back() { + async back() { const reducerState = anastasisState.reducerState; if (!reducerState) { return; } if ( - reducerState.backup_state === BackupStates.ContinentSelecting || - reducerState.recovery_state === RecoveryStates.ContinentSelecting + (reducerState.reducer_type === "backup" && + reducerState.backup_state === BackupStates.ContinentSelecting) || + (reducerState.reducer_type === "recovery" && + reducerState.recovery_state === RecoveryStates.ContinentSelecting) ) { setAnastasisState({ ...anastasisState, @@ -239,7 +361,7 @@ export function useAnastasisReducer(): AnastasisReducerApi { reducerState: undefined, }); } else { - doTransition("back", {}); + await doTransition("back", {}); } }, dismissError() { @@ -252,30 +374,26 @@ export function useAnastasisReducer(): AnastasisReducerApi { reducerState: undefined, }); }, - runTransaction(f) { - async function run() { - const txHandle = new ReducerTxImpl(anastasisState.reducerState!); - try { - await f(txHandle); - } catch (e) { - console.log("exception during reducer transaction", e); - } - const s = txHandle.transactionState; - console.log("transaction finished, new state", s); - if (s.code !== undefined) { - setAnastasisState({ - ...anastasisState, - currentError: txHandle.transactionState, - }); - } else { - setAnastasisState({ - ...anastasisState, - reducerState: txHandle.transactionState, - currentError: undefined, - }); - } + async runTransaction(f) { + const txHandle = new ReducerTxImpl(anastasisState.reducerState!); + try { + await f(txHandle); + } catch (e) { + console.log("exception during reducer transaction", e); + } + const s = txHandle.transactionState; + if (s.reducer_type === "error") { + setAnastasisState({ + ...anastasisState, + currentError: txHandle.transactionState, + }); + } else { + setAnastasisState({ + ...anastasisState, + reducerState: txHandle.transactionState, + currentError: undefined, + }); } - run(); }, }; } @@ -289,11 +407,10 @@ class ReducerTxImpl implements ReducerTransactionHandle { } else { s = await reduceAction(this.transactionState, action, args); } - console.log("making transition in transaction", action); this.transactionState = s; // Abort transaction as soon as we transition into an error state. - if (this.transactionState.code !== undefined) { - throw Error("transition resulted in error"); + if (this.transactionState.reducer_type === "error") { + throw new Error("transition resulted in error"); } return this.transactionState; } |