summaryrefslogtreecommitdiff
path: root/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts')
-rw-r--r--packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts213
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;
}