diff options
Diffstat (limited to 'packages/anastasis-webui')
18 files changed, 521 insertions, 328 deletions
diff --git a/packages/anastasis-webui/clean_and_build.sh b/packages/anastasis-webui/clean_and_build.sh index a88dc9c37..9486848fe 100755 --- a/packages/anastasis-webui/clean_and_build.sh +++ b/packages/anastasis-webui/clean_and_build.sh @@ -40,17 +40,20 @@ set -e echo compile build_css & build_js src/main.ts & +build_js src/stories.tsx & build_js src/main.test.ts & for file in $(find src/ -name test.ts); do build_js $file; done & wait -n wait -n wait -n wait -n +wait -n pnpm run --silent test -- -R dot echo html build_html ui build_html ui-dev +build_html stories if [ "WATCH" == "$1" ]; then @@ -62,6 +65,8 @@ if [ "WATCH" == "$1" ]; then echo $(date) $line build_js src/main.ts build_html ui-dev + build_js src/stories.tsx + build_html stories ./watch/send.sh '{"type":"RELOAD"}' done; fi diff --git a/packages/anastasis-webui/src/components/menu/SideBar.tsx b/packages/anastasis-webui/src/components/menu/SideBar.tsx index f83131ae1..ee394da83 100644 --- a/packages/anastasis-webui/src/components/menu/SideBar.tsx +++ b/packages/anastasis-webui/src/components/menu/SideBar.tsx @@ -28,11 +28,8 @@ interface Props { mobile?: boolean; } -// @ts-ignore -const maybeEnv = process?.env || {}; - -const VERSION: string = maybeEnv.__VERSION__ || "dev"; -const GIT_HASH: string | undefined = maybeEnv.__GIT_HASH__; +const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : "dev"; +const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; const VERSION_WITH_HASH = GIT_HASH ? `${VERSION}-${GIT_HASH}` : VERSION; export function Sidebar({ mobile }: Props): VNode { diff --git a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts index f78576118..3ad563ee6 100644 --- a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts +++ b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts @@ -180,7 +180,7 @@ function getStateFromStorage(): any { state = JSON.parse(s); } } catch (e) { - console.log(e); + console.log("ERROR: getStateFromStorage ", e); } return state ?? undefined; } @@ -203,7 +203,7 @@ export function useAnastasisReducer(): AnastasisReducerApi { JSON.stringify(newState.reducerState), ); } catch (e) { - console.log(e); + console.log("ERROR setAnastasisState", e); } setAnastasisStateInternal(newState); @@ -239,7 +239,7 @@ export function useAnastasisReducer(): AnastasisReducerApi { }, }); }; - doUpdate().catch((e) => console.log(e)); + doUpdate().catch((e) => console.log("ERROR doUpdate", e)); }; tryUpdateProviders(); diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/index.ts b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/index.ts index 5d5913ffc..0ab275f54 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/index.ts +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/index.ts @@ -16,6 +16,7 @@ import { AuthenticationProviderStatus } from "@gnu-taler/anastasis-core"; import InvalidState from "../../../components/InvalidState.js"; import NoReducer from "../../../components/NoReducer.js"; +import { Notification } from "../../../components/Notifications.js"; import { compose, StateViewMap } from "../../../utils/index.js"; import useComponentState from "./state.js"; import { WithoutProviderType, WithProviderType } from "./views.js"; @@ -44,6 +45,7 @@ interface CommonProps { setProviderURL: (url: string) => Promise<void>; providerURL: string; errors: string | undefined; + notifications: Notification[]; } export interface WithType extends CommonProps { @@ -90,7 +92,7 @@ export async function testProvider( } return; } catch (e) { - console.log("error", e); + console.log("ERROR testProvider", e); const error = e instanceof Error ? Error( diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/state.ts b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/state.ts index a04c7957b..009ab20a2 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/state.ts +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/state.ts @@ -14,6 +14,7 @@ GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ import { useEffect, useRef, useState } from "preact/hooks"; +import { Notification } from "../../../components/Notifications.js"; import { useAnastasisContext } from "../../../context/anastasis.js"; import { authMethods, KnownAuthMethods } from "../authMethod/index.jsx"; import { AuthProvByStatusMap, State, testProvider } from "./index.js"; @@ -21,9 +22,10 @@ import { AuthProvByStatusMap, State, testProvider } from "./index.js"; interface Props { providerType?: KnownAuthMethods; onCancel: () => Promise<void>; + notifications?: Notification[]; } -export default function useComponentState({ providerType, onCancel }: Props): State { +export default function useComponentState({ providerType, onCancel, notifications = [] }: Props): State { const reducer = useAnastasisContext(); const [providerURL, setProviderURL] = useState(""); @@ -128,6 +130,7 @@ export default function useComponentState({ providerType, onCancel }: Props): St setProviderURL: async (s: string) => setProviderURL(s), errors, error, + notifications } if (!providerLabel) { diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/stories.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/stories.tsx index 7faaae4d8..dc41d9c1a 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/stories.tsx @@ -53,6 +53,7 @@ export const NewProvider = createExampleWithoutAnastasis(WithoutProviderType, { disabled: [], error: [], }, + notifications: [], }); export const NewProviderWithoutProviderList = createExampleWithoutAnastasis( @@ -64,6 +65,7 @@ export const NewProviderWithoutProviderList = createExampleWithoutAnastasis( disabled: [], error: [], }, + notifications: [], }, ); @@ -75,6 +77,7 @@ export const NewSmsProvider = createExampleWithoutAnastasis(WithProviderType, { error: [], }, providerLabel: "sms", + notifications: [], }); export const NewIBANProvider = createExampleWithoutAnastasis(WithProviderType, { @@ -85,4 +88,5 @@ export const NewIBANProvider = createExampleWithoutAnastasis(WithProviderType, { error: [], }, providerLabel: "IBAN", + notifications: [], }); diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/views.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/views.tsx index bb1283a82..e397e0b65 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/views.tsx +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/views.tsx @@ -20,6 +20,7 @@ import { import { h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { TextInput } from "../../../components/fields/TextInput.js"; +import { Notifications } from "../../../components/Notifications.js"; import { AnastasisClientFrame } from "../index.js"; import { testProvider, WithoutType, WithType } from "./index.js"; @@ -31,6 +32,7 @@ export function WithProviderType(props: WithType): VNode { hideNext={props.errors} > <div> + <Notifications notifications={props.notifications} /> <p>Add a provider url for a {props.providerLabel} service</p> <div class="container"> <TextInput @@ -108,10 +110,11 @@ export function WithoutProviderType(props: WithoutType): VNode { return ( <AnastasisClientFrame hideNav - title="Backup: Manage providers2" + title="Backup: Manage providers" hideNext={props.errors} > <div> + <Notifications notifications={props.notifications} /> <p>Add a provider url</p> <div class="container"> <TextInput diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx index e3d92c4df..552cb069f 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx @@ -174,85 +174,88 @@ export const OnePolicyWithAllTheChallengesInDifferentState = createExample( { uuid: "uuid-7" }, { uuid: "uuid-8" }, { uuid: "uuid-9" }, + { uuid: "uuid-10" }, ], ], challenges: [ { - instructions: 'in state "solved"', + instructions: 'this challenge is in state "solved"', type: "question", uuid: "uuid-1", }, { - instructions: 'in state "message"', + instructions: 'this challenge is in state "code-in-file"', type: "question", uuid: "uuid-2", }, { - instructions: 'in state "auth iban"', + instructions: 'this challenge is in state "code-sent"', type: "question", uuid: "uuid-3", }, { - instructions: 'in state "payment "', + instructions: 'this challenge is in state "server-failure "', type: "question", uuid: "uuid-4", }, { - instructions: 'in state "rate limit"', + instructions: 'this challenge is in state "truth-unknown"', type: "question", uuid: "uuid-5", }, { - instructions: 'in state "redirect"', + instructions: 'this challenge is in state "taler-payment"', type: "question", uuid: "uuid-6", }, { - instructions: 'in state "server failure"', + instructions: 'this challenge is in state "unsupported"', type: "question", uuid: "uuid-7", }, { - instructions: 'in state "truth unknown"', + instructions: 'this challenge is in state "rate-limit-exceeded"', type: "question", uuid: "uuid-8", }, { - instructions: 'in state "unsupported"', + instructions: 'this challenge is in state "iban-instructions"', type: "question", uuid: "uuid-9", }, + { + instructions: 'this challenge is in state "incorrect-answer"', + type: "question", + uuid: "uuid-10", + }, ], }, challenge_feedback: { "uuid-1": { state: ChallengeFeedbackStatus.Solved.toString() }, - "uuid-3": { - state: ChallengeFeedbackStatus.IbanInstructions.toString(), - challenge_amount: "EUR:1", - target_iban: "DE12345789000", - target_business_name: "Data Loss Incorporated", - wire_transfer_subject: "Anastasis 987654321", - }, + "uuid-2": { state: ChallengeFeedbackStatus.CodeInFile.toString() }, + "uuid-3": { state: ChallengeFeedbackStatus.CodeSent.toString() }, "uuid-4": { + state: ChallengeFeedbackStatus.ServerFailure.toString(), + http_status: 500, + error_response: "some error message or error object", + }, + "uuid-5": { state: ChallengeFeedbackStatus.TruthUnknown.toString() }, + "uuid-6": { state: ChallengeFeedbackStatus.TalerPayment.toString(), taler_pay_uri: "taler://pay/...", provider: "https://localhost:8080/", payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG", }, - "uuid-5": { - state: ChallengeFeedbackStatus.RateLimitExceeded.toString(), - // "error_code": 8121 - }, - "uuid-7": { - state: ChallengeFeedbackStatus.ServerFailure.toString(), - http_status: 500, - error_response: "some error message or error object", - }, - "uuid-8": { - state: ChallengeFeedbackStatus.TruthUnknown.toString(), - // "error_code": 8108 + "uuid-7": { state: ChallengeFeedbackStatus.Unsupported.toString() }, + "uuid-8": { state: ChallengeFeedbackStatus.RateLimitExceeded.toString() }, + "uuid-9": { + state: ChallengeFeedbackStatus.IbanInstructions.toString(), + challenge_amount: "EUR:1", + target_iban: "DE12345789000", + target_business_name: "Data Loss Incorporated", + wire_transfer_subject: "Anastasis 987654321", }, - "uuid-9": { state: ChallengeFeedbackStatus.Unsupported.toString() }, + "uuid-10": { state: ChallengeFeedbackStatus.IncorrectAnswer.toString() }, }, } as ReducerState, ); diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx index 8de0ceb08..5b9c11bab 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx @@ -17,22 +17,25 @@ import { ChallengeFeedback, ChallengeFeedbackStatus, } from "@gnu-taler/anastasis-core"; -import { h, VNode } from "preact"; +import { Fragment, h, VNode } from "preact"; import { AsyncButton } from "../../components/AsyncButton.js"; import { useAnastasisContext } from "../../context/anastasis.js"; import { authMethods, KnownAuthMethods } from "./authMethod/index.js"; import { AnastasisClientFrame } from "./index.js"; -function OverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }) { +function OverviewFeedbackDisplay(props: { + feedback?: ChallengeFeedback; +}): VNode { const { feedback } = props; if (!feedback) { - return null; + return <Fragment />; } + switch (feedback.state) { case ChallengeFeedbackStatus.Solved: return <div />; case ChallengeFeedbackStatus.IbanInstructions: - return null; + return <div class="block has-text-info">Payment required.</div>; case ChallengeFeedbackStatus.ServerFailure: return <div class="block has-text-danger">Server error.</div>; case ChallengeFeedbackStatus.RateLimitExceeded: @@ -51,12 +54,20 @@ function OverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }) { case ChallengeFeedbackStatus.TruthUnknown: return ( <div class="block has-text-danger"> - Provider doesn't recognize the challenge of the policy. Contact - the provider for further information. + Provider doesn't recognize the type of challenge. Use another + version or contact the provider. </div> ); - default: - return <div />; + case ChallengeFeedbackStatus.IncorrectAnswer: + return ( + <div class="block has-text-danger">The answer was not correct.</div> + ); + case ChallengeFeedbackStatus.CodeInFile: + return <div class="block has-text-info">code in file</div>; + case ChallengeFeedbackStatus.CodeSent: + return <div class="block has-text-info">Code sent</div>; + case ChallengeFeedbackStatus.TalerPayment: + return <div class="block has-text-info">Payment required</div>; } } diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx index fec1e0113..b457937f8 100644 --- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx @@ -21,29 +21,57 @@ import { ReducerState } from "@gnu-taler/anastasis-core"; import { createExample, reducerStatesExample } from "../../utils/index.js"; -import { SecretSelectionScreen as TestedComponent } from "./SecretSelectionScreen.js"; +import { + SecretSelectionScreen, + SecretSelectionScreenFound, +} from "./SecretSelectionScreen.js"; export default { - component: TestedComponent, + component: SecretSelectionScreen, args: { order: 4, }, - argTypes: { - onUpdate: { action: "onUpdate" }, - onBack: { action: "onBack" }, - }, }; -export const Example = createExample(TestedComponent, { - ...reducerStatesExample.secretSelection, - recovery_document: { - provider_url: "https://kudos.demo.anastasis.lu/", - secret_name: "secretName", - version: 1, +export const Example = createExample( + SecretSelectionScreenFound, + { + ...reducerStatesExample.secretSelection, + recovery_document: { + provider_url: "https://kudos.demo.anastasis.lu/", + secret_name: "secretName", + version: 1, + }, + } as ReducerState, + { + policies: [ + { + secret_name: "The secret name 1", + attribute_mask: 1, + policy_hash: "abcdefghijklmnopqrstuvwxyz", + providers: [ + { + url: "http://someurl", + version: 1, + }, + ], + }, + { + secret_name: "The secret name 2", + attribute_mask: 1, + policy_hash: "abcdefghijklmnopqrstuvwxyz", + providers: [ + { + url: "http://someurl", + version: 1, + }, + ], + }, + ], }, -} as ReducerState); +); -export const NoRecoveryDocumentFound = createExample(TestedComponent, { +export const NoRecoveryDocumentFound = createExample(SecretSelectionScreen, { ...reducerStatesExample.secretSelection, recovery_document: undefined, } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx index 11271aaa5..ce44b0884 100644 --- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx @@ -14,6 +14,7 @@ GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ import { + AggregatedPolicyMetaInfo, AuthenticationProviderStatus, AuthenticationProviderStatusOk, } from "@gnu-taler/anastasis-core"; @@ -25,20 +26,16 @@ import { useAnastasisContext } from "../../context/anastasis.js"; import AddingProviderScreen from "./AddingProviderScreen/index.js"; import { AnastasisClientFrame } from "./index.js"; -export function SecretSelectionScreen(): VNode { - const [selectingVersion, setSelectingVersion] = useState<boolean>(false); +export function SecretSelectionScreenFound({ + policies, + onManageProvider, + onNext, +}: { + policies: AggregatedPolicyMetaInfo[]; + onManageProvider: () => void; + onNext: (version: AggregatedPolicyMetaInfo) => void; +}): VNode { const reducer = useAnastasisContext(); - const [manageProvider, setManageProvider] = useState(false); - - useEffect(() => { - async function f() { - if (reducer) { - await reducer.discoverStart(); - } - } - f().catch((e) => console.log(e)); - }, []); - if (!reducer) { return <div>no reducer in context</div>; } @@ -49,45 +46,6 @@ export function SecretSelectionScreen(): VNode { ) { return <div>invalid state</div>; } - - const provs = reducer.currentReducerState.authentication_providers ?? {}; - const recoveryDocument = reducer.currentReducerState.recovery_document; - - if (manageProvider) { - return ( - <AddingProviderScreen onCancel={async () => setManageProvider(false)} /> - ); - } - - if (reducer.discoveryState.state === "none") { - // Can this even happen? - return ( - <AnastasisClientFrame title="Recovery: Select secret"> - <div>waiting to start discovery</div> - </AnastasisClientFrame> - ); - } - - if (reducer.discoveryState.state === "active") { - return ( - <AnastasisClientFrame title="Recovery: Select secret"> - <div>loading secret versions</div> - </AnastasisClientFrame> - ); - } - - const policies = reducer.discoveryState.aggregatedPolicies ?? []; - - if (policies.length === 0) { - return ( - <ChooseAnotherProviderScreen - providers={provs} - selected="" - onChange={() => null} - ></ChooseAnotherProviderScreen> - ); - } - return ( <AnastasisClientFrame title="Recovery: Select secret" @@ -115,9 +73,7 @@ export function SecretSelectionScreen(): VNode { <b>Id:</b> <span class="icon has-tooltip-top" - data-tooltip={version.policy_hash - .match(/(.{22})/g) - ?.join("\n")} + data-tooltip={version.policy_hash} > <i class="mdi mdi-information" /> </span> @@ -128,9 +84,7 @@ export function SecretSelectionScreen(): VNode { <div> <AsyncButton class="button" - onClick={() => - reducer.transition("select_version", version) - } + onClick={async () => onNext(version)} > Recover </AsyncButton> @@ -145,9 +99,7 @@ export function SecretSelectionScreen(): VNode { challenges solving </p> <p class="block"> - <a onClick={() => setManageProvider(true)}> - Manage recovery providers - </a> + <a onClick={onManageProvider}>Manage recovery providers</a> </p> </div> </div> @@ -155,8 +107,7 @@ export function SecretSelectionScreen(): VNode { ); } -export function OldSecretSelectionScreen(): VNode { - const [selectingVersion, setSelectingVersion] = useState<boolean>(false); +export function SecretSelectionScreen(): VNode { const reducer = useAnastasisContext(); const [manageProvider, setManageProvider] = useState(false); @@ -169,15 +120,10 @@ export function OldSecretSelectionScreen(): VNode { f().catch((e) => console.log(e)); }, []); - const currentVersion = - (reducer?.currentReducerState && - "recovery_document" in reducer.currentReducerState && - reducer.currentReducerState.recovery_document?.version) || - 0; - if (!reducer) { return <div>no reducer in context</div>; } + if ( !reducer.currentReducerState || reducer.currentReducerState.reducer_type !== "recovery" @@ -185,97 +131,178 @@ export function OldSecretSelectionScreen(): VNode { return <div>invalid state</div>; } - async function doSelectVersion(p: string, n: number): Promise<void> { - if (!reducer) return Promise.resolve(); - return reducer.runTransaction(async (tx) => { - await tx.transition("select_version", { - version: n, - provider_url: p, - }); - setSelectingVersion(false); - }); - } - - const provs = reducer.currentReducerState.authentication_providers ?? {}; - const recoveryDocument = reducer.currentReducerState.recovery_document; - - if (!recoveryDocument) { + if (manageProvider) { return ( - <ChooseAnotherProviderScreen - providers={provs} - selected="" - onChange={(newProv) => doSelectVersion(newProv, 0)} - /> + <AddingProviderScreen onCancel={async () => setManageProvider(false)} /> ); } - if (selectingVersion) { - return ( - <SelectOtherVersionProviderScreen - providers={provs} - provider={recoveryDocument.provider_url} - version={recoveryDocument.version} - onCancel={() => setSelectingVersion(false)} - onConfirm={doSelectVersion} - /> - ); + if ( + reducer.discoveryState.state === "none" || + reducer.discoveryState.state === "active" + ) { + // Can this even happen? + return <SecretSelectionScreenWaiting />; } - if (manageProvider) { + const policies = reducer.discoveryState.aggregatedPolicies ?? []; + + if (policies.length === 0) { return ( - <AddingProviderScreen onCancel={async () => setManageProvider(false)} /> + <AddingProviderScreen + onCancel={async () => setManageProvider(false)} + notifications={[ + { + message: "Secret not found", + type: "ERROR", + description: + "With the information you provided we could not found secret in any of the providers. You can try adding more providers if you think the data is correct.", + }, + ]} + /> ); } - const providerInfo = provs[ - recoveryDocument.provider_url - ] as AuthenticationProviderStatusOk; - return ( - <AnastasisClientFrame title="Recovery: Select secret"> - <div class="columns"> - <div class="column"> - <div class="box" style={{ border: "2px solid green" }}> - <h1 class="subtitle">{providerInfo.business_name}</h1> - <div class="block"> - {currentVersion === 0 ? ( - <p>Set to recover the latest version</p> - ) : ( - <p>Set to recover the version number {currentVersion}</p> - )} - </div> - <div class="buttons is-right"> - <button class="button" onClick={(e) => setSelectingVersion(true)}> - Change secret's version - </button> - </div> - </div> - </div> - <div class="column"> - <p> - Secret found, you can select another version or continue to the - challenges solving - </p> - <p class="block"> - <a onClick={() => setManageProvider(true)}> - Manage recovery providers - </a> - </p> - </div> - </div> - </AnastasisClientFrame> + <SecretSelectionScreenFound + policies={policies} + onNext={(version) => reducer.transition("select_version", version)} + onManageProvider={async () => setManageProvider(false)} + /> ); } +// export function OldSecretSelectionScreen(): VNode { +// const [selectingVersion, setSelectingVersion] = useState<boolean>(false); +// const reducer = useAnastasisContext(); +// const [manageProvider, setManageProvider] = useState(false); + +// useEffect(() => { +// async function f() { +// if (reducer) { +// await reducer.discoverStart(); +// } +// } +// f().catch((e) => console.log(e)); +// }, []); + +// const currentVersion = +// (reducer?.currentReducerState && +// "recovery_document" in reducer.currentReducerState && +// reducer.currentReducerState.recovery_document?.version) || +// 0; + +// if (!reducer) { +// return <div>no reducer in context</div>; +// } +// if ( +// !reducer.currentReducerState || +// reducer.currentReducerState.reducer_type !== "recovery" +// ) { +// return <div>invalid state</div>; +// } + +// async function doSelectVersion(p: string, n: number): Promise<void> { +// if (!reducer) return Promise.resolve(); +// return reducer.runTransaction(async (tx) => { +// await tx.transition("select_version", { +// version: n, +// provider_url: p, +// }); +// setSelectingVersion(false); +// }); +// } + +// const provs = reducer.currentReducerState.authentication_providers ?? {}; +// const recoveryDocument = reducer.currentReducerState.recovery_document; + +// if (!recoveryDocument) { +// return ( +// <ChooseAnotherProviderScreen +// providers={provs} +// selected="" +// onChange={(newProv) => doSelectVersion(newProv, 0)} +// /> +// ); +// } + +// if (selectingVersion) { +// return ( +// <SelectOtherVersionProviderScreen +// providers={provs} +// provider={recoveryDocument.provider_url} +// version={recoveryDocument.version} +// onCancel={() => setSelectingVersion(false)} +// onConfirm={doSelectVersion} +// /> +// ); +// } + +// if (manageProvider) { +// return ( +// <AddingProviderScreen onCancel={async () => setManageProvider(false)} /> +// ); +// } + +// const providerInfo = provs[ +// recoveryDocument.provider_url +// ] as AuthenticationProviderStatusOk; + +// return ( +// <AnastasisClientFrame title="Recovery: Select secret"> +// <div class="columns"> +// <div class="column"> +// <div class="box" style={{ border: "2px solid green" }}> +// <h1 class="subtitle">{providerInfo.business_name}</h1> +// <div class="block"> +// {currentVersion === 0 ? ( +// <p>Set to recover the latest version</p> +// ) : ( +// <p>Set to recover the version number {currentVersion}</p> +// )} +// </div> +// <div class="buttons is-right"> +// <button class="button" onClick={(e) => setSelectingVersion(true)}> +// Change secret's version +// </button> +// </div> +// </div> +// </div> +// <div class="column"> +// <p> +// Secret found, you can select another version or continue to the +// challenges solving +// </p> +// <p class="block"> +// <a onClick={() => setManageProvider(true)}> +// Manage recovery providers +// </a> +// </p> +// </div> +// </div> +// </AnastasisClientFrame> +// ); +// } + function ChooseAnotherProviderScreen({ - providers, - selected, onChange, }: { - selected: string; - providers: { [url: string]: AuthenticationProviderStatus }; onChange: (prov: string) => void; }): VNode { + const reducer = useAnastasisContext(); + + if (!reducer) { + return <div>no reducer in context</div>; + } + + if ( + !reducer.currentReducerState || + reducer.currentReducerState.reducer_type !== "recovery" + ) { + return <div>invalid state</div>; + } + const providers = reducer.currentReducerState.authentication_providers ?? {}; + return ( <AnastasisClientFrame hideNext="Recovery document not found" @@ -286,13 +313,9 @@ function ChooseAnotherProviderScreen({ <label class="label">Provider</label> <div class="control is-expanded has-icons-left"> <div class="select is-fullwidth"> - <select - onChange={(e) => onChange(e.currentTarget.value)} - value={selected} - > + <select onChange={(e) => onChange(e.currentTarget.value)} value=""> <option key="none" disabled selected value=""> - {" "} - Choose a provider{" "} + Choose a provider </option> {Object.keys(providers).map((url) => { const p = providers[url]; @@ -419,3 +442,11 @@ function SelectOtherVersionProviderScreen({ </AnastasisClientFrame> ); } + +function SecretSelectionScreenWaiting(): VNode { + return ( + <AnastasisClientFrame title="Recovery: Select secret"> + <div>loading secret versions</div> + </AnastasisClientFrame> + ); +} diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx index 23910565e..7f4d5aa18 100644 --- a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx @@ -40,7 +40,14 @@ export function SolveOverviewFeedbackDisplay(props: { message: `Message from provider`, description: ( <span> - To pay you can <a href={feedback.taler_pay_uri}>click here</a> + To pay you can{" "} + <a + href={feedback.taler_pay_uri} + target="_blank" + rel="noreferrer" + > + click here + </a> </span> ), }, @@ -65,8 +72,12 @@ export function SolveOverviewFeedbackDisplay(props: { notifications={[ { type: "ERROR", - message: `Server error: Code ${feedback.http_status}`, - description: feedback.error_response, + message: `Server error: response code ${feedback.http_status}`, + description: !feedback.error_response + ? undefined + : `More information: ${JSON.stringify( + feedback.error_response, + )}`, }, ]} /> @@ -77,8 +88,7 @@ export function SolveOverviewFeedbackDisplay(props: { notifications={[ { type: "ERROR", - message: `Message from provider`, - description: "There were to many failed attempts.", + message: "There were to many failed attempts.", }, ]} /> @@ -107,11 +117,56 @@ export function SolveOverviewFeedbackDisplay(props: { ]} /> ); - default: - console.warn( - `unknown challenge feedback status ${JSON.stringify(feedback)}`, + case ChallengeFeedbackStatus.CodeInFile: + return ( + <Notifications + notifications={[ + { + type: "INFO", + message: `Required TAN can be found in file "${feedback.filename}"`, + description: feedback.display_hint + ? `HINT: ${feedback.display_hint}` + : undefined, + }, + ]} + /> + ); + case ChallengeFeedbackStatus.CodeSent: + return ( + <Notifications + notifications={[ + { + type: "INFO", + message: `Code sent to address "${feedback.address_hint}"`, + description: feedback.display_hint + ? `HINT: ${feedback.display_hint}` + : undefined, + }, + ]} + /> + ); + case ChallengeFeedbackStatus.IncorrectAnswer: + return ( + <Notifications + notifications={[ + { + type: "ERROR", + message: `The answer is wrong.`, + }, + ]} + /> + ); + case ChallengeFeedbackStatus.Solved: + return ( + <Notifications + notifications={[ + { + type: "SUCCESS", + message: `This challenge is solved`, + }, + ]} + /> ); - return <div />; } } diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx index 038d5e897..182538775 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx @@ -20,7 +20,6 @@ */ import { - ChallengeFeedbackBankTransferRequired, ChallengeFeedbackStatus, ReducerState, } from "@gnu-taler/anastasis-core"; @@ -61,142 +60,190 @@ export const WithoutFeedback = createExample( }, ); -export const ServerFailureFeedback = createExample( - TestedComponent[type].solve, - { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [ - { - instructions: "does P equal NP?", - type: "question", - uuid: "ASDASDSAD!1", - }, - ], - policies: [], +const recovery_information = { + challenges: [ + { + instructions: "does P equal NP?", + type: "question", + uuid: "ASDASDSAD!1", }, - selected_challenge_uuid: "ASDASDSAD!1", - challenge_feedback: { - "ASDASDSAD!1": { - state: ChallengeFeedbackStatus.ServerFailure, - http_status: 500, - error_response: "Couldn't connect to mysql", - }, + ], + policies: [], +}; + +export const CodeInFileFeedback = createExample(TestedComponent[type].solve, { + ...reducerStatesExample.challengeSolving, + recovery_information, + selected_challenge_uuid: "ASDASDSAD!1", + challenge_feedback: { + "ASDASDSAD!1": { + state: ChallengeFeedbackStatus.CodeInFile, + filename: "asd", + display_hint: "hint", }, - } as ReducerState, -); + }, +} as ReducerState); -export const MessageRateLimitExceededFeedback = createExample( +export const CodeSentFeedback = createExample(TestedComponent[type].solve, { + ...reducerStatesExample.challengeSolving, + recovery_information, + selected_challenge_uuid: "ASDASDSAD!1", + challenge_feedback: { + "ASDASDSAD!1": { + state: ChallengeFeedbackStatus.CodeSent, + address_hint: "asdasd", + display_hint: "qweqweqw", + }, + }, +} as ReducerState); + +export const SolvedFeedback = createExample(TestedComponent[type].solve, { + ...reducerStatesExample.challengeSolving, + recovery_information, + selected_challenge_uuid: "ASDASDSAD!1", + challenge_feedback: { + "ASDASDSAD!1": { + state: ChallengeFeedbackStatus.Solved, + }, + }, +} as ReducerState); + +export const ServerFailureFeedback = createExample( TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [ - { - instructions: "does P equals NP?", - type: "question", - uuid: "ASDASDSAD!1", - }, - ], - policies: [], - }, + recovery_information, selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { "ASDASDSAD!1": { - state: ChallengeFeedbackStatus.RateLimitExceeded, + state: ChallengeFeedbackStatus.ServerFailure, + http_status: 500, }, }, } as ReducerState, ); -export const UnsupportedFeedback = createExample(TestedComponent[type].solve, { +export const TruthUnknownFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [ - { - instructions: "does P equals NP?", - type: "question", - uuid: "ASDASDSAD!1", - }, - ], - policies: [], - }, + recovery_information, selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { "ASDASDSAD!1": { - state: ChallengeFeedbackStatus.Unsupported, - http_status: 500, - unsupported_method: "Question", + state: ChallengeFeedbackStatus.TruthUnknown, }, }, } as ReducerState); -export const TruthUnknownFeedback = createExample(TestedComponent[type].solve, { +export const TalerPaymentFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [ - { - instructions: "does P equals NP?", - type: "question", - uuid: "ASDASDSAD!1", - }, - ], - policies: [], - }, + recovery_information, selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { "ASDASDSAD!1": { - state: ChallengeFeedbackStatus.TruthUnknown, + state: ChallengeFeedbackStatus.TalerPayment, + payment_secret: "secret", + provider: "asdasdas", + taler_pay_uri: "taler://pay/...", }, }, } as ReducerState); -const ibanFeedback: ChallengeFeedbackBankTransferRequired = { - state: ChallengeFeedbackStatus.IbanInstructions, - challenge_amount: "EUR:1", - target_iban: "DE12345789000", - target_business_name: "Data Loss Incorporated", - wire_transfer_subject: "Anastasis 987654321", - answer_code: 987654321, -}; - -export const AuthIbanFeedback = createExample(TestedComponent[type].solve, { +export const UnsupportedFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [ - { - instructions: "does P equals NP?", - type: "question", - uuid: "ASDASDSAD!1", - }, - ], - policies: [], - }, + recovery_information, selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { - "ASDASDSAD!1": ibanFeedback, + "ASDASDSAD!1": { + state: ChallengeFeedbackStatus.Unsupported, + unsupported_method: "method", + }, }, } as ReducerState); -export const PaymentFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [ - { - instructions: "does P equals NP?", - type: "question", - uuid: "ASDASDSAD!1", +export const RateLimitExceededFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information, + selected_challenge_uuid: "ASDASDSAD!1", + challenge_feedback: { + "ASDASDSAD!1": { + state: ChallengeFeedbackStatus.RateLimitExceeded, }, - ], - policies: [], - }, - selected_challenge_uuid: "ASDASDSAD!1", - challenge_feedback: { - "ASDASDSAD!1": { - state: ChallengeFeedbackStatus.TalerPayment, - taler_pay_uri: "taler://pay/...", - provider: "https://localhost:8080/", - payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG", }, - }, -} as ReducerState); + } as ReducerState, +); + +export const IbanInstructionsFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information, + selected_challenge_uuid: "ASDASDSAD!1", + challenge_feedback: { + "ASDASDSAD!1": { + state: ChallengeFeedbackStatus.IbanInstructions, + challenge_amount: "EUR:1", + target_iban: "DE12345789000", + target_business_name: "Data Loss Incorporated", + wire_transfer_subject: "Anastasis 987654321", + answer_code: 987654321, + }, + }, + } as ReducerState, +); + +export const IncorrectAnswerFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information, + selected_challenge_uuid: "ASDASDSAD!1", + challenge_feedback: { + "ASDASDSAD!1": { + state: ChallengeFeedbackStatus.IncorrectAnswer, + }, + }, + } as ReducerState, +); + +// export const AuthIbanFeedback = createExample(TestedComponent[type].solve, { +// ...reducerStatesExample.challengeSolving, +// recovery_information: { +// challenges: [ +// { +// instructions: "does P equals NP?", +// type: "question", +// uuid: "ASDASDSAD!1", +// }, +// ], +// policies: [], +// }, +// selected_challenge_uuid: "ASDASDSAD!1", +// challenge_feedback: { +// "ASDASDSAD!1": ibanFeedback, +// }, +// } as ReducerState); + +// export const PaymentFeedback = createExample(TestedComponent[type].solve, { +// ...reducerStatesExample.challengeSolving, +// recovery_information: { +// challenges: [ +// { +// instructions: "does P equals NP?", +// type: "question", +// uuid: "ASDASDSAD!1", +// }, +// ], +// policies: [], +// }, +// selected_challenge_uuid: "ASDASDSAD!1", +// challenge_feedback: { +// "ASDASDSAD!1": { +// state: ChallengeFeedbackStatus.TalerPayment, +// taler_pay_uri: "taler://pay/...", +// provider: "https://localhost:8080/", +// payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG", +// }, +// }, +// } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/index.storiesNo.tsx b/packages/anastasis-webui/src/pages/home/index.storiesNo.tsx index 31595111a..0dad73724 100644 --- a/packages/anastasis-webui/src/pages/home/index.storiesNo.tsx +++ b/packages/anastasis-webui/src/pages/home/index.storiesNo.tsx @@ -20,7 +20,7 @@ */ export * as AddingProviderScreen from "./AddingProviderScreen/stories.js"; -export * as algo from "./AttributeEntryScreen.stories.js"; +export * as AttributeEntryScreen from "./AttributeEntryScreen.stories.js"; export * as AuthenticationEditorScreen from "./AuthenticationEditorScreen.stories.js"; export * as authMethod_AuthMethodEmailSetup from "./authMethod/AuthMethodEmailSetup.stories.js"; diff --git a/packages/anastasis-webui/src/pages/home/index.tsx b/packages/anastasis-webui/src/pages/home/index.tsx index 25d9c63d9..44e065807 100644 --- a/packages/anastasis-webui/src/pages/home/index.tsx +++ b/packages/anastasis-webui/src/pages/home/index.tsx @@ -85,7 +85,7 @@ function ErrorBoundary(props: { children: ComponentChildren; }): VNode { const [error, resetError] = useErrorBoundary((error) => - console.log("got error", error), + console.log("ErrorBoundary got error", error), ); if (error) { return ( @@ -132,7 +132,7 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode { history.pushState({ id: nextId }, "unused", `#${nextId}`); } catch (e) { - console.log(e); + console.log("ERROR doNext ", e); } } diff --git a/packages/anastasis-webui/src/stories.tsx b/packages/anastasis-webui/src/stories.tsx index fb37cdfa5..7d22deece 100644 --- a/packages/anastasis-webui/src/stories.tsx +++ b/packages/anastasis-webui/src/stories.tsx @@ -38,7 +38,6 @@ function parseExampleImport( im: any, name?: string, ): ComponentItem { - console.log(im); const component = name || im.default.title; const order: number = im.default.args?.order || 0; return { @@ -372,9 +371,8 @@ function LiveReload({ port = 8002 }: { port?: number }): VNode { } function setupLiveReload(port: number, onReload: () => void): void { - const protocol = location.protocol === "https:" ? "wss:" : "ws:"; - const host = location.hostname; - const socketPath = `${protocol}//${host}:${port}/socket`; + const socketPath = `ws://localhost:8003/socket`; + // const socketPath = `${protocol}//${host}:${port}/socket`; const ws = new WebSocket(socketPath); ws.onmessage = (message) => { diff --git a/packages/anastasis-webui/src/utils/index.tsx b/packages/anastasis-webui/src/utils/index.tsx index 63bed9392..f87dd7f13 100644 --- a/packages/anastasis-webui/src/utils/index.tsx +++ b/packages/anastasis-webui/src/utils/index.tsx @@ -54,7 +54,7 @@ export function createExample<Props>( discoverMore: noop, discoverStart: noop, discoveryState: { - state: "none", + state: "finished", }, currentError: undefined, back: noop, @@ -204,14 +204,17 @@ const base = { } as AuthenticationProviderStatusOk, "http://localhost:8087/": { + status: "error", code: 8414, hint: "request to provider failed", } as AuthenticationProviderStatusError, "http://localhost:8088/": { + status: "error", code: 8414, hint: "request to provider failed", } as AuthenticationProviderStatusError, "http://localhost:8089/": { + status: "error", code: 8414, hint: "request to provider failed", } as AuthenticationProviderStatusError, diff --git a/packages/anastasis-webui/watch/serve.sh b/packages/anastasis-webui/watch/serve.sh index c395c6a64..f4e9595d5 100755 --- a/packages/anastasis-webui/watch/serve.sh +++ b/packages/anastasis-webui/watch/serve.sh @@ -1,4 +1,7 @@ #!/bin/bash +#clean up +rm /tmp/send_signal + socat TCP-LISTEN:8003,fork,reuseaddr,keepalive EXEC:"./watch/reply.sh" |