diff options
| author | Florian Dold <florian.dold@gmail.com> | 2017-05-28 23:15:41 +0200 | 
|---|---|---|
| committer | Florian Dold <florian.dold@gmail.com> | 2017-05-28 23:15:41 +0200 | 
| commit | b6e774585d32017e5f1ceeeb2b2e2a5e350354d3 (patch) | |
| tree | 080cb5afe3b48c0428abd2d7de1ff7fe34d9b9b1 /src/pages/confirm-create-reserve.tsx | |
| parent | 38a74188d759444d7e1abac856f78ae710e2a4c5 (diff) | |
move webex specific things in their own directory
Diffstat (limited to 'src/pages/confirm-create-reserve.tsx')
| -rw-r--r-- | src/pages/confirm-create-reserve.tsx | 639 | 
1 files changed, 0 insertions, 639 deletions
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 <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 "../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<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)]); -  } -} - - -interface CollapsibleState { -  collapsed: boolean; -} - -interface CollapsibleProps { -  initiallyCollapsed: boolean; -  title: string; -} - -class Collapsible extends React.Component<CollapsibleProps, CollapsibleState> { -  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 <h2><a className="opener opener-collapsed" href="#" onClick={doOpen}>{this.props.title}</a></h2>; -    } -    return ( -      <div> -        <h2><a className="opener opener-open" href="#" onClick={doClose}>{this.props.title}</a></h2> -        {this.props.children} -      </div> -    ); -  } -} - -function renderAuditorDetails(rci: ReserveCreationInfo|null) { -  if (!rci) { -    return ( -      <p> -        Details will be displayed when a valid exchange provider URL is entered. -      </p> -    ); -  } -  if (rci.exchangeInfo.auditors.length == 0) { -    return ( -      <p> -        The exchange is not audited by any auditors. -      </p> -    ); -  } -  return ( -    <div> -      {rci.exchangeInfo.auditors.map(a => ( -        <h3>Auditor {a.url}</h3> -      ))} -    </div> -  ); -} - -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: 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 ( -      <tr> -        <td>{countByPub[denom.denomPub] + "x"}</td> -        <td>{amountToPretty(denom.value)}</td> -        <td>{amountToPretty(denom.feeWithdraw)}</td> -        <td>{amountToPretty(denom.feeRefresh)}</td> -        <td>{amountToPretty(denom.feeDeposit)}</td> -      </tr> -    ); -  } - -  function wireFee(s: string) { -    return [ -      <thead> -        <tr> -        <th colSpan={3}>Wire Method {s}</th> -        </tr> -        <tr> -        <th>Applies Until</th> -        <th>Wire Fee</th> -        <th>Closing Fee</th> -        </tr> -      </thead>, -      <tbody> -      {rci!.wireFees.feesForType[s].map(f => ( -        <tr> -          <td>{moment.unix(f.endStamp).format("llll")}</td> -          <td>{amountToPretty(f.wireFee)}</td> -          <td>{amountToPretty(f.closingFee)}</td> -        </tr> -      ))} -      </tbody> -    ]; -  } - -  let withdrawFeeStr = amountToPretty(rci.withdrawFee); -  let overheadStr = amountToPretty(rci.overhead); - -  return ( -    <div> -      <h3>Overview</h3> -      <p>{i18n.str`Withdrawal fees: ${withdrawFeeStr}`}</p> -      <p>{i18n.str`Rounding loss: ${overheadStr}`}</p> -      <p>{i18n.str`Earliest expiration (for deposit): ${moment.unix(rci.earliestDepositExpiration).fromNow()}`}</p> -      <h3>Coin Fees</h3> -      <table className="pure-table"> -        <thead> -        <tr> -          <th>{i18n.str`# Coins`}</th> -          <th>{i18n.str`Value`}</th> -          <th>{i18n.str`Withdraw Fee`}</th> -          <th>{i18n.str`Refresh Fee`}</th> -          <th>{i18n.str`Deposit Fee`}</th> -        </tr> -        </thead> -        <tbody> -        {uniq.map(row)} -        </tbody> -      </table> -      <h3>Wire Fees</h3> -      <table className="pure-table"> -      {Object.keys(rci.wireFees.feesForType).map(wireFee)} -      </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>{i18n.str`Withdraw fees:`} {amountToPretty(totalCost)}</p>; -  } -  return <p />; -} - - -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<ManualSelectionProps> { -  url: StateHolder<string> = this.makeState(""); -  errorMessage: StateHolder<string|null> = this.makeState(null); -  isOkay: StateHolder<boolean> = this.makeState(false); -  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)} /> -        </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> -          {this.errorMessage()} -        </div> -      </div> -    ); -  } - -  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<ExchangeSelectionProps> { -  statusString: StateHolder<string|null> = this.makeState(null); -  reserveCreationInfo: StateHolder<ReserveCreationInfo|null> = this.makeState( -    null); -  url: StateHolder<string|null> = this.makeState(null); - -  selectingExchange: StateHolder<boolean> = 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 = ( -          <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>{amountToPretty(totalCost)}</span> -          {" "} -          in fees. -        </i18n.Translate> -        {trustMessage} -        </div> -      ); -    } -    if (this.url() && !this.statusString()) { -      let shortName = new URI(this.url()!).host(); -      return ( -        <i18n.Translate wrap="p"> -          Waiting for a response from -          {" "} -          <em>{shortName}</em> -        </i18n.Translate> -      ); -    } -    if (this.statusString()) { -      return ( -        <p> -          <strong style={{color: "red"}}>{i18n.str`A problem occured, see below. ${this.statusString()}`}</strong> -        </p> -      ); -    } -    return ( -      <p> -        {i18n.str`Information about fees will be available when an exchange provider is selected.`} -      </p> -    ); -  } - -  renderConfirm() { -    return ( -      <div> -        {this.renderFeeStatus()} -        <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> -        <br/> -        <Collapsible initiallyCollapsed={true} title="Fee and Spending Details"> -          {renderReserveCreationDetails(this.reserveCreationInfo())} -        </Collapsible> -        <Collapsible initiallyCollapsed={true} title="Auditor Details"> -          {renderAuditorDetails(this.reserveCreationInfo())} -        </Collapsible> -      </div> -    ); -  } - -  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 ( -      <div> -        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)}> -              Select <strong>{this.props.suggestedExchangeUrl}</strong> -            </button> -          </div> -        )} - -        {exchanges.length > 0 && ( -          <div> -            <h2>Known Exchanges</h2> -            {exchanges.map(e => ( -              <button className="pure-button button-success" onClick={() => this.select(e.baseUrl)}> -              Select <strong>{e.baseUrl}</strong> -              </button> -            ))} -          </div> -        )} - -        <h2>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>{amountToPretty(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); -  } - -  /** -   * 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 <p><strong style={{color: "red"}}>{this.statusString()}</strong></p>; -    } else if (!this.reserveCreationInfo()) { -      return <p>{i18n.str`Checking URL, please wait ...`}</p>; -    } -    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(<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.message}"`, e); -  } -} - -document.addEventListener("DOMContentLoaded", () => { -  main(); -});  | 
