aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts')
-rw-r--r--packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts90
1 files changed, 60 insertions, 30 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts b/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts
index 61f4308f4..1b9cbe397 100644
--- a/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts
+++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts
@@ -14,21 +14,37 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { useState, useEffect, useCallback } from "preact/hooks";
-import { Props, State } from "./index.js";
-import { ExchangeEntryStatus, TalerCorebankApi, TalerExchangeApi, canonicalizeBaseUrl } from "@gnu-taler/taler-util";
+import { ExchangeEntryStatus, OperationFailWithBody, OperationOk, TalerExchangeApi, TalerExchangeHttpClient, canonicalizeBaseUrl, opKnownFailureWithBody } from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { BrowserHttpLib } from "@gnu-taler/web-util/browser";
+import { useCallback, useEffect, useState } from "preact/hooks";
import { useBackendContext } from "../../context/backend.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { RecursiveState } from "../../utils/index.js";
-import { HttpResponse, useApiContext } from "@gnu-taler/web-util/browser";
-import { alertFromError } from "../../context/alert.js";
import { withSafe } from "../../mui/handlers.js";
+import { RecursiveState } from "../../utils/index.js";
+import { CheckExchangeErrors, Props, State } from "./index.js";
+
+function urlFromInput(str: string): URL {
+ let result: URL;
+ try {
+ result = new URL(str)
+ } catch (original) {
+ try {
+ result = new URL(`https://${str}`)
+ } catch (e) {
+ throw original
+ }
+ }
+ if (!result.pathname.endsWith("/")) {
+ result.pathname = result.pathname + "/";
+ }
+ result.search = "";
+ result.hash = "";
+ return result;
+}
export function useComponentState({ onBack, currency, noDebounce }: Props): RecursiveState<State> {
- const [verified, setVerified] = useState<
- { url: string; config: { currency_specification: {currency: string}, version: string} } | undefined
- >(undefined);
+ const [verified, setVerified] = useState<string>();
const api = useBackendContext();
const hook = useAsyncAsHook(() =>
@@ -38,20 +54,30 @@ export function useComponentState({ onBack, currency, noDebounce }: Props): Recu
const used = walletExchanges.filter(e => e.exchangeEntryStatus === ExchangeEntryStatus.Used);
const preset = walletExchanges.filter(e => e.exchangeEntryStatus === ExchangeEntryStatus.Preset);
-
if (!verified) {
return (): State => {
- const { request } = useApiContext();
- const ccc = useCallback(async (str: string) => {
- const c = canonicalizeBaseUrl(str)
- const found = used.findIndex((e) => e.exchangeBaseUrl === c);
+ const checkExchangeBaseUrl_memo = useCallback(async function checkExchangeBaseUrl(str: string) {
+ const baseUrl = urlFromInput(str)
+ if (baseUrl.protocol !== "http:" && baseUrl.protocol !== "https:") {
+ return opKnownFailureWithBody<CheckExchangeErrors>("invalid-protocol", undefined)
+ }
+ const found = used.findIndex((e) => e.exchangeBaseUrl === baseUrl.href);
if (found !== -1) {
- throw Error("This exchange is already active")
+ return opKnownFailureWithBody<CheckExchangeErrors>("already-active", undefined);
}
- const result = await request<{ currency_specification: {currency: string}, version: string}>(c, "/keys")
- return result
+ const api = new TalerExchangeHttpClient(baseUrl.href, new BrowserHttpLib() as any);
+ const config = await api.getConfig()
+ if (!api.isCompatible(config.body.version)) {
+ return opKnownFailureWithBody<CheckExchangeErrors>("invalid-version", config.body.version)
+ }
+ if (currency !== undefined && currency !== config.body.currency) {
+ return opKnownFailureWithBody<CheckExchangeErrors>("invalid-currency", config.body.currency)
+ }
+ const keys = await api.getKeys()
+ return keys
}, [used])
- const { result, value: url, update, error: requestError } = useDebounce<HttpResponse<{ currency_specification: {currency: string}, version: string}, unknown>>(ccc, noDebounce ?? false)
+
+ const { result, value: url, loading, update, error: requestError } = useDebounce(checkExchangeBaseUrl_memo, noDebounce ?? false)
const [inputError, setInputError] = useState<string>()
return {
@@ -60,10 +86,11 @@ export function useComponentState({ onBack, currency, noDebounce }: Props): Recu
onCancel: onBack,
expectedCurrency: currency,
onAccept: async () => {
- if (!url || !result || !result.ok) return;
- setVerified({ url, config: result.data })
+ if (!result || result.type !== "ok") return;
+ setVerified(result.body.base_url)
},
result,
+ loading,
knownExchanges: preset.map(e => new URL(e.exchangeBaseUrl)),
url: {
value: url ?? "",
@@ -79,7 +106,7 @@ export function useComponentState({ onBack, currency, noDebounce }: Props): Recu
async function onConfirm() {
if (!verified) return;
await api.wallet.call(WalletApiOperation.AddExchange, {
- exchangeBaseUrl: canonicalizeBaseUrl(verified.url),
+ exchangeBaseUrl: canonicalizeBaseUrl(verified),
forceUpdate: true,
});
onBack();
@@ -90,7 +117,7 @@ export function useComponentState({ onBack, currency, noDebounce }: Props): Recu
error: undefined,
onCancel: onBack,
onConfirm,
- url: verified.url
+ url: verified
};
}
@@ -101,7 +128,7 @@ function useDebounce<T>(
disabled: boolean,
): {
loading: boolean;
- error?: string;
+ error?: Error;
value: string | undefined;
result: T | undefined;
update: (s: string) => void;
@@ -110,7 +137,7 @@ function useDebounce<T>(
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 [error, setError] = useState<Error | undefined>(undefined);
const [handler, setHandler] = useState<any | undefined>(undefined);
@@ -126,10 +153,13 @@ function useDebounce<T>(
setResult(result);
setError(undefined);
setLoading(false);
- } catch (e) {
- const errorMessage =
- e instanceof Error ? e.message : `unknown error: ${e}`;
- setError(errorMessage);
+ } catch (er) {
+ if (er instanceof Error) {
+ setError(er);
+ } else {
+ // @ts-expect-error cause still not in typescript
+ setError(new Error('unkown error on debounce', { cause: er }))
+ }
setLoading(false);
setResult(undefined);
}
@@ -143,7 +173,7 @@ function useDebounce<T>(
loading: loading,
result: result,
value: value,
- update: disabled ? onTrigger : setValue ,
+ update: disabled ? onTrigger : setValue,
};
}