aboutsummaryrefslogtreecommitdiff
path: root/src/webex/pages/confirm-create-reserve.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/webex/pages/confirm-create-reserve.tsx')
-rw-r--r--src/webex/pages/confirm-create-reserve.tsx526
1 files changed, 0 insertions, 526 deletions
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 <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 { 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<T>(delayMs: number, value: T): Promise<T> {
- return new Promise<T>((resolve, reject) => {
- setTimeout(() => resolve(value), delayMs);
- });
-}
-
-class EventTrigger {
- private triggerResolve: any;
- private 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)]);
- }
-}
-
-
-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<ManualSelectionProps> {
- private url: StateHolder<string> = this.makeState("");
- private errorMessage: StateHolder<string|null> = this.makeState(null);
- private isOkay: StateHolder<boolean> = this.makeState(false);
- private updateEvent = new EventTrigger();
- constructor(p: ManualSelectionProps) {
- super(p);
- this.url(p.initialUrl);
- this.update();
- }
- render() {
- return (
- <div className="pure-g pure-form pure-form-stacked">
- <div className="pure-u-1">
- <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)}
- onChange={(e) => this.onUrlChanged((e.target as HTMLInputElement).value)} />
- </div>
- <div className="pure-u-1">
- <button className="pure-button button-success"
- disabled={!this.isOkay()}
- onClick={() => this.props.onSelect(this.url())}>
- {i18n.str`Select`}
- </button>
- <span> </span>
- {this.errorMessage()}
- </div>
- </div>
- );
- }
-
- 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<ExchangeSelectionProps> {
- private statusString: StateHolder<string|null> = this.makeState(null);
- private reserveCreationInfo: StateHolder<ReserveCreationInfo|null> = this.makeState(
- null);
- private url: StateHolder<string|null> = this.makeState(null);
-
- private selectingExchange: StateHolder<boolean> = 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 = (
- <i18n.Translate wrap="p">
- The exchange is trusted by the wallet.
- </i18n.Translate>
- );
- } else if (rci.isAudited) {
- trustMessage = (
- <i18n.Translate wrap="p">
- The exchange is audited by a trusted auditor.
- </i18n.Translate>
- );
- } else {
- trustMessage = (
- <i18n.Translate wrap="p">
- 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.
- </i18n.Translate>
- );
- }
- return (
- <div>
- <i18n.Translate wrap="p">
- Using exchange provider <strong>{this.url()}</strong>.
- The exchange provider will charge
- {" "}<span>{renderAmount(totalCost)}</span>{" "}
- in fees.
- </i18n.Translate>
- {trustMessage}
- </div>
- );
- }
- if (this.url() && !this.statusString()) {
- const shortName = new URI(this.url()!).host();
- return (
- <i18n.Translate wrap="p">
- Waiting for a response from
- <span> </span>
- <em>{shortName}</em>
- </i18n.Translate>
- );
- }
- if (this.statusString()) {
- return (
- <p>
- <strong style={{color: "red"}}>{this.statusString()}</strong>
- </p>
- );
- }
- return (
- <p>
- {i18n.str`Information about fees will be available when an exchange provider is selected.`}
- </p>
- );
- }
-
- 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 (
- <p className="errorbox">
- <i18n.Translate wrap="span">
- Your wallet (protocol version <span>{rci.walletVersion}</span>) might be outdated.<span> </span>
- The exchange has a higher, incompatible
- protocol version (<span>{rci.exchangeVersion}</span>).
- </i18n.Translate>
- </p>
- );
- }
- if (rci.versionMatch.currentCmp === 1) {
- return (
- <p className="errorbox">
- <i18n.Translate wrap="span">
- The chosen exchange (protocol version <span>{rci.exchangeVersion}</span> might be outdated.<span> </span>
- The exchange has a lower, incompatible
- protocol version than your wallet (protocol version <span>{rci.walletVersion}</span>).
- </i18n.Translate>
- </p>
- );
- }
- throw Error("not reached");
- }
-
- renderConfirm() {
- return (
- <div>
- {this.renderFeeStatus()}
- <p>
- <button className="pure-button button-success"
- disabled={this.reserveCreationInfo() === null}
- onClick={() => this.confirmReserve()}>
- {i18n.str`Accept fees and withdraw`}
- </button>
- { " " }
- <button className="pure-button button-secondary"
- onClick={() => this.selectingExchange(true)}>
- {i18n.str`Change Exchange Provider`}
- </button>
- </p>
- {this.renderUpdateStatus()}
- <WithdrawDetailView rci={this.reserveCreationInfo()} />
- </div>
- );
- }
-
- 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 (
- <div>
- {i18n.str`Please select an exchange. You can review the details before after your selection.`}
-
- {this.props.suggestedExchangeUrl && (
- <div>
- <h2>Bank Suggestion</h2>
- <button className="pure-button button-success" onClick={() => this.select(this.props.suggestedExchangeUrl)}>
- <i18n.Translate wrap="span">
- Select <strong>{this.props.suggestedExchangeUrl}</strong>
- </i18n.Translate>
- </button>
- </div>
- )}
-
- {exchanges.length > 0 && (
- <div>
- <h2>Known Exchanges</h2>
- {exchanges.map((e) => (
- <button key={e.baseUrl} className="pure-button button-success" onClick={() => this.select(e.baseUrl)}>
- <i18n.Translate>
- Select <strong>{e.baseUrl}</strong>
- </i18n.Translate>
- </button>
- ))}
- </div>
- )}
-
- <h2>i18n.str`Manual Selection`</h2>
- <ManualSelection initialUrl={this.url() || ""} onSelect={(url: string) => this.select(url)} />
- </div>
- );
- }
-
- render(): JSX.Element {
- return (
- <div>
- <i18n.Translate wrap="p">
- You are about to withdraw
- {" "}<strong>{renderAmount(this.props.amount)}</strong>{" "}
- from your bank account into your wallet.
- </i18n.Translate>
- {this.selectingExchange() ? this.renderSelect() : this.renderConfirm()}
- </div>
- );
- }
-
-
- 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 <p><strong style={{color: "red"}}>{this.statusString()}</strong></p>;
- } else if (!this.reserveCreationInfo()) {
- return <p>{i18n.str`Checking URL, please wait ...`}</p>;
- }
- 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(<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 = i18n.str`Fatal error: "${e.message}".`;
- console.error("got error", e);
- }
-}
-
-document.addEventListener("DOMContentLoaded", () => {
- main();
-});