aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-02-09 21:56:06 +0100
committerFlorian Dold <florian.dold@gmail.com>2016-02-09 21:56:06 +0100
commit5e85cd8b8fa25ed3fbfc260b48bcad098978407a (patch)
tree3c875261e2c3fa2176911caefc4dcf225a04df74
parent42a0076f5951d303635b2e544aa66112cdb9abfe (diff)
new reserve creation protocol
-rw-r--r--.gitignore1
-rw-r--r--extension/background/main.ts4
-rw-r--r--extension/content_scripts/notify.js38
-rw-r--r--extension/content_scripts/notify.ts44
-rw-r--r--extension/gulpfile.js6
-rw-r--r--extension/lib/wallet/checkable.ts190
-rw-r--r--extension/lib/wallet/query.ts4
-rw-r--r--extension/lib/wallet/types.ts138
-rw-r--r--extension/lib/wallet/wallet.ts208
-rw-r--r--extension/lib/wallet/wxmessaging.js57
-rw-r--r--extension/lib/wallet/wxmessaging.ts45
-rw-r--r--extension/lib/web-common.ts6
-rw-r--r--extension/manifest.json2
-rw-r--r--extension/pages/confirm-create-reserve.html85
-rw-r--r--extension/pages/confirm-create-reserve.js56
-rw-r--r--extension/pages/confirm-create-reserve.tsx48
-rw-r--r--extension/pages/show-db.js43
-rw-r--r--extension/pages/show-db.ts44
-rw-r--r--extension/tsconfig.json2
19 files changed, 724 insertions, 297 deletions
diff --git a/.gitignore b/.gitignore
index 431134610..6c18b141a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
*.zip
*.xpi
+_build/
diff --git a/extension/background/main.ts b/extension/background/main.ts
index 88b56fd4b..7d607aa49 100644
--- a/extension/background/main.ts
+++ b/extension/background/main.ts
@@ -14,7 +14,9 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/
-// Entry point for the background page.
+/**
+ * Entry point for the background page.
+ */
"use strict";
diff --git a/extension/content_scripts/notify.js b/extension/content_scripts/notify.js
index a38288d88..7791f86e1 100644
--- a/extension/content_scripts/notify.js
+++ b/extension/content_scripts/notify.js
@@ -13,13 +13,16 @@
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/>
*/
-// Script that is injected into pages in order to allow merchants pages to
-// query the availability of Taler.
/// <reference path="../lib/decl/chrome/chrome.d.ts" />
+/**
+ * Script that is injected into (all!) pages to allow them
+ * to interact with the GNU Taler wallet via DOM Events.
+ */
"use strict";
// Make sure we don't pollute the namespace too much.
var TalerNotify;
(function (TalerNotify) {
+ var PROTOCOL_VERSION = 1;
console.log("Taler injected");
function subst(url, H_contract) {
url = url.replace("${H_contract}", H_contract);
@@ -28,28 +31,35 @@ var TalerNotify;
}
var $ = function (x) { return document.getElementById(x); };
document.addEventListener("taler-probe", function (e) {
- var evt = new Event("taler-wallet-present");
+ var evt = new CustomEvent("taler-wallet-present", {
+ detail: {
+ walletProtocolVersion: PROTOCOL_VERSION
+ }
+ });
document.dispatchEvent(evt);
console.log("handshake done");
});
document.addEventListener("taler-create-reserve", function (e) {
console.log("taler-create-reserve with " + JSON.stringify(e.detail));
- var form_uri = $(e.detail.form_id).action;
- // TODO: validate event fields
- // TODO: also send extra bank-defined form fields
var params = {
- post_url: URI(form_uri).absoluteTo(document.location.href).href(),
- // TODO: This should change in the future, we should not deal with the
- // amount as a bank-specific string here.
- amount_str: $(e.detail.input_amount).value,
- // TODO: This double indirection is way too much ...
- field_amount: $(e.detail.input_amount).name,
- field_reserve_pub: $(e.detail.input_pub).name,
- field_mint: $(e.detail.mint_rcv).name,
+ amount: JSON.stringify(e.detail.amount),
+ callback_url: URI(e.detail.callback_url).absoluteTo(document.location.href),
};
var uri = URI(chrome.extension.getURL("pages/confirm-create-reserve.html"));
document.location.href = uri.query(params).href();
});
+ document.addEventListener("taler-confirm-reserve", function (e) {
+ console.log("taler-confirm-reserve with " + JSON.stringify(e.detail));
+ var msg = {
+ type: "confirm-reserve",
+ detail: {
+ reservePub: e.detail.reserve_pub
+ }
+ };
+ chrome.runtime.sendMessage(msg, function (resp) {
+ console.log("confirm reserve done");
+ });
+ });
document.addEventListener("taler-contract", function (e) {
// XXX: the merchant should just give us the parsed data ...
var offer = JSON.parse(e.detail);
diff --git a/extension/content_scripts/notify.ts b/extension/content_scripts/notify.ts
index fac289d7f..c2dd5ab64 100644
--- a/extension/content_scripts/notify.ts
+++ b/extension/content_scripts/notify.ts
@@ -14,16 +14,20 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/
-// Script that is injected into pages in order to allow merchants pages to
-// query the availability of Taler.
-
/// <reference path="../lib/decl/chrome/chrome.d.ts" />
-"use strict";
+/**
+ * Script that is injected into (all!) pages to allow them
+ * to interact with the GNU Taler wallet via DOM Events.
+ */
+"use strict";
+
// Make sure we don't pollute the namespace too much.
namespace TalerNotify {
+ const PROTOCOL_VERSION = 1;
+
console.log("Taler injected");
function subst(url: string, H_contract) {
@@ -35,30 +39,38 @@ namespace TalerNotify {
let $ = (x) => document.getElementById(x);
document.addEventListener("taler-probe", function(e) {
- let evt = new Event("taler-wallet-present");
+ let evt = new CustomEvent("taler-wallet-present", {
+ detail: {
+ walletProtocolVersion: PROTOCOL_VERSION
+ }
+ });
document.dispatchEvent(evt);
console.log("handshake done");
});
document.addEventListener("taler-create-reserve", function(e: CustomEvent) {
console.log("taler-create-reserve with " + JSON.stringify(e.detail));
- let form_uri = (<HTMLFormElement>$(e.detail.form_id)).action;
- // TODO: validate event fields
- // TODO: also send extra bank-defined form fields
let params = {
- post_url: URI(form_uri).absoluteTo(document.location.href).href(),
- // TODO: This should change in the future, we should not deal with the
- // amount as a bank-specific string here.
- amount_str: (<HTMLInputElement>$(e.detail.input_amount)).value,
- // TODO: This double indirection is way too much ...
- field_amount: (<HTMLInputElement>$(e.detail.input_amount)).name,
- field_reserve_pub: (<HTMLInputElement>$(e.detail.input_pub)).name,
- field_mint: (<HTMLInputElement>$(e.detail.mint_rcv)).name,
+ amount: JSON.stringify(e.detail.amount),
+ callback_url: URI(e.detail.callback_url).absoluteTo(document.location.href),
};
let uri = URI(chrome.extension.getURL("pages/confirm-create-reserve.html"));
document.location.href = uri.query(params).href();
});
+ document.addEventListener("taler-confirm-reserve", function(e: CustomEvent) {
+ console.log("taler-confirm-reserve with " + JSON.stringify(e.detail));
+ let msg = {
+ type: "confirm-reserve",
+ detail: {
+ reservePub: e.detail.reserve_pub
+ }
+ };
+ chrome.runtime.sendMessage(msg, (resp) => {
+ console.log("confirm reserve done");
+ });
+ });
+
document.addEventListener("taler-contract", function(e: CustomEvent) {
// XXX: the merchant should just give us the parsed data ...
diff --git a/extension/gulpfile.js b/extension/gulpfile.js
index 4b27a0a0b..925b5149e 100644
--- a/extension/gulpfile.js
+++ b/extension/gulpfile.js
@@ -53,8 +53,12 @@ const paths = {
dist: [
"manifest.json",
"img/*",
+ "style/*.css",
"lib/vendor/*",
- "lib/emscripten/libwrapper.js"
+ "lib/emscripten/libwrapper.js",
+ "lib/module-trampoline.js",
+ "popup/**/*.{html,css}",
+ "pages/**/*.{html,css}",
],
};
diff --git a/extension/lib/wallet/checkable.ts b/extension/lib/wallet/checkable.ts
new file mode 100644
index 000000000..8f89c8669
--- /dev/null
+++ b/extension/lib/wallet/checkable.ts
@@ -0,0 +1,190 @@
+/*
+ This file is part of TALER
+ (C) 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/>
+ */
+
+
+"use strict";
+
+/**
+ * Decorators for type-checking JSON into
+ * an object.
+ * @module Checkable
+ * @author Florian Dold
+ */
+
+export namespace Checkable {
+ let chkSym = Symbol("checkable");
+
+
+ function checkNumber(target, prop, path): any {
+ if ((typeof target) !== "number") {
+ throw Error(`expected number for ${path}`);
+ }
+ return target;
+ }
+
+
+ function checkString(target, prop, path): any {
+ if (typeof target !== "string") {
+ throw Error(`expected string for ${path}, got ${typeof target} instead`);
+ }
+ return target;
+ }
+
+
+ function checkAnyObject(target, prop, path): any {
+ if (typeof target !== "object") {
+ throw Error(`expected (any) object for ${path}, got ${typeof target} instead`);
+ }
+ return target;
+ }
+
+
+ function checkAny(target, prop, path): any {
+ return target;
+ }
+
+
+ function checkList(target, prop, path): any {
+ if (!Array.isArray(target)) {
+ throw Error(`array expected for ${path}, got ${typeof target} instead`);
+ }
+ for (let i = 0; i < target.length; i++) {
+ let v = target[i];
+ prop.elementChecker(v, prop.elementProp, path.concat([i]));
+ }
+ return target;
+ }
+
+
+ function checkValue(target, prop, path): any {
+ let type = prop.type;
+ if (!type) {
+ throw Error(`assertion failed (prop is ${JSON.stringify(prop)})`);
+ }
+ let v = target;
+ if (!v || typeof v !== "object") {
+ throw Error(`expected object for ${path}, got ${typeof v} instead`);
+ }
+ let props = type.prototype[chkSym].props;
+ let remainingPropNames = new Set(Object.getOwnPropertyNames(v));
+ let obj = new type();
+ for (let prop of props) {
+ if (!remainingPropNames.has(prop.propertyKey)) {
+ throw Error("Property missing: " + prop.propertyKey);
+ }
+ if (!remainingPropNames.delete(prop.propertyKey)) {
+ throw Error("assertion failed");
+ }
+ let propVal = v[prop.propertyKey];
+ obj[prop.propertyKey] = prop.checker(propVal,
+ prop,
+ path.concat([prop.propertyKey]));
+ }
+
+ if (remainingPropNames.size != 0) {
+ throw Error("superfluous properties " + JSON.stringify(Array.from(
+ remainingPropNames.values())));
+ }
+ return obj;
+ }
+
+
+ export function Class(target) {
+ target.checked = (v) => {
+ return checkValue(v, {
+ propertyKey: "(root)",
+ type: target,
+ checker: checkValue
+ }, []);
+ };
+ return target;
+ }
+
+
+ export function Value(type) {
+ if (!type) {
+ throw Error("Type does not exist yet (wrong order of definitions?)");
+ }
+ function deco(target: Object, propertyKey: string | symbol): void {
+ let chk = mkChk(target);
+ chk.props.push({
+ propertyKey: propertyKey,
+ checker: checkValue,
+ type: type
+ });
+ }
+
+ return deco;
+ }
+
+
+ export function List(type) {
+ let stub = {};
+ type(stub, "(list-element)");
+ let elementProp = mkChk(stub).props[0];
+ let elementChecker = elementProp.checker;
+ if (!elementChecker) {
+ throw Error("assertion failed");
+ }
+ function deco(target: Object, propertyKey: string | symbol): void {
+ let chk = mkChk(target);
+ chk.props.push({
+ elementChecker,
+ elementProp,
+ propertyKey: propertyKey,
+ checker: checkList,
+ });
+ }
+
+ return deco;
+ }
+
+
+ export function Number(target: Object, propertyKey: string | symbol): void {
+ let chk = mkChk(target);
+ chk.props.push({propertyKey: propertyKey, checker: checkNumber});
+ }
+
+
+ export function AnyObject(target: Object,
+ propertyKey: string | symbol): void {
+ let chk = mkChk(target);
+ chk.props.push({propertyKey: propertyKey, checker: checkAnyObject});
+ }
+
+
+ export function Any(target: Object,
+ propertyKey: string | symbol): void {
+ let chk = mkChk(target);
+ chk.props.push({propertyKey: propertyKey, checker: checkAny});
+ }
+
+
+ export function String(target: Object, propertyKey: string | symbol): void {
+ let chk = mkChk(target);
+ chk.props.push({propertyKey: propertyKey, checker: checkString});
+ }
+
+
+ function mkChk(target) {
+ let chk = target[chkSym];
+ if (!chk) {
+ chk = {props: []};
+ target[chkSym] = chk;
+ }
+ return chk;
+ }
+} \ No newline at end of file
diff --git a/extension/lib/wallet/query.ts b/extension/lib/wallet/query.ts
index 82053138f..b82c85189 100644
--- a/extension/lib/wallet/query.ts
+++ b/extension/lib/wallet/query.ts
@@ -268,6 +268,10 @@ class QueryRoot {
* Get one object from a store by its key.
*/
get(storeName, key): Promise<any> {
+ if (key === void 0) {
+ throw Error("key must not be undefined");
+ }
+
const {resolve, promise} = openPromise();
const doGet = (tx) => {
diff --git a/extension/lib/wallet/types.ts b/extension/lib/wallet/types.ts
index 478287a21..4f512800e 100644
--- a/extension/lib/wallet/types.ts
+++ b/extension/lib/wallet/types.ts
@@ -14,6 +14,8 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/
+import {EddsaPublicKey} from "./emscriptif";
+import {Checkable} from "./checkable";
"use strict";
// TODO: factor into multiple files
@@ -61,48 +63,138 @@ export interface Coin {
}
-export interface AmountJson {
+@Checkable.Class
+export class AmountJson {
+ @Checkable.Number
value: number;
- fraction: number
+
+ @Checkable.Number
+ fraction: number;
+
+ @Checkable.String
currency: string;
+
+ static checked: (obj: any) => AmountJson;
}
-export interface ConfirmReserveRequest {
+@Checkable.Class
+export class CreateReserveRequest {
/**
- * Name of the form field for the amount.
+ * The initial amount for the reserve.
*/
- field_amount;
+ @Checkable.Value(AmountJson)
+ amount: AmountJson;
/**
- * Name of the form field for the reserve public key.
+ * Mint URL where the bank should create the reserve.
*/
- field_reserve_pub;
+ @Checkable.String
+ mint: string;
- /**
- * Name of the form field for the reserve public key.
- */
- field_mint;
+ static checked: (obj: any) => CreateReserveRequest;
+}
- /**
- * The actual amount in string form.
- * TODO: where is this format specified?
- */
- amount_str;
+@Checkable.Class
+export class CreateReserveResponse {
/**
- * Target URL for the reserve creation request.
+ * Mint URL where the bank should create the reserve.
+ * The URL is canonicalized in the response.
*/
- post_url;
+ @Checkable.String
+ mint: string;
+
+ @Checkable.String
+ reservePub: string;
+
+ static checked: (obj: any) => CreateReserveResponse;
+}
+
+@Checkable.Class
+export class ConfirmReserveRequest {
/**
- * Mint URL where the bank should create the reserve.
+ * Public key of then reserve that should be marked
+ * as confirmed.
*/
- mint;
+ @Checkable.String
+ reservePub: string;
+
+ static checked: (obj: any) => ConfirmReserveRequest;
}
-export interface ConfirmReserveResponse {
- backlink?: string;
- success: boolean;
+@Checkable.Class
+export class MintInfo {
+ @Checkable.String
+ master_pub: string;
+
+ @Checkable.String
+ url: string;
+
+ static checked: (obj: any) => MintInfo;
+}
+
+
+@Checkable.Class
+export class Contract {
+ @Checkable.String
+ H_wire: string;
+
+ @Checkable.Value(AmountJson)
+ amount: AmountJson;
+
+ @Checkable.List(Checkable.AnyObject)
+ auditors: any[];
+
+ @Checkable.String
+ expiry: string;
+
+ @Checkable.Any
+ locations: any;
+
+ @Checkable.Value(AmountJson)
+ max_fee: AmountJson;
+
+ @Checkable.Any
+ merchant: any;
+
+ @Checkable.String
+ merchant_pub: string;
+
+ @Checkable.List(Checkable.Value(MintInfo))
+ mints: MintInfo[];
+
+ @Checkable.List(Checkable.AnyObject)
+ products: any[];
+
+ @Checkable.String
+ refund_deadline: string;
+
+ @Checkable.String
+ timestamp: string;
+
+ @Checkable.Number
+ transaction_id: number;
+
+ @Checkable.String
+ fulfillment_url: string;
+
+ static checked: (obj: any) => Contract;
+}
+
+
+@Checkable.Class
+export class Offer {
+ @Checkable.Value(Contract)
+ contract: Contract;
+
+ @Checkable.String
+ merchant_sig: string;
+
+ @Checkable.String
+ H_contract: string;
+
+ static checked: (obj: any) => Offer;
} \ No newline at end of file
diff --git a/extension/lib/wallet/wallet.ts b/extension/lib/wallet/wallet.ts
index cbc3e4b01..62d67b40c 100644
--- a/extension/lib/wallet/wallet.ts
+++ b/extension/lib/wallet/wallet.ts
@@ -32,8 +32,7 @@ import {UInt64} from "./emscriptif";
import {DepositRequestPS} from "./emscriptif";
import {eddsaSign} from "./emscriptif";
import {EddsaPrivateKey} from "./emscriptif";
-import {ConfirmReserveRequest} from "./types";
-import {ConfirmReserveResponse} from "./types";
+import {CreateReserveRequest} from "./types";
import {RsaPublicKey} from "./emscriptif";
import {Denomination} from "./types";
import {RsaBlindingKey} from "./emscriptif";
@@ -48,24 +47,15 @@ import {HttpResponse} from "./http";
import {RequestException} from "./http";
import {Query} from "./query";
import {AmountJson} from "./types";
+import {ConfirmReserveRequest} from "./types";
+import {Offer} from "./types";
+import {Contract} from "./types";
+import {MintInfo} from "./types";
+import {CreateReserveResponse} from "./types";
"use strict";
-
-class CoinPaySig {
- coin_sig: string;
-
- coin_pub: string;
-
- ub_sig: string;
-
- denom_pub: string;
-
- f: AmountJson;
-}
-
-
interface ConfirmPayRequest {
offer: Offer;
}
@@ -75,36 +65,7 @@ interface MintCoins {
}
-interface MintInfo {
- master_pub: string;
- url: string;
-}
-
-interface Offer {
- contract: Contract;
- merchant_sig: string;
- H_contract: string;
-}
-
-interface Contract {
- H_wire: string;
- amount: AmountJson;
- auditors: string[];
- expiry: string,
- locations: string[];
- max_fee: AmountJson;
- merchant: any;
- merchant_pub: string;
- mints: MintInfo[];
- products: string[];
- refund_deadline: string;
- timestamp: string;
- transaction_id: number;
- fulfillment_url: string;
-}
-
-
-interface CoinPaySig_interface {
+interface CoinPaySig {
coin_sig: string;
coin_pub: string;
ub_sig: string;
@@ -127,19 +88,13 @@ interface Reserve {
}
-interface PaymentResponse {
- payReq: any;
- contract: Contract;
-}
-
-
export interface Badge {
setText(s: string): void;
setColor(c: string): void;
}
-type PayCoinInfo = Array<{ updatedCoin: Coin, sig: CoinPaySig_interface }>;
+type PayCoinInfo = Array<{ updatedCoin: Coin, sig: CoinPaySig }>;
/**
@@ -250,7 +205,7 @@ export class Wallet {
EddsaPrivateKey.fromCrock(cd.coin.coinPriv))
.toCrock();
- let s: CoinPaySig_interface = {
+ let s: CoinPaySig = {
coin_sig: coinSig,
coin_pub: cd.coin.coinPub,
ub_sig: cd.coin.denomSig,
@@ -425,6 +380,11 @@ export class Wallet {
});
}
+
+ /**
+ * First fetch information requred to withdraw from the reserve,
+ * then deplete the reserve, withdrawing coins until it is empty.
+ */
initReserve(reserveRecord) {
this.updateMintFromUrl(reserveRecord.mint_base_url)
.then((mint) =>
@@ -447,72 +407,82 @@ export class Wallet {
}
- confirmReserve(req: ConfirmReserveRequest): Promise<ConfirmReserveResponse> {
- let reservePriv = EddsaPrivateKey.create();
- let reservePub = reservePriv.getPublicKey();
- let form = new FormData();
- let now: number = (new Date).getTime();
- form.append(req.field_amount, req.amount_str);
- form.append(req.field_reserve_pub, reservePub.toCrock());
- form.append(req.field_mint, req.mint);
- // TODO: set bank-specified fields.
- let mintBaseUrl = canonicalizeBaseUrl(req.mint);
- let requestedAmount = parsePrettyAmount(req.amount_str);
-
- if (!requestedAmount) {
- throw Error(`unrecognized amount ${req.amount_str}.`);
- }
+ /**
+ * Create a reserve, but do not flag it as confirmed yet.
+ */
+ createReserve(req: CreateReserveRequest): Promise<CreateReserveResponse> {
+ const reservePriv = EddsaPrivateKey.create();
+ const reservePub = reservePriv.getPublicKey();
+
+ const now = (new Date).getTime();
+ const canonMint = canonicalizeBaseUrl(req.mint);
+
+ const reserveRecord = {
+ reserve_pub: reservePub.toCrock(),
+ reserve_priv: reservePriv.toCrock(),
+ mint_base_url: canonMint,
+ created: now,
+ last_query: null,
+ current_amount: null,
+ requested_amount: req.amount,
+ confirmed: false,
+ };
+
+
+ const historyEntry = {
+ type: "create-reserve",
+ timestamp: now,
+ detail: {
+ requestedAmount: req.amount,
+ reservePub: reserveRecord.reserve_pub,
+ }
+ };
+
+ return Query(this.db)
+ .put("reserves", reserveRecord)
+ .put("history", historyEntry)
+ .finish()
+ .then(() => {
+ let r: CreateReserveResponse = {
+ mint: canonMint,
+ reservePub: reservePub.toCrock(),
+ };
+ return r;
+ });
+ }
- return this.http.postForm(req.post_url, form)
- .then((hresp) => {
- // TODO: look at response status code and handle errors appropriately
- let json = JSON.parse(hresp.responseText);
- if (!json) {
- return {
- success: false
- };
- }
- let resp: ConfirmReserveResponse = {
- success: undefined,
- backlink: json.redirect_url,
- };
- let reserveRecord = {
- reserve_pub: reservePub.toCrock(),
- reserve_priv: reservePriv.toCrock(),
- mint_base_url: mintBaseUrl,
- created: now,
- last_query: null,
- current_amount: null,
- // XXX: set to actual amount
- requested_amount: null
- };
-
- if (hresp.status != 200) {
- resp.success = false;
- return resp;
- }
-
- let historyEntry = {
- type: "create-reserve",
- timestamp: now,
- detail: {
- requestedAmount,
- reservePub: reserveRecord.reserve_pub,
- }
- };
-
- resp.success = true;
-
- return Query(this.db)
- .put("reserves", reserveRecord)
- .put("history", historyEntry)
- .finish()
- .then(() => {
- // Do this in the background
- this.initReserve(reserveRecord);
- return resp;
- });
- });
+
+ /**
+ * Mark an existing reserve as confirmed. The wallet will start trying
+ * to withdraw from that reserve. This may not immediately succeed,
+ * since the mint might not know about the reserve yet, even though the
+ * bank confirmed its creation.
+ *
+ * A confirmed reserve should be shown to the user in the UI, while
+ * an unconfirmed reserve should be hidden.
+ */
+ confirmReserve(req: ConfirmReserveRequest): Promise<void> {
+ const now = (new Date).getTime();
+ const historyEntry = {
+ type: "confirm-reserve",
+ timestamp: now,
+ detail: {
+ reservePub: req.reservePub,
+ }
+ };
+ return Query(this.db)
+ .get("reserves", req.reservePub)
+ .then((r) => {
+ r.confirmed = true;
+ return Query(this.db)
+ .put("reserves", r)
+ .put("history", historyEntry)
+ .finish()
+ .then(() => {
+ // Do this in the background
+ this.initReserve(r);
+ });
+ });
}
diff --git a/extension/lib/wallet/wxmessaging.js b/extension/lib/wallet/wxmessaging.js
index bd108276f..1e1029be0 100644
--- a/extension/lib/wallet/wxmessaging.js
+++ b/extension/lib/wallet/wxmessaging.js
@@ -13,10 +13,17 @@
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(["./wallet", "./db", "./http"], function(exports_1) {
+System.register(["./types", "./wallet", "./db", "./http"], function(exports_1) {
"use strict";
- var wallet_1, db_1, db_2, db_3, http_1;
+ var types_1, wallet_1, db_1, db_2, db_3, http_1, types_2, types_3;
var ChromeBadge;
+ /**
+ * Messaging for the WebExtensions wallet. Should contain
+ * parts that are specific for WebExtensions, but as little business
+ * logic as possible.
+ *
+ * @author Florian Dold
+ */
function makeHandlers(wallet) {
return (_a = {},
_a["balances"] = function (db, detail, sendResponse) {
@@ -43,29 +50,43 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) {
// Response is synchronous
return false;
},
+ _a["create-reserve"] = function (db, detail, sendResponse) {
+ var d = {
+ mint: detail.mint,
+ amount: detail.amount,
+ };
+ var req = types_2.CreateReserveRequest.checked(d);
+ wallet.createReserve(req)
+ .then(function (resp) {
+ sendResponse(resp);
+ })
+ .catch(function (e) {
+ sendResponse({ error: "exception" });
+ console.error("exception during 'create-reserve'");
+ console.error(e.stack);
+ });
+ return true;
+ },
_a["confirm-reserve"] = function (db, detail, sendResponse) {
// TODO: make it a checkable
- var req = {
- field_amount: detail.field_amount,
- field_mint: detail.field_mint,
- field_reserve_pub: detail.field_reserve_pub,
- post_url: detail.post_url,
- mint: detail.mint,
- amount_str: detail.amount_str
+ var d = {
+ reservePub: detail.reservePub
};
+ var req = types_1.ConfirmReserveRequest.checked(d);
wallet.confirmReserve(req)
.then(function (resp) {
sendResponse(resp);
})
.catch(function (e) {
- sendResponse({ success: false });
+ sendResponse({ error: "exception" });
console.error("exception during 'confirm-reserve'");
console.error(e.stack);
});
return true;
},
_a["confirm-pay"] = function (db, detail, sendResponse) {
- wallet.confirmPay(detail.offer, detail.merchantPageUrl)
+ var offer = types_3.Offer.checked(detail.offer);
+ wallet.confirmPay(offer)
.then(function (r) {
sendResponse(r);
})
@@ -84,7 +105,7 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) {
.catch(function (e) {
console.error("exception during 'execute-payment'");
console.error(e.stack);
- sendResponse({ success: false, error: e.message });
+ sendResponse({ error: e.message });
});
// async sendResponse
return true;
@@ -133,6 +154,11 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) {
exports_1("wxMain", wxMain);
return {
setters:[
+ function (types_1_1) {
+ types_1 = types_1_1;
+ types_2 = types_1_1;
+ types_3 = types_1_1;
+ },
function (wallet_1_1) {
wallet_1 = wallet_1_1;
},
@@ -145,13 +171,6 @@ System.register(["./wallet", "./db", "./http"], function(exports_1) {
http_1 = http_1_1;
}],
execute: function() {
- /**
- * Messaging for the WebExtensions wallet. Should contain
- * parts that are specific for WebExtensions, but as little business
- * logic as possible.
- * @module Messaging
- * @author Florian Dold
- */
"use strict";
ChromeBadge = (function () {
function ChromeBadge() {
diff --git a/extension/lib/wallet/wxmessaging.ts b/extension/lib/wallet/wxmessaging.ts
index 63310270f..9af63eb2f 100644
--- a/extension/lib/wallet/wxmessaging.ts
+++ b/extension/lib/wallet/wxmessaging.ts
@@ -22,18 +22,20 @@ import {deleteDb} from "./db";
import {openTalerDb} from "./db";
import {BrowserHttpLib} from "./http";
import {Badge} from "./wallet";
+import {CreateReserveRequest} from "./types";
+import {Offer} from "./types";
+
+"use strict";
/**
* Messaging for the WebExtensions wallet. Should contain
* parts that are specific for WebExtensions, but as little business
* logic as possible.
- * @module Messaging
+ *
* @author Florian Dold
*/
-"use strict";
-
-function makeHandlers(wallet) {
+function makeHandlers(wallet: Wallet) {
return {
["balances"]: function(db, detail, sendResponse) {
wallet.getBalances()
@@ -60,29 +62,43 @@ function makeHandlers(wallet) {
// Response is synchronous
return false;
},
+ ["create-reserve"]: function(db, detail, sendResponse) {
+ const d = {
+ mint: detail.mint,
+ amount: detail.amount,
+ };
+ const req = CreateReserveRequest.checked(d);
+ wallet.createReserve(req)
+ .then((resp) => {
+ sendResponse(resp);
+ })
+ .catch((e) => {
+ sendResponse({error: "exception"});
+ console.error("exception during 'create-reserve'");
+ console.error(e.stack);
+ });
+ return true;
+ },
["confirm-reserve"]: function(db, detail, sendResponse) {
// TODO: make it a checkable
- let req: ConfirmReserveRequest = {
- field_amount: detail.field_amount,
- field_mint: detail.field_mint,
- field_reserve_pub: detail.field_reserve_pub,
- post_url: detail.post_url,
- mint: detail.mint,
- amount_str: detail.amount_str
+ const d = {
+ reservePub: detail.reservePub
};
+ const req = ConfirmReserveRequest.checked(d);
wallet.confirmReserve(req)
.then((resp) => {
sendResponse(resp);
})
.catch((e) => {
- sendResponse({success: false});
+ sendResponse({error: "exception"});
console.error("exception during 'confirm-reserve'");
console.error(e.stack);
});
return true;
},
["confirm-pay"]: function(db, detail, sendResponse) {
- wallet.confirmPay(detail.offer, detail.merchantPageUrl)
+ const offer = Offer.checked(detail.offer);
+ wallet.confirmPay(offer)
.then((r) => {
sendResponse(r)
})
@@ -101,7 +117,7 @@ function makeHandlers(wallet) {
.catch((e) => {
console.error("exception during 'execute-payment'");
console.error(e.stack);
- sendResponse({success: false, error: e.message});
+ sendResponse({error: e.message});
});
// async sendResponse
return true;
@@ -124,6 +140,7 @@ function makeHandlers(wallet) {
};
}
+
class ChromeBadge implements Badge {
setText(s: string) {
chrome.browserAction.setBadgeText({text: s});
diff --git a/extension/lib/web-common.ts b/extension/lib/web-common.ts
index 34ae2a1a8..96f9b61b7 100644
--- a/extension/lib/web-common.ts
+++ b/extension/lib/web-common.ts
@@ -14,8 +14,14 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/
+import {AmountJson} from "./wallet/types";
export function substituteFulfillmentUrl(url: string, vars) {
url = url.replace("${H_contract}", vars.H_contract);
url = url.replace("${$}", "$");
return url;
+}
+
+export function amountToPretty(amount: AmountJson): string {
+ let x = amount.value + amount.fraction / 1e6;
+ return `${x} ${amount.currency}`;
} \ No newline at end of file
diff --git a/extension/manifest.json b/extension/manifest.json
index 8c2f0ff36..a4f621da4 100644
--- a/extension/manifest.json
+++ b/extension/manifest.json
@@ -2,7 +2,7 @@
"description": "Privacy preserving and transparent payments",
"manifest_version": 2,
"name": "GNU Taler Wallet",
- "version": "0.5.1",
+ "version": "0.5.5",
"applications": {
"gecko": {
diff --git a/extension/pages/confirm-create-reserve.html b/extension/pages/confirm-create-reserve.html
index b78f96712..7af54a828 100644
--- a/extension/pages/confirm-create-reserve.html
+++ b/extension/pages/confirm-create-reserve.html
@@ -1,43 +1,50 @@
<!doctype html>
<html>
- <head>
- <title>Taler Wallet: Select Taler Provider</title>
- <script src="../lib/vendor/URI.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">
- </head>
-
- <body>
-
- <header>
- <div id="logo"></div>
- <h1>Select Taler Provider</h1>
- </header>
-
- <aside class="sidebar" id="left">
- </aside>
-
- <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/"></input>
- </div>
- <button id="confirm">Confirm Mint Selection</button>
- </div>
- </article>
-
- </section>
- </body>
+<head>
+ <title>Taler Wallet: Select Taler Provider</title>
+ <script src="../lib/vendor/URI.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">
+</head>
+
+<body>
+
+<header>
+ <div id="logo"></div>
+ <h1>Select Taler Provider</h1>
+</header>
+
+<aside class="sidebar" id="left">
+</aside>
+
+<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>
+ </article>
+
+</section>
+</body>
</html>
diff --git a/extension/pages/confirm-create-reserve.js b/extension/pages/confirm-create-reserve.js
index ca9ef4099..a53833f04 100644
--- a/extension/pages/confirm-create-reserve.js
+++ b/extension/pages/confirm-create-reserve.js
@@ -13,41 +13,69 @@
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([], function(exports_1) {
+System.register(["../lib/wallet/types", "../lib/web-common"], function(exports_1) {
"use strict";
+ var types_1, web_common_1, types_2;
function main() {
function updateAmount() {
var showAmount = document.getElementById("show-amount");
console.log("Query is " + JSON.stringify(query));
- var s = query.amount_str;
- if (!s) {
- document.body.innerHTML = "Oops, something went wrong.";
- return;
- }
- showAmount.textContent = s;
+ var amount = types_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 = Object.assign({}, query);
- d.mint = document.getElementById('mint-url').value;
- var cb = function (resp) {
- if (resp.success === true) {
- document.location.href = resp.backlink;
+ 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 = types_2.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");
+ }
+ document.location.href = url_1.href();
}
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.";
}
};
- chrome.runtime.sendMessage({ type: 'confirm-reserve', detail: d }, cb);
+ chrome.runtime.sendMessage({ type: 'create-reserve', detail: d }, cb);
});
}
exports_1("main", main);
return {
- setters:[],
+ setters:[
+ function (types_1_1) {
+ types_1 = types_1_1;
+ types_2 = types_1_1;
+ },
+ function (web_common_1_1) {
+ web_common_1 = web_common_1_1;
+ }],
execute: function() {
+ "use strict";
}
}
});
diff --git a/extension/pages/confirm-create-reserve.tsx b/extension/pages/confirm-create-reserve.tsx
index 88af96466..e4d2d27e6 100644
--- a/extension/pages/confirm-create-reserve.tsx
+++ b/extension/pages/confirm-create-reserve.tsx
@@ -14,6 +14,9 @@
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
*/
+import {AmountJson} from "../lib/wallet/types";
+import {amountToPretty} from "../lib/web-common";
+import {CreateReserveResponse} from "../lib/wallet/types";
"use strict";
@@ -21,12 +24,8 @@ export function main() {
function updateAmount() {
let showAmount = document.getElementById("show-amount");
console.log("Query is " + JSON.stringify(query));
- let s = query.amount_str;
- if (!s) {
- document.body.innerHTML = "Oops, something went wrong.";
- return;
- }
- showAmount.textContent = s;
+ let amount = AmountJson.checked(JSON.parse(query.amount));
+ showAmount.textContent = amountToPretty(amount);
}
let url = URI(document.location.href);
@@ -35,12 +34,37 @@ export function main() {
updateAmount();
document.getElementById("confirm").addEventListener("click", (e) => {
- let d = Object.assign({}, query);
- d.mint = (document.getElementById('mint-url') as HTMLInputElement).value;
+ const d = {
+ mint: (document.getElementById('mint-url') as HTMLInputElement).value,
+ amount: JSON.parse(query.amount)
+ };
+
+ if (!d.mint) {
+ // FIXME: indicate error instead!
+ throw Error("mint missing");
+ }
- const cb = (resp) => {
- if (resp.success === true) {
- document.location.href = resp.backlink;
+ if (!d.amount) {
+ // FIXME: indicate error instead!
+ throw Error("amount missing");
+ }
+
+ 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: query.amount,
+ };
+ let url = URI(query.callback_url).addQuery(q);
+ if (!url.is("absolute")) {
+ throw Error("callback url is not absolute");
+ }
+ document.location.href = url.href();
} else {
document.body.innerHTML =
`Oops, something went wrong. It looks like the bank could not
@@ -48,6 +72,6 @@ export function main() {
to check what happened.`;
}
};
- chrome.runtime.sendMessage({type: 'confirm-reserve', detail: d}, cb);
+ chrome.runtime.sendMessage({type: 'create-reserve', detail: d}, cb);
});
} \ No newline at end of file
diff --git a/extension/pages/show-db.js b/extension/pages/show-db.js
index 1c414dde7..bcd8485cb 100644
--- a/extension/pages/show-db.js
+++ b/extension/pages/show-db.js
@@ -13,32 +13,27 @@
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/>
*/
-
-
function replacer(match, pIndent, pKey, pVal, pEnd) {
- var key = '<span class=json-key>';
- var val = '<span class=json-value>';
- var str = '<span class=json-string>';
- var r = pIndent || '';
- if (pKey)
- r = r + key + pKey.replace(/[": ]/g, '') + '</span>: ';
- if (pVal)
- r = r + (pVal[0] == '"' ? str : val) + pVal + '</span>';
- return r + (pEnd || '');
+ var key = '<span class=json-key>';
+ var val = '<span class=json-value>';
+ var str = '<span class=json-string>';
+ var r = pIndent || '';
+ if (pKey)
+ r = r + key + pKey.replace(/[": ]/g, '') + '</span>: ';
+ if (pVal)
+ r = r + (pVal[0] == '"' ? str : val) + pVal + '</span>';
+ return r + (pEnd || '');
}
-
-
function prettyPrint(obj) {
- var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg;
- return JSON.stringify(obj, null, 3)
- .replace(/&/g, '&amp;').replace(/\\"/g, '&quot;')
- .replace(/</g, '&lt;').replace(/>/g, '&gt;')
- .replace(jsonLine, replacer);
+ var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg;
+ return JSON.stringify(obj, null, 3)
+ .replace(/&/g, '&amp;').replace(/\\"/g, '&quot;')
+ .replace(/</g, '&lt;').replace(/>/g, '&gt;')
+ .replace(jsonLine, replacer);
}
-
-
-document.addEventListener("DOMContentLoaded", (e) => {
- chrome.runtime.sendMessage({type:'dump-db'}, (resp) => {
- document.getElementById('dump').innerHTML = prettyPrint(resp);
- });
+document.addEventListener("DOMContentLoaded", function (e) {
+ chrome.runtime.sendMessage({ type: 'dump-db' }, function (resp) {
+ document.getElementById('dump').innerHTML = prettyPrint(resp);
+ });
});
+//# sourceMappingURL=show-db.js.map \ No newline at end of file
diff --git a/extension/pages/show-db.ts b/extension/pages/show-db.ts
new file mode 100644
index 000000000..1c414dde7
--- /dev/null
+++ b/extension/pages/show-db.ts
@@ -0,0 +1,44 @@
+/*
+ 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/>
+ */
+
+
+function replacer(match, pIndent, pKey, pVal, pEnd) {
+ var key = '<span class=json-key>';
+ var val = '<span class=json-value>';
+ var str = '<span class=json-string>';
+ var r = pIndent || '';
+ if (pKey)
+ r = r + key + pKey.replace(/[": ]/g, '') + '</span>: ';
+ if (pVal)
+ r = r + (pVal[0] == '"' ? str : val) + pVal + '</span>';
+ return r + (pEnd || '');
+}
+
+
+function prettyPrint(obj) {
+ var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg;
+ return JSON.stringify(obj, null, 3)
+ .replace(/&/g, '&amp;').replace(/\\"/g, '&quot;')
+ .replace(/</g, '&lt;').replace(/>/g, '&gt;')
+ .replace(jsonLine, replacer);
+}
+
+
+document.addEventListener("DOMContentLoaded", (e) => {
+ chrome.runtime.sendMessage({type:'dump-db'}, (resp) => {
+ document.getElementById('dump').innerHTML = prettyPrint(resp);
+ });
+});
diff --git a/extension/tsconfig.json b/extension/tsconfig.json
index 5788cc057..18539aa4e 100644
--- a/extension/tsconfig.json
+++ b/extension/tsconfig.json
@@ -11,6 +11,7 @@
"lib/i18n.ts",
"lib/refs.ts",
"lib/web-common.ts",
+ "lib/wallet/checkable.ts",
"lib/wallet/db.ts",
"lib/wallet/emscriptif.ts",
"lib/wallet/http.ts",
@@ -21,6 +22,7 @@
"background/main.ts",
"content_scripts/notify.ts",
"popup/popup.tsx",
+ "pages/show-db.ts",
"pages/confirm-contract.tsx",
"pages/confirm-create-reserve.tsx",
"test/tests/taler.ts"