aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-12-06 15:27:20 -0300
committerSebastian <sebasjm@gmail.com>2021-12-06 15:27:25 -0300
commitcaa9a22d6970df331eebed032b9a9673d4217fc6 (patch)
treeec28516bb7330b3c843c595d9b3580ec76f7f0a0 /packages
parentce3ffbcd81b67c4a8e869b3392e6fdce44888300 (diff)
check timeout when doing a query to /keys to add an exchange
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-wallet-webextension/src/utils/index.ts25
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ExchangeAddPage.tsx11
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ExchangeAddSetUrl.stories.tsx40
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx118
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<T>(
.then(getJsonIfOk);
}
+function timeout<T>(ms: number, promise: Promise<T>): Promise<T> {
+ 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<T>(
url: string,
): Promise<T> {
- 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 (
<ExchangeSetUrlPage
onCancel={onBack}
- knownExchanges={knownExchanges}
- onVerify={(url) => 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<TalerConfigResponse>(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<TalerConfigResponse | undefined>;
onConfirm: (url: string) => Promise<string | undefined>;
withError?: string;
}
+function useEndpointStatus<T>(
+ endpoint: string,
+ onVerify: (e: string) => Promise<T>,
+): {
+ loading: boolean;
+ error?: string;
+ endpoint: string;
+ result: T | undefined;
+ updateEndpoint: (s: string) => void;
+} {
+ const [value, setValue] = useState<string>(endpoint);
+ const [dirty, setDirty] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [result, setResult] = useState<T | undefined>(undefined);
+ const [error, setError] = useState<string | undefined>(undefined);
+
+ const [handler, setHandler] = useState<number | undefined>(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<string>(initialValue || "");
- const [dirty, setDirty] = useState(false);
- const [result, setResult] = useState<TalerConfigResponse | undefined>(
- undefined,
- );
- const [error, setError] = useState<string | undefined>(withError);
-
- useEffect(() => {
- try {
- const url = canonicalizeBaseUrl(value);
+ const { loading, result, endpoint, updateEndpoint, error } =
+ useEndpointStatus(initialValue ?? "", onVerify);
- 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 (
<Fragment>
@@ -72,21 +95,32 @@ export function ExchangeSetUrlPage({
) : (
<h2>Add exchange for {expectedCurrency}</h2>
)}
+ {result && expectedCurrency && expectedCurrency !== result.currency && (
+ <WarningBox>
+ This exchange doesn't match the expected currency{" "}
+ <b>{expectedCurrency}</b>
+ </WarningBox>
+ )}
<ErrorMessage
title={error && "Unable to add this exchange"}
description={error}
/>
+ <ErrorMessage
+ title={confirmationError && "Unable to add this exchange"}
+ description={confirmationError}
+ />
<p>
- <Input invalid={dirty && !!error}>
+ <Input invalid={!!error}>
<label>URL</label>
<input
type="text"
placeholder="https://"
- value={value}
- onInput={(e) => setValue(e.currentTarget.value)}
+ value={endpoint}
+ onInput={(e) => updateEndpoint(e.currentTarget.value)}
/>
</Input>
- {result && (
+ {loading && <div>loading... </div>}
+ {result && !loading && (
<Fragment>
<Input>
<label>Version</label>
@@ -100,12 +134,6 @@ export function ExchangeSetUrlPage({
)}
</p>
</section>
- {result && expectedCurrency && expectedCurrency !== result.currency && (
- <WarningBox>
- This exchange doesn't match the expected currency{" "}
- <b>{expectedCurrency}</b>
- </WarningBox>
- )}
<footer>
<Button onClick={onCancel}>
<i18n.Translate>Cancel</i18n.Translate>
@@ -118,8 +146,10 @@ export function ExchangeSetUrlPage({
expectedCurrency !== result.currency)
}
onClick={() => {
- const url = canonicalizeBaseUrl(value);
- return onConfirm(url).then((r) => (r ? setError(r) : undefined));
+ const url = canonicalizeBaseUrl(endpoint);
+ return onConfirm(url).then((r) =>
+ r ? setConfirmationError(r) : undefined,
+ );
}}
>
<i18n.Translate>Next</i18n.Translate>