gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated (66c3a47ec -> 5db4cb99e)


From: gnunet
Subject: [taler-wallet-core] branch master updated (66c3a47ec -> 5db4cb99e)
Date: Mon, 22 Apr 2024 17:18:46 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a change to branch master
in repository wallet-core.

    from 66c3a47ec taler-wallet-cli: default to sqlite3 db
     new 82ec30e81 refactor to keep the challenge status up to date
     new 594dd1fd4 fix #8396
     new 5db4cb99e fix #8393

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/challenger-ui/src/Routing.tsx             | 127 +++++++++++--
 .../CheckChallengeIsUpToDate.tsx}                  | 118 ++++++------
 packages/challenger-ui/src/hooks/session.ts        |  38 +++-
 .../challenger-ui/src/pages/AnswerChallenge.tsx    |  97 +++-------
 packages/challenger-ui/src/pages/AskChallenge.tsx  | 199 ++++++++-------------
 .../challenger-ui/src/pages/CallengeCompleted.tsx  |   3 +-
 packages/challenger-ui/src/pages/Setup.tsx         |   2 +-
 packages/taler-util/src/http-client/types.ts       |  19 +-
 packages/web-util/src/utils/http-impl.sw.ts        |   2 +
 9 files changed, 313 insertions(+), 292 deletions(-)
 rename packages/challenger-ui/src/{pages/StartChallenge.tsx => 
components/CheckChallengeIsUpToDate.tsx} (63%)

diff --git a/packages/challenger-ui/src/Routing.tsx 
b/packages/challenger-ui/src/Routing.tsx
index e1e9434e5..f1f4d82d2 100644
--- a/packages/challenger-ui/src/Routing.tsx
+++ b/packages/challenger-ui/src/Routing.tsx
@@ -15,6 +15,7 @@
  */
 
 import {
+  Loading,
   urlPattern,
   useCurrentLocation,
   useNavigationContext,
@@ -22,14 +23,15 @@ import {
 import { Fragment, VNode, h } from "preact";
 
 import { assertUnreachable } from "@gnu-taler/taler-util";
+import { CheckChallengeIsUpToDate } from 
"./components/CheckChallengeIsUpToDate.js";
+import { SessionId, useSessionState } from "./hooks/session.js";
 import { AnswerChallenge } from "./pages/AnswerChallenge.js";
 import { AskChallenge } from "./pages/AskChallenge.js";
+import { CallengeCompleted } from "./pages/CallengeCompleted.js";
 import { Frame } from "./pages/Frame.js";
 import { MissingParams } from "./pages/MissingParams.js";
 import { NonceNotFound } from "./pages/NonceNotFound.js";
-import { StartChallenge } from "./pages/StartChallenge.js";
 import { Setup } from "./pages/Setup.js";
-import { CallengeCompleted } from "./pages/CallengeCompleted.js";
 
 export function Routing(): VNode {
   // check session and defined if this is
@@ -42,6 +44,10 @@ export function Routing(): VNode {
 }
 
 const publicPages = {
+  noinfo: urlPattern<{ nonce: string }>(
+    /\/noinfo\/(?<nonce>[a-zA-Z0-9]+)/,
+    ({ nonce }) => `#/noinfo/${nonce}`,
+  ),
   authorize: urlPattern<{ nonce: string }>(
     /\/authorize\/(?<nonce>[a-zA-Z0-9]+)/,
     ({ nonce }) => `#/authorize/${nonce}`,
@@ -84,18 +90,22 @@ function safeToURL(s: string | undefined): URL | undefined {
 function PublicRounting(): VNode {
   const location = useCurrentLocation(publicPages);
   const { navigateTo } = useNavigationContext();
+  const { start } = useSessionState();
 
   if (location === undefined) {
     return <NonceNotFound />;
   }
 
   switch (location.name) {
+    case "noinfo": {
+      return <div>no info</div>;
+    }
     case "setup": {
       return (
         <Setup
           clientId={location.values.client}
           onCreated={(nonce) => {
-            navigateTo(publicPages.ask.url({ nonce }))
+            navigateTo(publicPages.ask.url({ nonce }));
             //response_type=code
             //client_id=1
             
//redirect_uri=http://exchange.taler.test:1180/kyc-proof/kyc-provider-wallet
@@ -107,7 +117,7 @@ function PublicRounting(): VNode {
     case "authorize": {
       const responseType = safeGetParam(location.params, "response_type");
       const clientId = safeGetParam(location.params, "client_id");
-      const redirectURI = safeToURL(
+      const redirectURL = safeToURL(
         safeGetParam(location.params, "redirect_uri"),
       );
       const state = safeGetParam(location.params, "state");
@@ -119,58 +129,137 @@ function PublicRounting(): VNode {
       if (
         !responseType ||
         !clientId ||
-        !redirectURI ||
+        !redirectURL ||
         !state ||
         responseType !== "code"
       ) {
         return <MissingParams />;
       }
+      const sessionId: SessionId = {
+        clientId,
+        redirectURL: redirectURL.href,
+        state,
+      };
       return (
-        <StartChallenge
+        <CheckChallengeIsUpToDate
+          sessionId={sessionId}
           nonce={location.values.nonce}
-          clientId={clientId}
-          redirectURL={redirectURI}
-          state={state}
-          onSendSuccesful={() => {
+          onNoInfo={() => {
+            navigateTo(
+              publicPages.noinfo.url({
+                nonce: location.values.nonce,
+              }),
+            );
+          }}
+          onCompleted={() => {
+            start(sessionId);
+            navigateTo(
+              publicPages.completed.url({
+                nonce: location.values.nonce,
+              }),
+            );
+          }}
+          onChangeLeft={() => {
+            start(sessionId);
             navigateTo(
               publicPages.ask.url({
                 nonce: location.values.nonce,
               }),
             );
           }}
-        />
+          onNoMoreChanges={() => {
+            start(sessionId);
+            navigateTo(
+              publicPages.ask.url({
+                nonce: location.values.nonce,
+              }),
+            );
+          }}
+        >
+          <Loading />
+        </CheckChallengeIsUpToDate>
       );
     }
     case "ask": {
       return (
-        <AskChallenge
+        <CheckChallengeIsUpToDate
           nonce={location.values.nonce}
-          onSendSuccesful={() => {
+          onNoInfo={() => {
             navigateTo(
-              publicPages.answer.url({
+              publicPages.noinfo.url({
                 nonce: location.values.nonce,
               }),
             );
           }}
-        />
+          onCompleted={() => {
+            navigateTo(
+              publicPages.completed.url({
+                nonce: location.values.nonce,
+              }),
+            );
+          }}
+        >
+          <AskChallenge
+            nonce={location.values.nonce}
+            routeSolveChallenge={publicPages.answer}
+            onSendSuccesful={() => {
+              navigateTo(
+                publicPages.answer.url({
+                  nonce: location.values.nonce,
+                }),
+              );
+            }}
+          />
+        </CheckChallengeIsUpToDate>
       );
     }
     case "answer": {
       return (
-        <AnswerChallenge
+        <CheckChallengeIsUpToDate
           nonce={location.values.nonce}
-          onComplete={() => {
+          onNoInfo={() => {
+            navigateTo(
+              publicPages.noinfo.url({
+                nonce: location.values.nonce,
+              }),
+            );
+          }}
+          onCompleted={() => {
             navigateTo(
               publicPages.completed.url({
                 nonce: location.values.nonce,
               }),
             );
           }}
-        />
+        >
+          <AnswerChallenge
+            nonce={location.values.nonce}
+            onComplete={() => {
+              navigateTo(
+                publicPages.completed.url({
+                  nonce: location.values.nonce,
+                }),
+              );
+            }}
+          />
+        </CheckChallengeIsUpToDate>
       );
     }
     case "completed": {
-      return <CallengeCompleted nonce={location.values.nonce} />;
+      return (
+        <CheckChallengeIsUpToDate
+          nonce={location.values.nonce}
+          onNoInfo={() => {
+            navigateTo(
+              publicPages.noinfo.url({
+                nonce: location.values.nonce,
+              }),
+            );
+          }}
+        >
+          <CallengeCompleted nonce={location.values.nonce} />
+        </CheckChallengeIsUpToDate>
+      );
     }
     default:
       assertUnreachable(location);
diff --git a/packages/challenger-ui/src/pages/StartChallenge.tsx 
b/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx
similarity index 63%
rename from packages/challenger-ui/src/pages/StartChallenge.tsx
rename to packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx
index 6cf982a3d..70e41bf1e 100644
--- a/packages/challenger-ui/src/pages/StartChallenge.tsx
+++ b/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx
@@ -13,78 +13,63 @@
  You should have received a copy of the GNU General Public License along with
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
+import {
+  HttpStatusCode,
+  TalerError,
+  assertUnreachable,
+} from "@gnu-taler/taler-util";
 import {
   Attention,
-  Button,
   Loading,
-  LocalNotificationBanner,
-  ShowInputErrorLabel,
-  useChallengerApiContext,
-  useLocalNotificationHandler,
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
-import { useEffect, useState } from "preact/hooks";
+import { ComponentChildren, Fragment, VNode, h } from "preact";
 import { useChallengeSession } from "../hooks/challenge.js";
-import {
-  ChallengerApi,
-  HttpStatusCode,
-  TalerError,
-  assertUnreachable,
-} from "@gnu-taler/taler-util";
-import { useSessionState } from "../hooks/session.js";
-
-type Form = {
-  email: string;
-};
-export const EMAIL_REGEX = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/;
+import { SessionId, useSessionState } from "../hooks/session.js";
 
-type Props = {
+interface Props {
   nonce: string;
-  clientId: string;
-  redirectURL: URL;
-  state: string;
-  onSendSuccesful: () => void;
-};
-
-
-export function StartChallenge({
+  children: ComponentChildren;
+  sessionId?: SessionId;
+  onCompleted?: () => void;
+  onChangeLeft?: () => void;
+  onNoMoreChanges?: () => void;
+  onNoInfo: () => void;
+}
+export function CheckChallengeIsUpToDate({
+  sessionId: sessionFromParam,
   nonce,
-  clientId,
-  redirectURL,
-  state,
-  onSendSuccesful,
+  children,
+  onCompleted,
+  onChangeLeft,
+  onNoMoreChanges,
+  onNoInfo,
 }: Props): VNode {
+  const { state, updateStatus } = useSessionState();
   const { i18n } = useTranslationContext();
-  const { start } = useSessionState();
 
-  const result = useChallengeSession(nonce, {
-    clientId,
-    redirectURL: redirectURL.href,
-    state,
-  });
+  const sessionId = sessionFromParam
+    ? sessionFromParam
+    : !state
+      ? undefined
+      : {
+          clientId: state.clientId,
+          redirectURL: state.redirectURL,
+          state: state.state,
+        };
 
-  const session =
-    result && !(result instanceof TalerError) && result.type === "ok"
-      ? result.body
-      : undefined;
-
-  useEffect(() => {
-    if (session) {
-      start({
-        clientId,
-        redirectURL: redirectURL.href,
-        state,
-      });
-      onSendSuccesful();
-    }
-  }, [session]);
+  const result = useChallengeSession(nonce, sessionId);
+  console.log("asd");
+  if (!sessionId) {
+    onNoInfo();
+    return <Loading />;
+  }
 
   if (!result) {
     return <Loading />;
   }
   if (result instanceof TalerError) {
-    return <div />;
+    return <pre>{JSON.stringify(result, undefined, 2)}</pre>;
   }
 
   if (result.type === "fail") {
@@ -126,13 +111,22 @@ export function StartChallenge({
     }
   }
 
-  return <Loading />;
-}
+  updateStatus(result.body);
+
+  if (onCompleted && "redirectURL" in result.body) {
+    onCompleted();
+    return <Loading />;
+  }
+
+  if (onNoMoreChanges && !result.body.changes_left) {
+    onNoMoreChanges();
+    return <Loading />;
+  }
+
+  if (onChangeLeft && !result.body.changes_left) {
+    onChangeLeft();
+    return <Loading />;
+  }
 
-export function undefinedIfEmpty<T extends object>(obj: T): T | undefined {
-  return Object.keys(obj).some(
-    (k) => (obj as Record<string, T>)[k] !== undefined,
-  )
-    ? obj
-    : undefined;
+  return <Fragment>{children}</Fragment>;
 }
diff --git a/packages/challenger-ui/src/hooks/session.ts 
b/packages/challenger-ui/src/hooks/session.ts
index 4bb1bfbc8..ed7ea8986 100644
--- a/packages/challenger-ui/src/hooks/session.ts
+++ b/packages/challenger-ui/src/hooks/session.ts
@@ -15,13 +15,14 @@
  */
 
 import {
+  ChallengerApi,
   Codec,
   buildCodecForObject,
   codecForBoolean,
+  codecForChallengeStatus,
   codecForNumber,
   codecForString,
   codecForStringURL,
-  codecForURL,
   codecOptional,
 } from "@gnu-taler/taler-util";
 import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
@@ -44,8 +45,8 @@ export type LastChallengeResponse = {
 };
 
 export type SessionState = SessionId & {
-  email: string | undefined;
   lastTry: LastChallengeResponse | undefined;
+  lastStatus: ChallengerApi.ChallengeStatus | undefined;
   completedURL: string | undefined;
 };
 export const codecForLastChallengeResponse = (): Codec<LastChallengeResponse> 
=>
@@ -61,15 +62,16 @@ export const codecForSessionState = (): Codec<SessionState> 
=>
     .property("redirectURL", codecForStringURL())
     .property("completedURL", codecOptional(codecForStringURL()))
     .property("state", codecForString())
+    .property("lastStatus", 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;
+  accepted(l: LastChallengeResponse): void;
   completed(e: URL): void;
+  updateStatus(s: ChallengerApi.ChallengeStatus): void;
 }
 
 const SESSION_STATE_KEY = buildStorageKey(
@@ -92,15 +94,14 @@ export function useSessionState(): SessionStateHandler {
         ...info,
         lastTry: undefined,
         completedURL: undefined,
-        email: undefined,
+        lastStatus: undefined,
       });
       cleanAllCache();
     },
-    accepted(email, lastTry) {
+    accepted(lastTry) {
       if (!state) return;
       update({
         ...state,
-        email,
         lastTry,
       });
     },
@@ -111,6 +112,29 @@ export function useSessionState(): SessionStateHandler {
         completedURL: url.href,
       });
     },
+    updateStatus(st: ChallengerApi.ChallengeStatus) {
+      if (!state) return;
+      if (!state.lastStatus) {
+        update({
+          ...state,
+          lastStatus: st,
+        });
+        return;
+      }
+      // current status
+      const ls = state.lastStatus;
+      if (
+        ls.changes_left !== st.changes_left ||
+        ls.fix_address !== st.fix_address ||
+        ls.last_address !== st.last_address
+      ) {
+        update({
+          ...state,
+          lastStatus: st,
+        });
+        return;
+      }
+    },
   };
 }
 
diff --git a/packages/challenger-ui/src/pages/AnswerChallenge.tsx 
b/packages/challenger-ui/src/pages/AnswerChallenge.tsx
index 69600e2ba..62b7e775d 100644
--- a/packages/challenger-ui/src/pages/AnswerChallenge.tsx
+++ b/packages/challenger-ui/src/pages/AnswerChallenge.tsx
@@ -19,6 +19,7 @@ import {
   assertUnreachable,
 } from "@gnu-taler/taler-util";
 import {
+  Attention,
   Button,
   LocalNotificationBanner,
   ShowInputErrorLabel,
@@ -37,13 +38,7 @@ type Props = {
   onComplete: () => void;
 };
 
-function SolveChallengeForm({
-  nonce,
-  onComplete,
-}: {
-  nonce: string;
-  onComplete: () => void;
-}): VNode {
+export function AnswerChallenge({ nonce, onComplete }: Props): VNode {
   const { lib } = useChallengerApiContext();
   const { i18n } = useTranslationContext();
   const { state, accepted, completed } = useSessionState();
@@ -55,19 +50,25 @@ function SolveChallengeForm({
     pin: !pin ? i18n.str`Can't be empty` : undefined,
   });
 
+  const lastEmail = !state
+    ? undefined
+    : !state.lastStatus
+      ? undefined
+      : ((state.lastStatus.last_address as any)["email"] as string);
+
   const onSendAgain =
-    !state || state.email === undefined
+    !state || lastEmail === undefined
       ? undefined
       : withErrorHandler(
           async () => {
-            if (!state?.email) return;
-            return await lib.bank.challenge(nonce, { email: state.email });
+            if (!lastEmail) return;
+            return await lib.bank.challenge(nonce, { email: lastEmail });
           },
           (ok) => {
-            if ('redirectURL' in ok.body) {
-              completed(ok.body.redirectURL)
+            if ("redirectURL" in ok.body) {
+              completed(ok.body.redirectURL);
             } else {
-              accepted(state.email!, {
+              accepted({
                 attemptsLeft: ok.body.attempts_left,
                 nextSend: ok.body.next_tx_time,
                 transmitted: ok.body.transmitted,
@@ -99,7 +100,7 @@ function SolveChallengeForm({
             return lib.bank.solve(nonce, { pin: pin! });
           },
           (ok) => {
-            completed(ok.body.redirectURL as URL)
+            completed(ok.body.redirectURL as URL);
             onComplete();
           },
           (fail) => {
@@ -146,14 +147,16 @@ function SolveChallengeForm({
           <p class="mt-2 text-lg leading-8 text-gray-600">
             {state.lastTry.transmitted ? (
               <i18n.Translate>
-                A TAN was sent to your address &quot;{state.email}&quot;.
+                A TAN was sent to your address &quot;{lastEmail}&quot;.
               </i18n.Translate>
             ) : (
-              <i18n.Translate>
-                We recently already sent a TAN to your address &quot;
-                {state.email}&quot;. A new TAN will not be transmitted again
-                before {state.lastTry.nextSend}.
-              </i18n.Translate>
+              <Attention title={i18n.str`Resend failed`} type="warning">
+                <i18n.Translate>
+                  We recently already sent a TAN to your address &quot;
+                  {lastEmail}&quot;. A new TAN will not be transmitted again
+                  before &quot;{state.lastTry.nextSend}&quot;.
+                </i18n.Translate>
+              </Attention>
             )}
           </p>
           {!lastTryError ? undefined : (
@@ -233,60 +236,6 @@ function SolveChallengeForm({
   );
 }
 
-export function AnswerChallenge({ nonce, onComplete }: Props): VNode {
-  const { i18n } = useTranslationContext();
-
-  // const result = useChallengeSession(nonce, clientId, redirectURI, state);
-
-  // if (!result) {
-  //   return <Loading />;
-  // }
-  // if (result instanceof TalerError) {
-  //   return <div />;
-  // }
-
-  // if (result.type === "fail") {
-  //   switch (result.case) {
-  //     case HttpStatusCode.BadRequest: {
-  //       return (
-  //         <Attention type="danger" title={i18n.str`Bad request`}>
-  //           <i18n.Translate>
-  //             Could not start the challenge, check configuration.
-  //           </i18n.Translate>
-  //         </Attention>
-  //       );
-  //     }
-  //     case HttpStatusCode.NotFound: {
-  //       return (
-  //         <Attention type="danger" title={i18n.str`Not found`}>
-  //           <i18n.Translate>Nonce not found</i18n.Translate>
-  //         </Attention>
-  //       );
-  //     }
-  //     case HttpStatusCode.NotAcceptable: {
-  //       return (
-  //         <Attention type="danger" title={i18n.str`Not acceptable`}>
-  //           <i18n.Translate>
-  //             Server has wrong template configuration
-  //           </i18n.Translate>
-  //         </Attention>
-  //       );
-  //     }
-  //     case HttpStatusCode.InternalServerError: {
-  //       return (
-  //         <Attention type="danger" title={i18n.str`Internal error`}>
-  //           <i18n.Translate>Check logs</i18n.Translate>
-  //         </Attention>
-  //       );
-  //     }
-  //     default:
-  //       assertUnreachable(result);
-  //   }
-  // }
-
-  return <SolveChallengeForm nonce={nonce} onComplete={onComplete} />;
-}
-
 export function undefinedIfEmpty<T extends object>(obj: T): T | undefined {
   return Object.keys(obj).some(
     (k) => (obj as Record<string, T>)[k] !== undefined,
diff --git a/packages/challenger-ui/src/pages/AskChallenge.tsx 
b/packages/challenger-ui/src/pages/AskChallenge.tsx
index 71f45dde3..76fe6f00a 100644
--- a/packages/challenger-ui/src/pages/AskChallenge.tsx
+++ b/packages/challenger-ui/src/pages/AskChallenge.tsx
@@ -13,11 +13,12 @@
  You should have received a copy of the GNU General Public License along with
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
+import { HttpStatusCode } from "@gnu-taler/taler-util";
 import {
   Attention,
   Button,
-  Loading,
   LocalNotificationBanner,
+  RouteDefinition,
   ShowInputErrorLabel,
   useChallengerApiContext,
   useLocalNotificationHandler,
@@ -25,13 +26,6 @@ import {
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
 import { useState } from "preact/hooks";
-import { useChallengeSession } from "../hooks/challenge.js";
-import {
-  ChallengerApi,
-  HttpStatusCode,
-  TalerError,
-  assertUnreachable,
-} from "@gnu-taler/taler-util";
 import { useSessionState } from "../hooks/session.js";
 
 type Form = {
@@ -42,33 +36,27 @@ export const EMAIL_REGEX = 
/^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/;
 type Props = {
   nonce: string;
   onSendSuccesful: () => void;
+  routeSolveChallenge: RouteDefinition<{ nonce: string }>;
 };
 
-function ChallengeForm({
+export function AskChallenge({
   nonce,
-  status,
   onSendSuccesful,
-}: {
-  nonce: string;
-  status: ChallengerApi.ChallengeStatus;
-  onSendSuccesful: () => void;
-}): VNode {
-  const prevEmail = !status.last_address
-    ? undefined
-    : ((status.last_address as any)["email"] as string);
-  const regexEmail = !status.restrictions
-    ? undefined
-    : ((status.restrictions as any)["email"] as {
-        regex?: string;
-        hint?: string;
-        hint_i18n?: string;
-      });
+  routeSolveChallenge,
+}: Props): VNode {
+  const { state, accepted, completed } = useSessionState();
+  const status = state?.lastStatus;
+  const prevEmail =
+    !status || !status.last_address ? undefined : status.last_address["email"];
+  const regexEmail =
+    !status || !status.restrictions
+      ? undefined
+      : status.restrictions["email"];
 
   const { lib } = useChallengerApiContext();
   const { i18n } = useTranslationContext();
   const [notification, withErrorHandler] = useLocalNotificationHandler();
   const [email, setEmail] = useState<string | undefined>(prevEmail);
-  const { accepted, completed } = useSessionState();
   const [repeat, setRepeat] = useState<string | undefined>();
 
   const errors = undefinedIfEmpty({
@@ -82,43 +70,51 @@ function ChallengeForm({
           : undefined
         : !EMAIL_REGEX.test(email)
           ? i18n.str`invalid email`
-          : email !== repeat
-            ? i18n.str`emails don't match`
-            : undefined,
-    repeat: !repeat ? i18n.str`required` : undefined,
+          : undefined,
+    repeat: !repeat
+      ? i18n.str`required`
+      : email !== repeat
+        ? i18n.str`emails doesn't match`
+        : undefined,
   });
 
-  const onSend = withErrorHandler(
-    async () => {
-      return lib.bank.challenge(nonce, { email: email! });
-    },
-    (ok) => {
-      if ("redirectURL" in ok.body) {
-        completed(ok.body.redirectURL);
-      } else {
-        accepted(email!, {
-          attemptsLeft: ok.body.attempts_left,
-          nextSend: ok.body.next_tx_time,
-          transmitted: ok.body.transmitted,
-        });
-      }
-      onSendSuccesful();
-    },
-    (fail) => {
-      switch (fail.case) {
-        case HttpStatusCode.BadRequest:
-          return i18n.str``;
-        case HttpStatusCode.NotFound:
-          return i18n.str``;
-        case HttpStatusCode.NotAcceptable:
-          return i18n.str``;
-        case HttpStatusCode.TooManyRequests:
-          return i18n.str``;
-        case HttpStatusCode.InternalServerError:
-          return i18n.str``;
-      }
-    },
-  );
+  const onSend = errors
+    ? undefined
+    : withErrorHandler(
+        async () => {
+          return lib.bank.challenge(nonce, { email: email! });
+        },
+        (ok) => {
+          if ("redirectURL" in ok.body) {
+            completed(ok.body.redirectURL);
+          } else {
+            accepted({
+              attemptsLeft: ok.body.attempts_left,
+              nextSend: ok.body.next_tx_time,
+              transmitted: ok.body.transmitted,
+            });
+          }
+          onSendSuccesful();
+        },
+        (fail) => {
+          switch (fail.case) {
+            case HttpStatusCode.BadRequest:
+              return i18n.str``;
+            case HttpStatusCode.NotFound:
+              return i18n.str``;
+            case HttpStatusCode.NotAcceptable:
+              return i18n.str``;
+            case HttpStatusCode.TooManyRequests:
+              return i18n.str``;
+            case HttpStatusCode.InternalServerError:
+              return i18n.str``;
+          }
+        },
+      );
+
+  if (!status) {
+    return <div>no status loaded</div>;
+  }
 
   return (
     <Fragment>
@@ -136,6 +132,19 @@ function ChallengeForm({
             </i18n.Translate>
           </p>
         </div>
+        {state.lastTry && (
+          <Fragment>
+            <Attention title={i18n.str`A code has been sent to ${prevEmail}`}>
+              <i18n.Translate>
+                You can change the destination or{" "}
+                <a href={routeSolveChallenge.url({ nonce })}>
+                  <i18n.Translate>complete the challenge here</i18n.Translate>
+                </a>
+                .
+              </i18n.Translate>
+            </Attention>
+          </Fragment>
+        )}
         <form
           method="POST"
           class="mx-auto mt-16 max-w-xl sm:mt-20"
@@ -191,6 +200,10 @@ function ChallengeForm({
                   autocomplete="email"
                   class="block w-full rounded-md border-0 px-3.5 py-2 
text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 
placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 
sm:text-sm sm:leading-6"
                 />
+                <ShowInputErrorLabel
+                  message={errors?.repeat}
+                  isDirty={repeat !== undefined}
+                />
               </div>
             </div>
 
@@ -205,7 +218,8 @@ function ChallengeForm({
           <div class="mt-10">
             <Button
               type="submit"
-              class="block w-full rounded-md bg-indigo-600 px-3.5 py-2.5 
text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+              disabled={!onSend}
+              class="block w-full disabled:bg-gray-300 rounded-md 
bg-indigo-600 px-3.5 py-2.5 text-center text-sm font-semibold text-white 
shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
               handler={onSend}
             >
               <i18n.Translate>Send email</i18n.Translate>
@@ -217,67 +231,6 @@ function ChallengeForm({
   );
 }
 
-export function AskChallenge({ nonce, onSendSuccesful }: Props): VNode {
-  const { i18n } = useTranslationContext();
-  const { state } = useSessionState();
-
-  const result = useChallengeSession(nonce, state);
-
-  if (!result) {
-    return <Loading />;
-  }
-  if (result instanceof TalerError) {
-    return <div />;
-  }
-
-  if (result.type === "fail") {
-    switch (result.case) {
-      case HttpStatusCode.BadRequest: {
-        return (
-          <Attention type="danger" title={i18n.str`Bad request`}>
-            <i18n.Translate>
-              Could not start the challenge, check configuration.
-            </i18n.Translate>
-          </Attention>
-        );
-      }
-      case HttpStatusCode.NotFound: {
-        return (
-          <Attention type="danger" title={i18n.str`Not found`}>
-            <i18n.Translate>Nonce not found</i18n.Translate>
-          </Attention>
-        );
-      }
-      case HttpStatusCode.NotAcceptable: {
-        return (
-          <Attention type="danger" title={i18n.str`Not acceptable`}>
-            <i18n.Translate>
-              Server has wrong template configuration
-            </i18n.Translate>
-          </Attention>
-        );
-      }
-      case HttpStatusCode.InternalServerError: {
-        return (
-          <Attention type="danger" title={i18n.str`Internal error`}>
-            <i18n.Translate>Check logs</i18n.Translate>
-          </Attention>
-        );
-      }
-      default:
-        assertUnreachable(result);
-    }
-  }
-
-  return (
-    <ChallengeForm
-      nonce={nonce}
-      status={result.body}
-      onSendSuccesful={onSendSuccesful}
-    />
-  );
-}
-
 export function undefinedIfEmpty<T extends object>(obj: T): T | undefined {
   return Object.keys(obj).some(
     (k) => (obj as Record<string, T>)[k] !== undefined,
diff --git a/packages/challenger-ui/src/pages/CallengeCompleted.tsx 
b/packages/challenger-ui/src/pages/CallengeCompleted.tsx
index 24a05c67f..f8cd7ce60 100644
--- a/packages/challenger-ui/src/pages/CallengeCompleted.tsx
+++ b/packages/challenger-ui/src/pages/CallengeCompleted.tsx
@@ -19,7 +19,8 @@ type Props = {
   nonce: string;
 }
 export function CallengeCompleted({nonce}:Props):VNode {
+  
   return <div>
-    completed
+    completed {nonce}
   </div>
 }
\ No newline at end of file
diff --git a/packages/challenger-ui/src/pages/Setup.tsx 
b/packages/challenger-ui/src/pages/Setup.tsx
index 5d23045cf..400c9b780 100644
--- a/packages/challenger-ui/src/pages/Setup.tsx
+++ b/packages/challenger-ui/src/pages/Setup.tsx
@@ -70,7 +70,7 @@ export function Setup({ clientId, onCreated }: Props): VNode {
           <div class="mt-10">
             <Button
               type="submit"
-              class="block w-full rounded-md bg-indigo-600 px-3.5 py-2.5 
text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 
focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 
focus-visible:outline-indigo-600"
+              class="block w-full disabled:bg-gray-300 rounded-md 
bg-indigo-600 px-3.5 py-2.5 text-center text-sm font-semibold text-white 
shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
               handler={onStart}
             >
               <i18n.Translate>Start</i18n.Translate>
diff --git a/packages/taler-util/src/http-client/types.ts 
b/packages/taler-util/src/http-client/types.ts
index 329acd484..a2f709769 100644
--- a/packages/taler-util/src/http-client/types.ts
+++ b/packages/taler-util/src/http-client/types.ts
@@ -18,6 +18,7 @@ import {
 import { PaytoString, codecForPaytoString } from "../payto.js";
 import {
   AmountString,
+  InternationalizedString,
   codecForInternationalizedString,
   codecForLocation,
 } from "../taler-types.js";
@@ -223,7 +224,9 @@ export function createRFC8959AccessToken(token: string): 
AccessToken {
  * @param clientSecret
  * @returns
  */
-export function createClientSecretAccessToken(clientSecret: string): 
AccessToken {
+export function createClientSecretAccessToken(
+  clientSecret: string,
+): AccessToken {
   return clientSecret as AccessToken;
 }
 
@@ -1516,9 +1519,9 @@ export const codecForChallengeSetupResponse =
 export const codecForChallengeStatus =
   (): Codec<ChallengerApi.ChallengeStatus> =>
     buildCodecForObject<ChallengerApi.ChallengeStatus>()
-      .property("restrictions", codecForAny())
+      .property("restrictions", codecOptional(codecForMap(codecForAny())))
       .property("fix_address", codecForBoolean())
-      .property("last_address", codecForAny())
+      .property("last_address", codecOptional(codecForMap(codecForAny())))
       .property("changes_left", codecForNumber())
       .build("ChallengerApi.ChallengeStatus");
 export const codecForChallengeCreateResponse =
@@ -5279,6 +5282,12 @@ export namespace ChallengerApi {
     nonce: string;
   }
 
+  export interface Restriction {
+    regex?: string;
+    hint?: string;
+    hint_i18n?: InternationalizedString;
+  }
+
   export interface ChallengeStatus {
     // Object; map of keys (names of the fields of the address
     // to be entered by the user) to objects with a "regex" (string)
@@ -5288,7 +5297,7 @@ export namespace ChallengerApi {
     // by the user does not match the regex. Keys that are not mapped
     // to such an object have no restriction on the value provided by
     // the user.  See "ADDRESS_RESTRICTIONS" in the challenger configuration.
-    restrictions: Object;
+    restrictions: Record<string, Restriction> | undefined;
 
     // indicates if the given address cannot be changed anymore, the
     // form should be read-only if set to true.
@@ -5296,7 +5305,7 @@ export namespace ChallengerApi {
 
     // form values from the previous submission if available, details depend
     // on the ADDRESS_TYPE, should be used to pre-populate the form
-    last_address: Object;
+    last_address: Record<string, string> | undefined;
 
     // number of times the address can still be changed, may or may not be
     // shown to the user
diff --git a/packages/web-util/src/utils/http-impl.sw.ts 
b/packages/web-util/src/utils/http-impl.sw.ts
index 3c4b8b587..9c820bb4b 100644
--- a/packages/web-util/src/utils/http-impl.sw.ts
+++ b/packages/web-util/src/utils/http-impl.sw.ts
@@ -59,6 +59,7 @@ export class BrowserFetchHttpLib implements 
HttpRequestLibrary {
     const requestTimeout =
       options?.timeout ?? 
Duration.fromMilliseconds(DEFAULT_REQUEST_TIMEOUT_MS);
     const requestCancel = options?.cancellationToken;
+    const requestRedirect = options?.redirect;
 
     const parsedUrl = new URL(requestUrl);
     if (this.throttlingEnabled && this.throttle.applyThrottle(requestUrl)) {
@@ -115,6 +116,7 @@ export class BrowserFetchHttpLib implements 
HttpRequestLibrary {
         body: myBody,
         method: requestMethod,
         signal: controller.signal,
+        redirect: requestRedirect
       });
 
       if (timeoutId) {

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]