refactoring; make wallet follow new bank protocol

This commit is contained in:
Florian Dold 2016-02-11 11:29:57 +01:00
parent 47f2084706
commit 164d5f20c3
11 changed files with 412 additions and 167 deletions

View File

@ -14,7 +14,7 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/
import {AmountJson} from "./wallet";
import {AmountJson} from "./types";
import * as EmscWrapper from "../emscripten/emsc";
/**

View File

@ -14,7 +14,13 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/
import {AmountJson} from "./wallet/wallet";
/**
* Smaller helper functions that do not depend
* on the emscripten machinery.
*/
import {AmountJson} from "./types";
export function substituteFulfillmentUrl(url: string, vars) {
url = url.replace("${H_contract}", vars.H_contract);
@ -22,7 +28,38 @@ export function substituteFulfillmentUrl(url: string, vars) {
return url;
}
export function amountToPretty(amount: AmountJson): string {
let x = amount.value + amount.fraction / 1e6;
return `${x} ${amount.currency}`;
}
/**
* Canonicalize a base url, typically for the mint.
*
* See http://api.taler.net/wallet.html#general
*/
export function canonicalizeBaseUrl(url) {
let x = new URI(url);
if (!x.protocol()) {
x.protocol("https");
}
x.path(x.path() + "/").normalizePath();
x.fragment();
x.query();
return x.href()
}
export function parsePrettyAmount(pretty: string): AmountJson {
const res = /([0-9]+)(.[0-9]+)?\s*(\w+)/.exec(pretty);
if (!res) {
return null;
}
return {
value: parseInt(res[1], 10),
fraction: res[2] ? (parseFloat(`0.${res[2]}`) * 1e-6) : 0,
currency: res[3]
}
}

View File

@ -0,0 +1,56 @@
/*
This file is part of TALER
(C) 2015 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/>
*/
/**
* Common types that are used by Taler.
*
* Note most types are defined in wallet.ts, types that
* are defined in types.ts are intended to be used by components
* that do not depend on the whole wallet implementation (which depends on
* emscripten).
*/
import {Checkable} from "./checkable";
@Checkable.Class
export class AmountJson {
@Checkable.Number
value: number;
@Checkable.Number
fraction: number;
@Checkable.String
currency: string;
static checked: (obj: any) => AmountJson;
}
@Checkable.Class
export class CreateReserveResponse {
/**
* Mint URL where the bank should create the reserve.
* The URL is canonicalized in the response.
*/
@Checkable.String
mint: string;
@Checkable.String
reservePub: string;
static checked: (obj: any) => CreateReserveResponse;
}

View File

@ -22,9 +22,11 @@
*/
import * as native from "./emscriptif";
import {AmountJson, CreateReserveResponse} from "./types";
import {HttpResponse, RequestException} from "./http";
import {Query} from "./query";
import {Checkable} from "./checkable";
import {canonicalizeBaseUrl} from "./helpers";
"use strict";
@ -73,21 +75,6 @@ export interface Coin {
}
@Checkable.Class
export class AmountJson {
@Checkable.Number
value: number;
@Checkable.Number
fraction: number;
@Checkable.String
currency: string;
static checked: (obj: any) => AmountJson;
}
@Checkable.Class
export class CreateReserveRequest {
/**
@ -106,22 +93,6 @@ export class CreateReserveRequest {
}
@Checkable.Class
export class CreateReserveResponse {
/**
* Mint URL where the bank should create the reserve.
* The URL is canonicalized in the response.
*/
@Checkable.String
mint: string;
@Checkable.String
reservePub: string;
static checked: (obj: any) => CreateReserveResponse;
}
@Checkable.Class
export class ConfirmReserveRequest {
/**
@ -270,34 +241,6 @@ function isWithdrawableDenom(d: Denomination) {
}
/**
* See http://api.taler.net/wallet.html#general
*/
function canonicalizeBaseUrl(url) {
let x = new URI(url);
if (!x.protocol()) {
x.protocol("https");
}
x.path(x.path() + "/").normalizePath();
x.fragment();
x.query();
return x.href()
}
function parsePrettyAmount(pretty: string): AmountJson {
const res = /([0-9]+)(.[0-9]+)?\s*(\w+)/.exec(pretty);
if (!res) {
return null;
}
return {
value: parseInt(res[1], 10),
fraction: res[2] ? (parseFloat(`0.${res[2]}`) * 1e-6) : 0,
currency: res[3]
}
}
interface HttpRequestLibrary {
req(method: string,
url: string|uri.URI,

View File

@ -13,11 +13,11 @@
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/>
*/
System.register(["../lib/web-common"], function(exports_1, context_1) {
System.register(["../lib/wallet/helpers"], function(exports_1, context_1) {
/// <reference path="../lib/decl/handlebars/handlebars.d.ts" />
"use strict";
var __moduleName = context_1 && context_1.id;
var web_common_1;
var helpers_1;
function prettyAmount(amount) {
var v = amount.value + amount.fraction / 1e6;
return v.toFixed(2) + " " + amount.currency;
@ -55,15 +55,15 @@ System.register(["../lib/web-common"], function(exports_1, context_1) {
}
var c = d.offer.contract;
console.log("contract", c);
document.location.href = web_common_1.substituteFulfillmentUrl(c.fulfillment_url, offer);
document.location.href = helpers_1.substituteFulfillmentUrl(c.fulfillment_url, offer);
});
}
}
exports_1("main", main);
return {
setters:[
function (web_common_1_1) {
web_common_1 = web_common_1_1;
function (helpers_1_1) {
helpers_1 = helpers_1_1;
}],
execute: function() {
}

View File

@ -17,7 +17,7 @@
/// <reference path="../lib/decl/handlebars/handlebars.d.ts" />
"use strict";
import {substituteFulfillmentUrl} from "../lib/web-common";
import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
declare var m: any;

View File

@ -4,6 +4,8 @@
<head>
<title>Taler Wallet: Select Taler Provider</title>
<script src="../lib/vendor/URI.js"></script>
<script src="../lib/i18n.js"></script>
<script src="../lib/vendor/mithril.js"></script>
<script src="../lib/vendor/system-csp-production.src.js"></script>
<script src="../lib/module-trampoline.js"></script>
<link rel="stylesheet" type="text/css" href="../style/wallet.css">
@ -22,27 +24,7 @@
<section id="main">
<article>
<p>
You asked to withdraw <span id="show-amount">(loading...)</span> from your
bank account.
</p>
<p>
Please specify the base URL of the Taler mint you want to use. The Taler
mint will process the payments, possibly for a fee. The mint underwrites
electronic coins and will hold matching funds in reserve in its bank
account. Mints are expected to be regularly audited by a trusted party to
ensure that they have sufficient reserves to cover all outstanding
obligations.
</p>
<div class="formish">
<div class="form-row">
<label for="mint-url">Mint URL</label>
<input class="url" id="mint-url" type="text"
value="http://mint.demo.taler.net/"/>
</div>
<button id="confirm">Confirm Mint Selection</button>
</div>
<div id="mint-selection"></div>
</article>
</section>

View File

@ -13,69 +13,176 @@
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/>
*/
System.register(["../lib/web-common", "../lib/wallet/wallet"], function(exports_1, context_1) {
System.register(["../lib/wallet/helpers", "../lib/wallet/types"], function(exports_1, context_1) {
"use strict";
var __moduleName = context_1 && context_1.id;
var web_common_1, wallet_1;
var helpers_1, types_1;
var DelayTimer, Controller;
function main() {
function updateAmount() {
var showAmount = document.getElementById("show-amount");
console.log("Query is " + JSON.stringify(query));
var amount = wallet_1.AmountJson.checked(JSON.parse(query.amount));
showAmount.textContent = web_common_1.amountToPretty(amount);
}
var url = URI(document.location.href);
var query = URI.parseQuery(url.query());
updateAmount();
document.getElementById("confirm").addEventListener("click", function (e) {
var d = {
mint: document.getElementById('mint-url').value,
amount: JSON.parse(query.amount)
};
if (!d.mint) {
// FIXME: indicate error instead!
throw Error("mint missing");
}
if (!d.amount) {
// FIXME: indicate error instead!
throw Error("amount missing");
}
var cb = function (rawResp) {
if (!rawResp) {
throw Error("empty response");
}
if (!rawResp.error) {
var resp = wallet_1.CreateReserveResponse.checked(rawResp);
var q = {
mint: resp.mint,
reserve_pub: resp.reservePub,
amount: query.amount,
};
var url_1 = URI(query.callback_url).addQuery(q);
if (!url_1.is("absolute")) {
throw Error("callback url is not absolute");
var amount = types_1.AmountJson.checked(JSON.parse(query.amount));
var callback_url = query.callback_url;
var MintSelection = {
controller: function () { return new Controller(); },
view: function (ctrl) {
var controls = [];
var mx = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i - 0] = arguments[_i];
}
document.location.href = url_1.href();
return controls.push(m.apply(void 0, args));
};
mx("p", (_a = ["The bank wants to create a reserve over ", "."], _a.raw = ["The bank wants to create a reserve over ", "."], i18n(_a, helpers_1.amountToPretty(amount))));
mx("input.url", {
type: "text",
spellcheck: false,
oninput: m.withAttr("value", ctrl.onUrlChanged.bind(ctrl)),
});
if (ctrl.isValidMint) {
mx("button", {
onclick: function () { return ctrl.confirmReserve(ctrl.url, amount, callback_url); }
}, "Confirm mint selection");
}
else {
document.body.innerHTML =
"Oops, something went wrong. It looks like the bank could not\n transfer funds to the mint. Please go back to your bank's website\n to check what happened.";
if (ctrl.errorString) {
mx("p", ctrl.errorString);
}
};
chrome.runtime.sendMessage({ type: 'create-reserve', detail: d }, cb);
});
return m("div", controls);
var _a;
}
};
m.mount(document.getElementById("mint-selection"), MintSelection);
}
exports_1("main", main);
return {
setters:[
function (web_common_1_1) {
web_common_1 = web_common_1_1;
function (helpers_1_1) {
helpers_1 = helpers_1_1;
},
function (wallet_1_1) {
wallet_1 = wallet_1_1;
function (types_1_1) {
types_1 = types_1_1;
}],
execute: function() {
"use strict";
/**
* Execute something after a delay, with the possibility
* to reset the delay.
*/
DelayTimer = (function () {
function DelayTimer(ms, f) {
this.timerId = null;
this.f = f;
this.ms = ms;
}
DelayTimer.prototype.bump = function () {
var _this = this;
if (this.timerId !== null) {
window.clearTimeout(this.timerId);
}
var handler = function () {
_this.f();
};
this.timerId = window.setTimeout(handler, this.ms);
};
return DelayTimer;
}());
Controller = (function () {
function Controller() {
var _this = this;
this.url = null;
this.errorString = null;
this.isValidMint = false;
this.update();
this.timer = new DelayTimer(800, function () { return _this.update(); });
}
Controller.prototype.update = function () {
var _this = this;
var doUpdate = function () {
if (!_this.url) {
_this.errorString = (_a = ["Please enter a URL"], _a.raw = ["Please enter a URL"], i18n(_a));
return;
}
_this.errorString = null;
var parsedUrl = URI(_this.url);
if (parsedUrl.is("relative")) {
_this.errorString = (_b = ["The URL you've entered is not valid (must be absolute)"], _b.raw = ["The URL you've entered is not valid (must be absolute)"], i18n(_b));
return;
}
var keysUrl = URI("/keys").absoluteTo(helpers_1.canonicalizeBaseUrl(_this.url));
console.log("requesting keys from '" + keysUrl + "'");
_this.request = new XMLHttpRequest();
_this.request.onreadystatechange = function () {
if (_this.request.readyState == XMLHttpRequest.DONE) {
switch (_this.request.status) {
case 200:
_this.isValidMint = true;
break;
case 0:
_this.errorString = "unknown request error";
break;
default:
_this.errorString = "request failed with status " + _this.request.status;
break;
}
m.redraw();
}
};
_this.request.open("get", keysUrl.href());
_this.request.send();
var _a, _b;
};
doUpdate();
m.redraw();
console.log("got update");
};
Controller.prototype.reset = function () {
this.isValidMint = false;
this.errorString = null;
if (this.request) {
this.request.abort();
this.request = null;
}
m.redraw();
};
Controller.prototype.confirmReserve = function (mint, amount, callback_url) {
var _this = this;
var d = { mint: mint, amount: amount };
var cb = function (rawResp) {
if (!rawResp) {
throw Error("empty response");
}
if (!rawResp.error) {
var resp = types_1.CreateReserveResponse.checked(rawResp);
var q = {
mint: resp.mint,
reserve_pub: resp.reservePub,
amount_value: amount.value,
amount_fraction: amount.fraction,
amount_currency: amount.currency,
};
var 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.errorString = ("Oops, something went wrong." +
("The wallet responded with error status (" + rawResp.error + ")."));
}
};
chrome.runtime.sendMessage({ type: 'create-reserve', detail: d }, cb);
};
Controller.prototype.onUrlChanged = function (url) {
this.reset();
this.url = url;
this.timer.bump();
};
return Controller;
}());
}
}
});

View File

@ -14,40 +14,107 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/
import {amountToPretty} from "../lib/web-common";
import {AmountJson, CreateReserveResponse} from "../lib/wallet/wallet";
import {amountToPretty, canonicalizeBaseUrl} from "../lib/wallet/helpers";
import {AmountJson, CreateReserveResponse} from "../lib/wallet/types";
"use strict";
declare var m: any;
export function main() {
function updateAmount() {
let showAmount = document.getElementById("show-amount");
console.log("Query is " + JSON.stringify(query));
let amount = AmountJson.checked(JSON.parse(query.amount));
showAmount.textContent = amountToPretty(amount);
/**
* 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;
}
let url = URI(document.location.href);
let query: any = URI.parseQuery(url.query());
bump() {
if (this.timerId !== null) {
window.clearTimeout(this.timerId);
}
const handler = () => {
this.f();
};
this.timerId = window.setTimeout(handler, this.ms);
}
}
updateAmount();
document.getElementById("confirm").addEventListener("click", (e) => {
const d = {
mint: (document.getElementById('mint-url') as HTMLInputElement).value,
amount: JSON.parse(query.amount)
class Controller {
url = null;
errorString = null;
isValidMint = false;
private timer: DelayTimer;
private request: XMLHttpRequest;
constructor() {
this.update();
this.timer = new DelayTimer(800, () => this.update());
}
update() {
const doUpdate = () => {
if (!this.url) {
this.errorString = i18n`Please enter a URL`;
return;
}
this.errorString = null;
let parsedUrl = URI(this.url);
if (parsedUrl.is("relative")) {
this.errorString = i18n`The URL you've entered is not valid (must be absolute)`;
return;
}
const keysUrl = URI("/keys").absoluteTo(canonicalizeBaseUrl(this.url));
console.log(`requesting keys from '${keysUrl}'`);
this.request = new XMLHttpRequest();
this.request.onreadystatechange = () => {
if (this.request.readyState == XMLHttpRequest.DONE) {
switch (this.request.status) {
case 200:
this.isValidMint = true;
break;
case 0:
this.errorString = `unknown request error`;
break;
default:
this.errorString = `request failed with status ${this.request.status}`;
break;
}
m.redraw();
}
};
this.request.open("get", keysUrl.href());
this.request.send();
};
if (!d.mint) {
// FIXME: indicate error instead!
throw Error("mint missing");
}
doUpdate();
m.redraw();
console.log("got update");
}
if (!d.amount) {
// FIXME: indicate error instead!
throw Error("amount missing");
reset() {
this.isValidMint = false;
this.errorString = null;
if (this.request) {
this.request.abort();
this.request = null;
}
m.redraw();
}
confirmReserve(mint: string, amount: AmountJson, callback_url: string) {
const d = {mint, amount};
const cb = (rawResp) => {
if (!rawResp) {
throw Error("empty response");
@ -57,20 +124,72 @@ export function main() {
let q = {
mint: resp.mint,
reserve_pub: resp.reservePub,
amount: query.amount,
amount_value: amount.value,
amount_fraction: amount.fraction,
amount_currency: amount.currency,
};
let url = URI(query.callback_url).addQuery(q);
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 {
document.body.innerHTML =
`Oops, something went wrong. It looks like the bank could not
transfer funds to the mint. Please go back to your bank's website
to check what happened.`;
this.reset();
this.errorString = (
`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();
}
}
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;
var MintSelection = {
controller: () => new Controller(),
view(ctrl: Controller) {
let controls = [];
let mx = (...args) => controls.push(m(...args));
mx("p",
i18n`The bank wants to create a reserve over ${amountToPretty(
amount)}.`);
mx("input.url",
{
type: "text",
spellcheck: false,
oninput: m.withAttr("value", ctrl.onUrlChanged.bind(ctrl)),
});
if (ctrl.isValidMint) {
mx("button", {
onclick: () => ctrl.confirmReserve(ctrl.url,
amount,
callback_url)
},
"Confirm mint selection");
}
if (ctrl.errorString) {
mx("p", ctrl.errorString);
}
return m("div", controls);
}
};
m.mount(document.getElementById("mint-selection"), MintSelection);
}

View File

@ -20,7 +20,7 @@
"use strict";
import {substituteFulfillmentUrl} from "../lib/web-common";
import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
declare var m: any;
declare var i18n: any;

View File

@ -12,12 +12,13 @@
"files": [
"lib/i18n.ts",
"lib/refs.ts",
"lib/web-common.ts",
"lib/wallet/checkable.ts",
"lib/wallet/db.ts",
"lib/wallet/emscriptif.ts",
"lib/wallet/helpers.ts",
"lib/wallet/http.ts",
"lib/wallet/query.ts",
"lib/wallet/types.ts",
"lib/wallet/wallet.ts",
"lib/wallet/wxmessaging.ts",
"background/main.ts",