check timeout when doing a query to /keys to add an exchange

This commit is contained in:
Sebastian 2021-12-06 15:27:20 -03:00
parent ce3ffbcd81
commit caa9a22d69
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
4 changed files with 130 additions and 64 deletions

View File

@ -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 {

View File

@ -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) => {

View File

@ -36,22 +36,18 @@ 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: [
const knownExchanges = [
{
currency: "TESTKUDOS",
exchangeBaseUrl: "https://exchange.demo.taler.net/",
@ -63,6 +59,16 @@ export const WithDemoAsKnownExchange = createExample(TestedComponent, {
},
paytoUris: [],
},
],
onVerify: queryToSlashKeys,
];
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);
},
});

View File

@ -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);
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 (
<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>