diff options
Diffstat (limited to 'pages/confirm-create-reserve.tsx')
-rw-r--r-- | pages/confirm-create-reserve.tsx | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/pages/confirm-create-reserve.tsx b/pages/confirm-create-reserve.tsx new file mode 100644 index 000000000..224a3e895 --- /dev/null +++ b/pages/confirm-create-reserve.tsx @@ -0,0 +1,305 @@ +/* + 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, If not, see <http://www.gnu.org/licenses/> + */ + +/// <reference path="../lib/decl/mithril.d.ts" /> + +import {amountToPretty, canonicalizeBaseUrl} from "../lib/wallet/helpers"; +import {AmountJson, CreateReserveResponse} from "../lib/wallet/types"; +import m from "mithril"; +import {IMintInfo} from "../lib/wallet/types"; +import {ReserveCreationInfo, Amounts} from "../lib/wallet/types"; +import MithrilComponent = _mithril.MithrilComponent; +import {Denomination} from "../lib/wallet/types"; +import {getReserveCreationInfo} from "../lib/wallet/wxApi"; + +"use strict"; + +/** + * Execute something after a delay, with the possibility + * to reset the delay. + */ +class DelayTimer { + ms: number; + f; + timerId: number = null; + + constructor(ms: number, f) { + this.f = f; + this.ms = ms; + } + + bump() { + this.stop(); + const handler = () => { + this.f(); + }; + this.timerId = window.setTimeout(handler, this.ms); + } + + stop() { + if (this.timerId !== null) { + window.clearTimeout(this.timerId); + } + } +} + + +class Controller { + url = m.prop<string>(); + statusString = null; + isValidMint = false; + reserveCreationInfo: ReserveCreationInfo = null; + private timer: DelayTimer; + private request: XMLHttpRequest; + amount: AmountJson; + callbackUrl: string; + detailCollapsed = m.prop<boolean>(true); + + constructor(initialMintUrl: string, amount: AmountJson, callbackUrl: string) { + console.log("creating main controller"); + this.amount = amount; + this.callbackUrl = callbackUrl; + this.timer = new DelayTimer(800, () => this.update()); + this.url(initialMintUrl); + this.update(); + } + + private update() { + this.timer.stop(); + const doUpdate = () => { + if (!this.url()) { + this.statusString = i18n`Please enter a URL`; + return; + } + this.statusString = null; + let parsedUrl = URI(this.url()); + if (parsedUrl.is("relative")) { + this.statusString = i18n`The URL you've entered is not valid (must be absolute)`; + return; + } + + m.redraw(true); + + console.log("doing get mint info"); + + getReserveCreationInfo(this.url(), this.amount) + .then((r: ReserveCreationInfo) => { + console.log("get mint info resolved"); + this.isValidMint = true; + this.reserveCreationInfo = r; + console.dir(r); + this.statusString = "The mint base URL is valid!"; + m.endComputation(); + }) + .catch((e) => { + console.log("get mint info rejected"); + if (e.hasOwnProperty("httpStatus")) { + this.statusString = `request failed with status ${this.request.status}`; + } else { + this.statusString = `unknown request error`; + } + m.endComputation(); + }); + }; + + doUpdate(); + + + console.log("got update"); + } + + reset() { + this.isValidMint = false; + this.statusString = null; + this.reserveCreationInfo = null; + if (this.request) { + this.request.abort(); + this.request = null; + } + } + + confirmReserve(mint: string, amount: AmountJson, callback_url: string) { + const d = {mint, amount}; + const cb = (rawResp) => { + if (!rawResp) { + throw Error("empty response"); + } + if (!rawResp.error) { + const resp = CreateReserveResponse.checked(rawResp); + let q = { + mint: resp.mint, + 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); + } + + onUrlChanged(url: string) { + this.reset(); + this.url(url); + this.timer.bump(); + } +} + + +function view(ctrl: Controller) { + let controls = []; + let mx = (x, ...args) => controls.push(m(x, ...args)); + + mx("p", + i18n`The bank wants to create a reserve over ${amountToPretty( + ctrl.amount)}.`); + mx("input", + { + className: "url", + type: "text", + spellcheck: false, + value: ctrl.url(), + oninput: m.withAttr("value", ctrl.onUrlChanged.bind(ctrl)), + }); + + mx("button", { + onclick: () => ctrl.confirmReserve(ctrl.url(), + ctrl.amount, + ctrl.callbackUrl), + disabled: !ctrl.isValidMint + }, + "Confirm exchange selection"); + + if (ctrl.statusString) { + mx("p", ctrl.statusString); + } else { + mx("p", "Checking URL, please wait ..."); + } + + if (ctrl.reserveCreationInfo) { + let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead, + ctrl.reserveCreationInfo.withdrawFee).amount; + mx("p", `Withdraw cost: ${amountToPretty(totalCost)}`); + if (ctrl.detailCollapsed()) { + mx("button.linky", { + onclick: () => { + ctrl.detailCollapsed(false); + } + }, "show more details"); + } else { + mx("button.linky", { + onclick: () => { + ctrl.detailCollapsed(true); + } + }, "hide details"); + mx("div", {}, renderReserveCreationDetails(ctrl.reserveCreationInfo)) + } + } + + return m("div", controls); +} + + +function renderReserveCreationDetails(rci: ReserveCreationInfo) { + let denoms = rci.selectedDenoms; + + function row(denom: Denomination) { + return m("tr", [ + m("td", denom.pub_hash.substr(0, 5) + "..."), + m("td", amountToPretty(denom.value)), + m("td", amountToPretty(denom.fee_withdraw)), + m("td", amountToPretty(denom.fee_refresh)), + m("td", amountToPretty(denom.fee_deposit)), + ]); + } + + let withdrawFeeStr = amountToPretty(rci.withdrawFee); + let overheadStr = amountToPretty(rci.overhead); + return [ + m("p", `Fee for withdrawal: ${withdrawFeeStr}`), + m("p", `Overhead: ${overheadStr}`), + m("table", [ + m("tr", [ + m("th", "Key Hash"), + m("th", "Value"), + m("th", "Withdraw Fee"), + m("th", "Refresh Fee"), + m("th", "Deposit Fee"), + ]), + denoms.map(row) + ]) + ]; +} + + +interface MintProbeResult { + keyInfo?: any; +} + +function probeMint(mintBaseUrl: string): Promise<MintProbeResult> { + throw Error("not implemented"); +} + + +function getSuggestedMint(currency: string): Promise<string> { + // TODO: make this request go to the wallet backend + // Right now, this is a stub. + const defaultMint = { + "KUDOS": "http://exchange.demo.taler.net", + "PUDOS": "http://exchange.test.taler.net", + }; + + let mint = defaultMint[currency]; + + if (!mint) { + mint = "" + } + + return Promise.resolve(mint); +} + + +export 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; + + getSuggestedMint(amount.currency) + .then((suggestedMintUrl) => { + const controller = () => new Controller(suggestedMintUrl, amount, callback_url); + var MintSelection = {controller, view}; + m.mount(document.getElementById("mint-selection"), MintSelection); + }) + .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 backend error "${e.message}"`); + }); +} |