diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts')
-rw-r--r-- | packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts | 90 |
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, }; } |