From caa9a22d6970df331eebed032b9a9673d4217fc6 Mon Sep 17 00:00:00 2001
From: Sebastian
Date: Mon, 6 Dec 2021 15:27:20 -0300
Subject: [PATCH] check timeout when doing a query to /keys to add an exchange
---
.../src/utils/index.ts | 25 +++-
.../src/wallet/ExchangeAddPage.tsx | 11 +-
.../src/wallet/ExchangeAddSetUrl.stories.tsx | 40 +++---
.../src/wallet/ExchangeSetUrl.tsx | 118 +++++++++++-------
4 files changed, 130 insertions(+), 64 deletions(-)
diff --git a/packages/taler-wallet-webextension/src/utils/index.ts b/packages/taler-wallet-webextension/src/utils/index.ts
index 8eb89d58f..88f9bc4b3 100644
--- a/packages/taler-wallet-webextension/src/utils/index.ts
+++ b/packages/taler-wallet-webextension/src/utils/index.ts
@@ -43,14 +43,37 @@ export async function queryToSlashConfig(
.then(getJsonIfOk);
}
+function timeout(ms: number, promise: Promise): Promise {
+ return new Promise((resolve, reject) => {
+ const timer = setTimeout(() => {
+ reject(new Error(`Timeout: the query took longer than ${Math.floor(ms / 1000)} secs`))
+ }, ms)
+
+ promise
+ .then(value => {
+ clearTimeout(timer)
+ resolve(value)
+ })
+ .catch(reason => {
+ clearTimeout(timer)
+ reject(reason)
+ })
+ })
+}
+
export async function queryToSlashKeys(
url: string,
): Promise {
- return fetch(new URL("keys", url).href)
+ const endpoint = new URL("keys", url)
+ endpoint.searchParams.set("cacheBreaker", new Date().getTime() + "");
+
+ const query = fetch(endpoint.href)
.catch(() => {
throw new Error(`Network error`);
})
.then(getJsonIfOk);
+
+ return timeout(3000, query)
}
export function buildTermsOfServiceState(tos: GetExchangeTosResult): TermsState {
diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx b/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx
index 0c8336e69..6dbdf4c30 100644
--- a/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx
@@ -47,8 +47,15 @@ export function ExchangeAddPage({ onBack }: Props): VNode {
return (
queryToSlashKeys(url)}
+ onVerify={async (url) => {
+ const found =
+ knownExchanges.findIndex((e) => e.exchangeBaseUrl === url) !== -1;
+
+ if (found) {
+ throw Error("This exchange is already known");
+ }
+ return queryToSlashKeys(url);
+ }}
onConfirm={(url) =>
queryToSlashKeys(url)
.then((config) => {
diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeAddSetUrl.stories.tsx b/packages/taler-wallet-webextension/src/wallet/ExchangeAddSetUrl.stories.tsx
index 9ea800fe4..6f0a58729 100644
--- a/packages/taler-wallet-webextension/src/wallet/ExchangeAddSetUrl.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ExchangeAddSetUrl.stories.tsx
@@ -36,33 +36,39 @@ export default {
export const ExpectedUSD = createExample(TestedComponent, {
expectedCurrency: "USD",
onVerify: queryToSlashKeys,
- knownExchanges: [],
});
export const ExpectedKUDOS = createExample(TestedComponent, {
expectedCurrency: "KUDOS",
onVerify: queryToSlashKeys,
- knownExchanges: [],
});
export const InitialState = createExample(TestedComponent, {
onVerify: queryToSlashKeys,
- knownExchanges: [],
});
-export const WithDemoAsKnownExchange = createExample(TestedComponent, {
- knownExchanges: [
- {
- currency: "TESTKUDOS",
- exchangeBaseUrl: "https://exchange.demo.taler.net/",
- tos: {
- currentVersion: "1",
- acceptedVersion: "1",
- content: "content of tos",
- contentType: "text/plain",
- },
- paytoUris: [],
+const knownExchanges = [
+ {
+ currency: "TESTKUDOS",
+ exchangeBaseUrl: "https://exchange.demo.taler.net/",
+ tos: {
+ currentVersion: "1",
+ acceptedVersion: "1",
+ content: "content of tos",
+ contentType: "text/plain",
},
- ],
- onVerify: queryToSlashKeys,
+ paytoUris: [],
+ },
+];
+
+export const WithDemoAsKnownExchange = createExample(TestedComponent, {
+ onVerify: async (url) => {
+ const found =
+ knownExchanges.findIndex((e) => e.exchangeBaseUrl === url) !== -1;
+
+ if (found) {
+ throw Error("This exchange is already known");
+ }
+ return queryToSlashKeys(url);
+ },
});
diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx b/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx
index e87a8894f..d529d162b 100644
--- a/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx
@@ -17,52 +17,75 @@ import {
export interface Props {
initialValue?: string;
expectedCurrency?: string;
- knownExchanges: ExchangeListItem[];
onCancel: () => void;
onVerify: (s: string) => Promise;
onConfirm: (url: string) => Promise;
withError?: string;
}
+function useEndpointStatus(
+ endpoint: string,
+ onVerify: (e: string) => Promise,
+): {
+ loading: boolean;
+ error?: string;
+ endpoint: string;
+ result: T | undefined;
+ updateEndpoint: (s: string) => void;
+} {
+ const [value, setValue] = useState(endpoint);
+ const [dirty, setDirty] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [result, setResult] = useState(undefined);
+ const [error, setError] = useState(undefined);
+
+ const [handler, setHandler] = useState(undefined);
+
+ useEffect(() => {
+ if (!value) return;
+ window.clearTimeout(handler);
+ const h = window.setTimeout(async () => {
+ setDirty(true);
+ setLoading(true);
+ try {
+ const url = canonicalizeBaseUrl(value);
+ const result = await onVerify(url);
+ setResult(result);
+ setError(undefined);
+ setLoading(false);
+ } catch (e) {
+ const errorMessage =
+ e instanceof Error ? e.message : `unknown error: ${e}`;
+ setError(errorMessage);
+ setLoading(false);
+ setResult(undefined);
+ }
+ }, 500);
+ setHandler(h);
+ }, [value]);
+
+ return {
+ error: dirty ? error : undefined,
+ loading: loading,
+ result: result,
+ endpoint: value,
+ updateEndpoint: setValue,
+ };
+}
+
export function ExchangeSetUrlPage({
initialValue,
- knownExchanges,
expectedCurrency,
onCancel,
onVerify,
onConfirm,
- withError,
}: Props) {
- const [value, setValue] = useState(initialValue || "");
- const [dirty, setDirty] = useState(false);
- const [result, setResult] = useState(
- undefined,
- );
- const [error, setError] = useState(withError);
+ const { loading, result, endpoint, updateEndpoint, error } =
+ useEndpointStatus(initialValue ?? "", onVerify);
- useEffect(() => {
- try {
- const url = canonicalizeBaseUrl(value);
-
- const found =
- knownExchanges.findIndex((e) => e.exchangeBaseUrl === url) !== -1;
-
- if (found) {
- setError("This exchange is already known");
- return;
- }
- onVerify(url)
- .then((r) => {
- setResult(r);
- })
- .catch(() => {
- setResult(undefined);
- });
- setDirty(true);
- } catch {
- setResult(undefined);
- }
- }, [value]);
+ const [confirmationError, setConfirmationError] = useState<
+ string | undefined
+ >(undefined);
return (
@@ -72,21 +95,32 @@ export function ExchangeSetUrlPage({
) : (
Add exchange for {expectedCurrency}
)}
+ {result && expectedCurrency && expectedCurrency !== result.currency && (
+
+ This exchange doesn't match the expected currency{" "}
+ {expectedCurrency}
+
+ )}
+
-
+
setValue(e.currentTarget.value)}
+ value={endpoint}
+ onInput={(e) => updateEndpoint(e.currentTarget.value)}
/>
- {result && (
+ {loading &&
loading...
}
+ {result && !loading && (
@@ -100,12 +134,6 @@ export function ExchangeSetUrlPage({
)}
- {result && expectedCurrency && expectedCurrency !== result.currency && (
-
- This exchange doesn't match the expected currency{" "}
- {expectedCurrency}
-
- )}