aboutsummaryrefslogtreecommitdiff
path: root/pages/confirm-create-reserve.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'pages/confirm-create-reserve.tsx')
-rw-r--r--pages/confirm-create-reserve.tsx395
1 files changed, 0 insertions, 395 deletions
diff --git a/pages/confirm-create-reserve.tsx b/pages/confirm-create-reserve.tsx
deleted file mode 100644
index 833bfed27..000000000
--- a/pages/confirm-create-reserve.tsx
+++ /dev/null
@@ -1,395 +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 <http://www.gnu.org/licenses/>
- */
-
-
-/**
- * Page shown to the user to confirm creation
- * of a reserve, usually requested by the bank.
- *
- * @author Florian Dold
- */
-
-import {amountToPretty, canonicalizeBaseUrl} from "../lib/wallet/helpers";
-import {AmountJson, CreateReserveResponse} from "../lib/wallet/types";
-import {ReserveCreationInfo, Amounts} from "../lib/wallet/types";
-import {Denomination} from "../lib/wallet/types";
-import {getReserveCreationInfo} from "../lib/wallet/wxApi";
-import {ImplicitStateComponent, StateHolder} from "../lib/components";
-
-"use strict";
-
-
-function delay<T>(delayMs: number, value: T): Promise<T> {
- return new Promise<T>((resolve, reject) => {
- setTimeout(() => resolve(value), delayMs);
- });
-}
-
-class EventTrigger {
- triggerResolve: any;
- triggerPromise: Promise<boolean>;
-
- constructor() {
- this.reset();
- }
-
- private reset() {
- this.triggerPromise = new Promise<boolean>((resolve, reject) => {
- this.triggerResolve = resolve;
- });
- }
-
- trigger() {
- this.triggerResolve(false);
- this.reset();
- }
-
- async wait(delayMs: number): Promise<boolean> {
- return await Promise.race([this.triggerPromise, delay(delayMs, true)]);
- }
-}
-
-
-function renderReserveCreationDetails(rci: ReserveCreationInfo|null) {
- if (!rci) {
- return <p>
- Details will be displayed when a valid exchange provider URL is entered.</p>
- }
-
- let denoms = rci.selectedDenoms;
-
- let countByPub: {[s: string]: number} = {};
- let uniq: Denomination[] = [];
-
- denoms.forEach((x: Denomination) => {
- let c = countByPub[x.denom_pub] || 0;
- if (c == 0) {
- uniq.push(x);
- }
- c += 1;
- countByPub[x.denom_pub] = c;
- });
-
- function row(denom: Denomination) {
- return (
- <tr>
- <td>{countByPub[denom.denom_pub] + "x"}</td>
- <td>{amountToPretty(denom.value)}</td>
- <td>{amountToPretty(denom.fee_withdraw)}</td>
- <td>{amountToPretty(denom.fee_refresh)}</td>
- <td>{amountToPretty(denom.fee_deposit)}</td>
- </tr>
- );
- }
-
- let withdrawFeeStr = amountToPretty(rci.withdrawFee);
- let overheadStr = amountToPretty(rci.overhead);
-
- return (
- <div>
- <p>{`Withdrawal fees: ${withdrawFeeStr}`}</p>
- <p>{`Rounding loss: ${overheadStr}`}</p>
- <table>
- <thead>
- <th># Coins</th>
- <th>Value</th>
- <th>Withdraw Fee</th>
- <th>Refresh Fee</th>
- <th>Deposit fee</th>
- </thead>
- <tbody>
- {uniq.map(row)}
- </tbody>
- </table>
- </div>
- );
-}
-
-
-function getSuggestedExchange(currency: string): Promise<string> {
- // 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 <p>Withdraw fees: {amountToPretty(totalCost)}</p>;
- }
- return <p />;
-}
-
-
-interface ExchangeSelectionProps {
- suggestedExchangeUrl: string;
- amount: AmountJson;
- callback_url: string;
- wt_types: string[];
-}
-
-
-class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
- statusString: StateHolder<string|null> = this.makeState(null);
- reserveCreationInfo: StateHolder<ReserveCreationInfo|null> = this.makeState(
- null);
- url: StateHolder<string|null> = this.makeState(null);
- detailCollapsed: StateHolder<boolean> = this.makeState(true);
-
- updateEvent = new EventTrigger();
-
- constructor(props: ExchangeSelectionProps) {
- super(props);
- this.onUrlChanged(props.suggestedExchangeUrl || null);
- }
-
-
- renderAdvanced(): JSX.Element {
- if (this.detailCollapsed() && this.url() !== null && !this.statusString()) {
- return (
- <button className="linky"
- onClick={() => this.detailCollapsed(false)}>
- view fee structure / select different exchange provider
- </button>
- );
- }
- return (
- <div>
- <h2>Provider Selection</h2>
- <label>URL: </label>
- <input className="url" type="text" spellCheck={false}
- value={this.url()!}
- key="exchange-url-input"
- onInput={(e) => this.onUrlChanged((e.target as HTMLInputElement).value)}/>
- <br />
- {this.renderStatus()}
- <h2>Detailed Fee Structure</h2>
- {renderReserveCreationDetails(this.reserveCreationInfo())}
- </div>)
- }
-
- renderFee() {
- if (!this.reserveCreationInfo()) {
- return "??";
- }
- let rci = this.reserveCreationInfo()!;
- let totalCost = Amounts.add(rci.overhead, rci.withdrawFee).amount;
- return `${amountToPretty(totalCost)}`;
- }
-
- renderFeeStatus() {
- if (this.reserveCreationInfo()) {
- return (
- <p>
- The exchange provider will charge
- {" "}
- {this.renderFee()}
- {" "}
- in fees.
- </p>
- );
- }
- if (this.url() && !this.statusString()) {
- let shortName = URI(this.url()!).host();
- return <p>
- Waiting for a response from
- {" "}
- <em>{shortName}</em>
- </p>;
- }
- if (this.statusString()) {
- return (
- <p>
- <strong style={{color: "red"}}>A problem occured, see below.</strong>
- </p>
- );
- }
- return (
- <p>
- Information about fees will be available when an exchange provider is selected.
- </p>
- );
- }
-
- render(): JSX.Element {
- return (
- <div>
- <p>
- {"You are about to withdraw "}
- <strong>{amountToPretty(this.props.amount)}</strong>
- {" from your bank account into your wallet."}
- </p>
- {this.renderFeeStatus()}
- <button className="accept"
- disabled={this.reserveCreationInfo() == null}
- onClick={() => this.confirmReserve()}>
- Accept fees and withdraw
- </button>
- <br/>
- {this.renderAdvanced()}
- </div>
- );
- }
-
-
- 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);
- if (!this.url()) {
- this.statusString(i18n`Error: URL is empty`);
- return;
- }
-
- this.statusString(null);
- let parsedUrl = URI(this.url()!);
- if (parsedUrl.is("relative")) {
- this.statusString(i18n`Error: URL may not be relative`);
- return;
- }
-
- try {
- let r = await getReserveCreationInfo(this.url()!,
- this.props.amount);
- console.log("get exchange info resolved");
- this.reserveCreationInfo(r);
- console.dir(r);
- } catch (e) {
- console.log("get exchange info rejected");
- 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})`);
- }
- }
- }
-
- reset() {
- this.statusString(null);
- this.reserveCreationInfo(null);
- }
-
- confirmReserveImpl(rci: ReserveCreationInfo,
- exchange: string,
- amount: AmountJson,
- callback_url: string) {
- const d = {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 wire_details = rci.wireInfo;
- if (!rawResp.error) {
- const resp = CreateReserveResponse.checked(rawResp);
- let q: {[name: string]: string|number} = {
- wire_details: JSON.stringify(wire_details),
- exchange: resp.exchange,
- reserve_pub: resp.reservePub,
- amount_value: amount.value,
- amount_fraction: amount.fraction,
- amount_currency: amount.currency,
- };
- let url = 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.reset();
- this.statusString(
- `Oops, something went wrong.` +
- `The wallet responded with error status (${rawResp.error}).`);
- }
- };
- chrome.runtime.sendMessage({type: 'create-reserve', detail: d}, cb);
- }
-
- async onUrlChanged(url: string|null) {
- this.reset();
- this.url(url);
- if (url == undefined) {
- return;
- }
- this.updateEvent.trigger();
- let waited = await this.updateEvent.wait(200);
- if (waited) {
- // Run the actual update if nobody else preempted us.
- this.forceReserveUpdate();
- this.forceUpdate();
- }
- }
-
- renderStatus(): any {
- if (this.statusString()) {
- return <p><strong style={{color: "red"}}>{this.statusString()}</strong></p>;
- } else if (!this.reserveCreationInfo()) {
- return <p>Checking URL, please wait ...</p>;
- }
- return "";
- }
-}
-
-export async function main() {
- const url = URI(document.location.href);
- const query: any = URI.parseQuery(url.query());
- const amount = AmountJson.checked(JSON.parse(query.amount));
- const callback_url = query.callback_url;
- const bank_url = query.bank_url;
- const wt_types = JSON.parse(query.wt_types);
-
- try {
- const suggestedExchangeUrl = await getSuggestedExchange(amount.currency);
- let args = {
- wt_types,
- suggestedExchangeUrl,
- callback_url,
- amount
- };
-
- ReactDOM.render(<ExchangeSelection {...args} />, 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 = `Fatal error: "${e.message}".`;
- console.error(`got error "${e.message}"`, e);
- }
-}