From defbf625bdef0f8a666b72b8ce99de5e01af6b91 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 29 Aug 2019 23:12:55 +0200 Subject: url-based pay/withdraw, use react hooks --- src/webex/pages/confirm-create-reserve.tsx | 526 ----------------------------- 1 file changed, 526 deletions(-) delete mode 100644 src/webex/pages/confirm-create-reserve.tsx (limited to 'src/webex/pages/confirm-create-reserve.tsx') diff --git a/src/webex/pages/confirm-create-reserve.tsx b/src/webex/pages/confirm-create-reserve.tsx deleted file mode 100644 index 2d4f41dfe..000000000 --- a/src/webex/pages/confirm-create-reserve.tsx +++ /dev/null @@ -1,526 +0,0 @@ -/* - This file is part of TALER - (C) 2015-2016 GNUnet e.V. - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - */ - - -/** - * Page shown to the user to confirm creation - * of a reserve, usually requested by the bank. - * - * @author Florian Dold - */ - -import { canonicalizeBaseUrl } from "../../helpers"; -import * as i18n from "../../i18n"; - -import { AmountJson } from "../../amounts"; -import * as Amounts from "../../amounts"; - -import { - CurrencyRecord, -} from "../../dbTypes"; -import { - CreateReserveResponse, - ReserveCreationInfo, -} from "../../walletTypes"; - -import { ImplicitStateComponent, StateHolder } from "../components"; -import { - WalletApiError, - createReserve, - getCurrency, - getExchangeInfo, - getReserveCreationInfo, -} from "../wxApi"; - -import { - WithdrawDetailView, - renderAmount, -} from "../renderHtml"; - -import * as React from "react"; -import * as ReactDOM from "react-dom"; -import URI = require("urijs"); - - -function delay(delayMs: number, value: T): Promise { - return new Promise((resolve, reject) => { - setTimeout(() => resolve(value), delayMs); - }); -} - -class EventTrigger { - private triggerResolve: any; - private triggerPromise: Promise; - - constructor() { - this.reset(); - } - - private reset() { - this.triggerPromise = new Promise((resolve, reject) => { - this.triggerResolve = resolve; - }); - } - - trigger() { - this.triggerResolve(false); - this.reset(); - } - - async wait(delayMs: number): Promise { - return await Promise.race([this.triggerPromise, delay(delayMs, true)]); - } -} - - -interface ExchangeSelectionProps { - suggestedExchangeUrl: string; - amount: AmountJson; - callback_url: string; - wt_types: string[]; - currencyRecord: CurrencyRecord|null; - sender_wire: string | undefined; -} - -interface ManualSelectionProps { - onSelect(url: string): void; - initialUrl: string; -} - -class ManualSelection extends ImplicitStateComponent { - private url: StateHolder = this.makeState(""); - private errorMessage: StateHolder = this.makeState(null); - private isOkay: StateHolder = this.makeState(false); - private updateEvent = new EventTrigger(); - constructor(p: ManualSelectionProps) { - super(p); - this.url(p.initialUrl); - this.update(); - } - render() { - return ( -
-
- - this.onUrlChanged((e.target as HTMLInputElement).value)} - onChange={(e) => this.onUrlChanged((e.target as HTMLInputElement).value)} /> -
-
- - - {this.errorMessage()} -
-
- ); - } - - async update() { - this.errorMessage(null); - this.isOkay(false); - if (!this.url()) { - return; - } - const parsedUrl = new URI(this.url()!); - if (parsedUrl.is("relative")) { - this.errorMessage(i18n.str`Error: URL may not be relative`); - this.isOkay(false); - return; - } - try { - const url = canonicalizeBaseUrl(this.url()!); - await getExchangeInfo(url); - console.log("getExchangeInfo returned"); - this.isOkay(true); - } catch (e) { - if (!(e instanceof WalletApiError)) { - // maybe it's something more serious, don't handle here! - throw e; - } - console.log(`got error "${e.message} "with detail`, e.detail); - this.errorMessage(i18n.str`Invalid exchange URL (${e.message})`); - } - } - - async onUrlChanged(s: string) { - this.url(s); - this.errorMessage(null); - this.isOkay(false); - this.updateEvent.trigger(); - const waited = await this.updateEvent.wait(200); - if (waited) { - // Run the actual update if nobody else preempted us. - this.update(); - } - } -} - - -class ExchangeSelection extends ImplicitStateComponent { - private statusString: StateHolder = this.makeState(null); - private reserveCreationInfo: StateHolder = this.makeState( - null); - private url: StateHolder = this.makeState(null); - - private selectingExchange: StateHolder = this.makeState(false); - - constructor(props: ExchangeSelectionProps) { - super(props); - const prefilledExchangesUrls = []; - if (props.currencyRecord) { - const exchanges = props.currencyRecord.exchanges.map((x) => x.baseUrl); - prefilledExchangesUrls.push(...exchanges); - } - if (props.suggestedExchangeUrl) { - prefilledExchangesUrls.push(props.suggestedExchangeUrl); - } - if (prefilledExchangesUrls.length !== 0) { - this.url(prefilledExchangesUrls[0]); - this.forceReserveUpdate(); - } else { - this.selectingExchange(true); - } - } - - renderFeeStatus() { - const rci = this.reserveCreationInfo(); - if (rci) { - const totalCost = Amounts.add(rci.overhead, rci.withdrawFee).amount; - let trustMessage; - if (rci.isTrusted) { - trustMessage = ( - - The exchange is trusted by the wallet. - - ); - } else if (rci.isAudited) { - trustMessage = ( - - The exchange is audited by a trusted auditor. - - ); - } else { - trustMessage = ( - - Warning: The exchange is neither directly trusted nor audited by a trusted auditor. - If you withdraw from this exchange, it will be trusted in the future. - - ); - } - return ( -
- - Using exchange provider {this.url()}. - The exchange provider will charge - {" "}{renderAmount(totalCost)}{" "} - in fees. - - {trustMessage} -
- ); - } - if (this.url() && !this.statusString()) { - const shortName = new URI(this.url()!).host(); - return ( - - Waiting for a response from - - {shortName} - - ); - } - if (this.statusString()) { - return ( -

- {this.statusString()} -

- ); - } - return ( -

- {i18n.str`Information about fees will be available when an exchange provider is selected.`} -

- ); - } - - renderUpdateStatus() { - const rci = this.reserveCreationInfo(); - if (!rci) { - return null; - } - if (!rci.versionMatch) { - return null; - } - if (rci.versionMatch.compatible) { - return null; - } - if (rci.versionMatch.currentCmp === -1) { - return ( -

- - Your wallet (protocol version {rci.walletVersion}) might be outdated. - The exchange has a higher, incompatible - protocol version ({rci.exchangeVersion}). - -

- ); - } - if (rci.versionMatch.currentCmp === 1) { - return ( -

- - The chosen exchange (protocol version {rci.exchangeVersion} might be outdated. - The exchange has a lower, incompatible - protocol version than your wallet (protocol version {rci.walletVersion}). - -

- ); - } - throw Error("not reached"); - } - - renderConfirm() { - return ( -
- {this.renderFeeStatus()} -

- - { " " } - -

- {this.renderUpdateStatus()} - -
- ); - } - - select(url: string) { - this.reserveCreationInfo(null); - this.url(url); - this.selectingExchange(false); - this.forceReserveUpdate(); - } - - renderSelect() { - const exchanges = (this.props.currencyRecord && this.props.currencyRecord.exchanges) || []; - console.log(exchanges); - return ( -
- {i18n.str`Please select an exchange. You can review the details before after your selection.`} - - {this.props.suggestedExchangeUrl && ( -
-

Bank Suggestion

- -
- )} - - {exchanges.length > 0 && ( -
-

Known Exchanges

- {exchanges.map((e) => ( - - ))} -
- )} - -

i18n.str`Manual Selection`

- this.select(url)} /> -
- ); - } - - render(): JSX.Element { - return ( -
- - You are about to withdraw - {" "}{renderAmount(this.props.amount)}{" "} - from your bank account into your wallet. - - {this.selectingExchange() ? this.renderSelect() : this.renderConfirm()} -
- ); - } - - - confirmReserve() { - this.confirmReserveImpl(this.reserveCreationInfo()!, - this.url()!, - this.props.amount, - this.props.callback_url, - this.props.sender_wire); - } - - /** - * Do an update of the reserve creation info, without any debouncing. - */ - async forceReserveUpdate() { - this.reserveCreationInfo(null); - try { - const url = canonicalizeBaseUrl(this.url()!); - const r = await getReserveCreationInfo(url, - this.props.amount); - console.log("get exchange info resolved"); - this.reserveCreationInfo(r); - console.dir(r); - } catch (e) { - console.log("get exchange info rejected", e); - this.statusString(`Error: ${e.message}`); - // Re-try every 5 seconds as long as there is a problem - setTimeout(() => this.statusString() ? this.forceReserveUpdate() : undefined, 5000); - } - } - - async confirmReserveImpl(rci: ReserveCreationInfo, - exchange: string, - amount: AmountJson, - callback_url: string, - sender_wire: string | undefined) { - const rawResp = await createReserve({ - amount, - exchange: canonicalizeBaseUrl(exchange), - senderWire: sender_wire, - }); - if (!rawResp) { - throw Error("empty response"); - } - // FIXME: filter out types that bank/exchange don't have in common - const exchangeWireAccounts = []; - - for (let acct of rci.exchangeWireAccounts) { - const payto = new URI(acct); - if (payto.scheme() != "payto") { - console.warn("unknown wire account URI scheme", acct); - continue; - } - if (this.props.wt_types.includes(payto.authority())) { - exchangeWireAccounts.push(acct); - } - } - - const chosenAcct = exchangeWireAccounts[0]; - - if (!chosenAcct) { - throw Error("no exchange account matches the bank's supported types"); - } - - if (!rawResp.error) { - const resp = CreateReserveResponse.checked(rawResp); - const q: {[name: string]: string|number} = { - amount_currency: amount.currency, - amount_fraction: amount.fraction, - amount_value: amount.value, - exchange: resp.exchange, - exchange_wire_details: chosenAcct, - reserve_pub: resp.reservePub, - }; - const url = new URI(callback_url).addQuery(q); - if (!url.is("absolute")) { - throw Error("callback url is not absolute"); - } - console.log("going to", url.href()); - document.location.href = url.href(); - } else { - this.statusString( - i18n.str`Oops, something went wrong. The wallet responded with error status (${rawResp.error}).`); - } - } - - renderStatus(): any { - if (this.statusString()) { - return

{this.statusString()}

; - } else if (!this.reserveCreationInfo()) { - return

{i18n.str`Checking URL, please wait ...`}

; - } - return ""; - } -} - -async function main() { - try { - const url = new URI(document.location.href); - const query: any = URI.parseQuery(url.query()); - let amount; - try { - amount = AmountJson.checked(JSON.parse(query.amount)); - } catch (e) { - throw Error(i18n.str`Can't parse amount: ${e.message}`); - } - const callback_url = query.callback_url; - let wt_types; - try { - wt_types = JSON.parse(query.wt_types); - } catch (e) { - throw Error(i18n.str`Can't parse wire_types: ${e.message}`); - } - - let sender_wire; - if (query.sender_wire) { - let senderWireUri = new URI(query.sender_wire); - if (senderWireUri.scheme() != "payto") { - throw Error("sender wire info must be a payto URI"); - } - sender_wire = query.sender_wire; - } - - const suggestedExchangeUrl = query.suggested_exchange_url; - const currencyRecord = await getCurrency(amount.currency); - - const args = { - amount, - callback_url, - currencyRecord, - sender_wire, - suggestedExchangeUrl, - wt_types, - }; - - ReactDOM.render(, document.getElementById( - "exchange-selection")!); - - } catch (e) { - // TODO: provide more context information, maybe factor it out into a - // TODO:generic error reporting function or component. - document.body.innerText = i18n.str`Fatal error: "${e.message}".`; - console.error("got error", e); - } -} - -document.addEventListener("DOMContentLoaded", () => { - main(); -}); -- cgit v1.2.3