From b6e774585d32017e5f1ceeeb2b2e2a5e350354d3 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Sun, 28 May 2017 23:15:41 +0200 Subject: move webex specific things in their own directory --- src/pages/confirm-create-reserve.tsx | 639 ----------------------------------- 1 file changed, 639 deletions(-) delete mode 100644 src/pages/confirm-create-reserve.tsx (limited to 'src/pages/confirm-create-reserve.tsx') diff --git a/src/pages/confirm-create-reserve.tsx b/src/pages/confirm-create-reserve.tsx deleted file mode 100644 index 2f341bb4e..000000000 --- a/src/pages/confirm-create-reserve.tsx +++ /dev/null @@ -1,639 +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 {amountToPretty, canonicalizeBaseUrl} from "../helpers"; -import { - AmountJson, CreateReserveResponse, - ReserveCreationInfo, Amounts, - Denomination, DenominationRecord, CurrencyRecord -} from "../types"; -import {getReserveCreationInfo, getCurrency, getExchangeInfo} from "../wxApi"; -import {ImplicitStateComponent, StateHolder} from "../components"; -import * as i18n from "../i18n"; -import * as React from "react"; -import * as ReactDOM from "react-dom"; -import URI = require("urijs"); -import * as moment from "moment"; - - -function delay(delayMs: number, value: T): Promise { - return new Promise((resolve, reject) => { - setTimeout(() => resolve(value), delayMs); - }); -} - -class EventTrigger { - triggerResolve: any; - 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 CollapsibleState { - collapsed: boolean; -} - -interface CollapsibleProps { - initiallyCollapsed: boolean; - title: string; -} - -class Collapsible extends React.Component { - constructor(props: CollapsibleProps) { - super(props); - this.state = { collapsed: props.initiallyCollapsed }; - } - render() { - const doOpen = (e: any) => { - this.setState({collapsed: false}) - e.preventDefault() - }; - const doClose = (e: any) => { - this.setState({collapsed: true}) - e.preventDefault(); - }; - if (this.state.collapsed) { - return

{this.props.title}

; - } - return ( -
-

{this.props.title}

- {this.props.children} -
- ); - } -} - -function renderAuditorDetails(rci: ReserveCreationInfo|null) { - if (!rci) { - return ( -

- Details will be displayed when a valid exchange provider URL is entered. -

- ); - } - if (rci.exchangeInfo.auditors.length == 0) { - return ( -

- The exchange is not audited by any auditors. -

- ); - } - return ( -
- {rci.exchangeInfo.auditors.map(a => ( -

Auditor {a.url}

- ))} -
- ); -} - -function renderReserveCreationDetails(rci: ReserveCreationInfo|null) { - if (!rci) { - return ( -

- Details will be displayed when a valid exchange provider URL is entered. -

- ); - } - - let denoms = rci.selectedDenoms; - - let countByPub: {[s: string]: number} = {}; - let uniq: DenominationRecord[] = []; - - denoms.forEach((x: DenominationRecord) => { - let c = countByPub[x.denomPub] || 0; - if (c == 0) { - uniq.push(x); - } - c += 1; - countByPub[x.denomPub] = c; - }); - - function row(denom: DenominationRecord) { - return ( - - {countByPub[denom.denomPub] + "x"} - {amountToPretty(denom.value)} - {amountToPretty(denom.feeWithdraw)} - {amountToPretty(denom.feeRefresh)} - {amountToPretty(denom.feeDeposit)} - - ); - } - - function wireFee(s: string) { - return [ - - - Wire Method {s} - - - Applies Until - Wire Fee - Closing Fee - - , - - {rci!.wireFees.feesForType[s].map(f => ( - - {moment.unix(f.endStamp).format("llll")} - {amountToPretty(f.wireFee)} - {amountToPretty(f.closingFee)} - - ))} - - ]; - } - - let withdrawFeeStr = amountToPretty(rci.withdrawFee); - let overheadStr = amountToPretty(rci.overhead); - - return ( -
-

Overview

-

{i18n.str`Withdrawal fees: ${withdrawFeeStr}`}

-

{i18n.str`Rounding loss: ${overheadStr}`}

-

{i18n.str`Earliest expiration (for deposit): ${moment.unix(rci.earliestDepositExpiration).fromNow()}`}

-

Coin Fees

- - - - - - - - - - - - {uniq.map(row)} - -
{i18n.str`# Coins`}{i18n.str`Value`}{i18n.str`Withdraw Fee`}{i18n.str`Refresh Fee`}{i18n.str`Deposit Fee`}
-

Wire Fees

- - {Object.keys(rci.wireFees.feesForType).map(wireFee)} -
-
- ); -} - - -function getSuggestedExchange(currency: string): Promise { - // TODO: make this request go to the wallet backend - // Right now, this is a stub. - const defaultExchange: {[s: string]: string} = { - "KUDOS": "https://exchange.demo.taler.net", - "PUDOS": "https://exchange.test.taler.net", - }; - - let exchange = defaultExchange[currency]; - - if (!exchange) { - exchange = "" - } - - return Promise.resolve(exchange); -} - - -function WithdrawFee(props: {reserveCreationInfo: ReserveCreationInfo|null}): JSX.Element { - if (props.reserveCreationInfo) { - let {overhead, withdrawFee} = props.reserveCreationInfo; - let totalCost = Amounts.add(overhead, withdrawFee).amount; - return

{i18n.str`Withdraw fees:`} {amountToPretty(totalCost)}

; - } - return

; -} - - -interface ExchangeSelectionProps { - suggestedExchangeUrl: string; - amount: AmountJson; - callback_url: string; - wt_types: string[]; - currencyRecord: CurrencyRecord|null; -} - -interface ManualSelectionProps { - onSelect(url: string): void; - initialUrl: string; -} - -class ManualSelection extends ImplicitStateComponent { - url: StateHolder = this.makeState(""); - errorMessage: StateHolder = this.makeState(null); - isOkay: StateHolder = this.makeState(false); - updateEvent = new EventTrigger(); - constructor(p: ManualSelectionProps) { - super(p); - this.url(p.initialUrl); - this.update(); - } - render() { - return ( -

-
- - this.onUrlChanged((e.target as HTMLInputElement).value)} /> -
-
- - {this.errorMessage()} -
-
- ); - } - - async update() { - this.errorMessage(null); - this.isOkay(false); - if (!this.url()) { - return; - } - let parsedUrl = new URI(this.url()!); - if (parsedUrl.is("relative")) { - this.errorMessage(i18n.str`Error: URL may not be relative`); - this.isOkay(false); - return; - } - try { - let url = canonicalizeBaseUrl(this.url()!); - let r = await getExchangeInfo(url) - console.log("getExchangeInfo returned") - this.isOkay(true); - } catch (e) { - console.log("got error", e); - if (e.hasOwnProperty("httpStatus")) { - this.errorMessage(`Error: request failed with status ${e.httpStatus}`); - } else if (e.hasOwnProperty("errorResponse")) { - let resp = e.errorResponse; - this.errorMessage(`Error: ${resp.error} (${resp.hint})`); - } else { - this.errorMessage("invalid exchange URL"); - } - } - } - - async onUrlChanged(s: string) { - this.url(s); - this.errorMessage(null); - this.isOkay(false); - this.updateEvent.trigger(); - let waited = await this.updateEvent.wait(200); - if (waited) { - // Run the actual update if nobody else preempted us. - this.update(); - } - } -} - - -class ExchangeSelection extends ImplicitStateComponent { - statusString: StateHolder = this.makeState(null); - reserveCreationInfo: StateHolder = this.makeState( - null); - url: StateHolder = this.makeState(null); - - selectingExchange: StateHolder = this.makeState(false); - - constructor(props: ExchangeSelectionProps) { - super(props); - let prefilledExchangesUrls = []; - if (props.currencyRecord) { - let 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() { - let rci = this.reserveCreationInfo(); - if (rci) { - let 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 - {" "} - {amountToPretty(totalCost)} - {" "} - in fees. - - {trustMessage} -
- ); - } - if (this.url() && !this.statusString()) { - let shortName = new URI(this.url()!).host(); - return ( - - Waiting for a response from - {" "} - {shortName} - - ); - } - if (this.statusString()) { - return ( -

- {i18n.str`A problem occured, see below. ${this.statusString()}`} -

- ); - } - return ( -

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

- ); - } - - renderConfirm() { - return ( -
- {this.renderFeeStatus()} - - { " " } - -
- - {renderReserveCreationDetails(this.reserveCreationInfo())} - - - {renderAuditorDetails(this.reserveCreationInfo())} - -
- ); - } - - select(url: string) { - this.reserveCreationInfo(null); - this.url(url); - this.selectingExchange(false); - this.forceReserveUpdate(); - } - - renderSelect() { - let exchanges = (this.props.currencyRecord && this.props.currencyRecord.exchanges) || []; - console.log(exchanges); - return ( -
- 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 => ( - - ))} -
- )} - -

Manual Selection

- this.select(url)} /> -
- ); - } - - render(): JSX.Element { - return ( -
- - {"You are about to withdraw "} - {amountToPretty(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); - } - - /** - * Do an update of the reserve creation info, without any debouncing. - */ - async forceReserveUpdate() { - this.reserveCreationInfo(null); - try { - let url = canonicalizeBaseUrl(this.url()!); - let 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); - if (e.hasOwnProperty("httpStatus")) { - this.statusString(`Error: request failed with status ${e.httpStatus}`); - } else if (e.hasOwnProperty("errorResponse")) { - let resp = e.errorResponse; - this.statusString(`Error: ${resp.error} (${resp.hint})`); - } - } - } - - confirmReserveImpl(rci: ReserveCreationInfo, - exchange: string, - amount: AmountJson, - callback_url: string) { - const d = {exchange: canonicalizeBaseUrl(exchange), amount}; - const cb = (rawResp: any) => { - if (!rawResp) { - throw Error("empty response"); - } - // FIXME: filter out types that bank/exchange don't have in common - let wireDetails = rci.wireInfo; - let filteredWireDetails: any = {}; - for (let wireType in wireDetails) { - if (this.props.wt_types.findIndex((x) => x.toLowerCase() == wireType.toLowerCase()) < 0) { - continue; - } - let obj = Object.assign({}, wireDetails[wireType]); - // The bank doesn't need to know about fees - delete obj.fees; - // Consequently the bank can't verify signatures anyway, so - // we delete this extra data, to make the request URL shorter. - delete obj.salt; - delete obj.sig; - filteredWireDetails[wireType] = obj; - } - if (!rawResp.error) { - const resp = CreateReserveResponse.checked(rawResp); - let q: {[name: string]: string|number} = { - wire_details: JSON.stringify(filteredWireDetails), - exchange: resp.exchange, - reserve_pub: resp.reservePub, - amount_value: amount.value, - amount_fraction: amount.fraction, - amount_currency: amount.currency, - }; - let 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}).`); - } - }; - chrome.runtime.sendMessage({type: 'create-reserve', detail: d}, cb); - } - - renderStatus(): any { - if (this.statusString()) { - return

{this.statusString()}

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

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

; - } - return ""; - } -} - -export 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; - const bank_url = query.bank_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 suggestedExchangeUrl = query.suggested_exchange_url; - let currencyRecord = await getCurrency(amount.currency); - - let args = { - wt_types, - suggestedExchangeUrl, - callback_url, - amount, - currencyRecord, - }; - - 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.message}"`, e); - } -} - -document.addEventListener("DOMContentLoaded", () => { - main(); -}); -- cgit v1.2.3