From 5e85cd8b8fa25ed3fbfc260b48bcad098978407a Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 9 Feb 2016 21:56:06 +0100 Subject: [PATCH] new reserve creation protocol --- .gitignore | 1 + extension/background/main.ts | 4 +- extension/content_scripts/notify.js | 38 ++-- extension/content_scripts/notify.ts | 44 +++-- extension/gulpfile.js | 6 +- extension/lib/wallet/checkable.ts | 190 +++++++++++++++++++ extension/lib/wallet/query.ts | 4 + extension/lib/wallet/types.ts | 152 ++++++++++++--- extension/lib/wallet/wallet.ts | 198 +++++++++----------- extension/lib/wallet/wxmessaging.js | 57 ++++-- extension/lib/wallet/wxmessaging.ts | 45 +++-- extension/lib/web-common.ts | 6 + extension/manifest.json | 2 +- extension/pages/confirm-create-reserve.html | 71 +++---- extension/pages/confirm-create-reserve.js | 56 ++++-- extension/pages/confirm-create-reserve.tsx | 48 +++-- extension/pages/show-db.js | 43 ++--- extension/pages/show-db.ts | 44 +++++ extension/tsconfig.json | 2 + 19 files changed, 719 insertions(+), 292 deletions(-) create mode 100644 extension/lib/wallet/checkable.ts create mode 100644 extension/pages/show-db.ts 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 */ -// 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 */ -// Script that is injected into pages in order to allow merchants pages to -// query the availability of Taler. /// +/** + * 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 */ -// Script that is injected into pages in order to allow merchants pages to -// query the availability of Taler. - /// +/** + * 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 = ($(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: ($(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), }; 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 + */ + + +"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 { + 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 */ +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; - - /** - * Name of the form field for the reserve public key. - */ - field_reserve_pub; - - /** - * Name of the form field for the reserve public key. - */ - field_mint; - - /** - * The actual amount in string form. - * TODO: where is this format specified? - */ - amount_str; - - /** - * Target URL for the reserve creation request. - */ - post_url; + @Checkable.Value(AmountJson) + amount: AmountJson; /** * Mint URL where the bank should create the reserve. */ - mint; + @Checkable.String + mint: string; + + static checked: (obj: any) => CreateReserveRequest; } -export interface ConfirmReserveResponse { - backlink?: string; - success: boolean; +@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 { + /** + * Public key of then reserve that should be marked + * as confirmed. + */ + @Checkable.String + reservePub: string; + + static checked: (obj: any) => ConfirmReserveRequest; +} + + +@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 { - 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); + /** + * Create a reserve, but do not flag it as confirmed yet. + */ + createReserve(req: CreateReserveRequest): Promise { + const reservePriv = EddsaPrivateKey.create(); + const reservePub = reservePriv.getPublicKey(); - if (!requestedAmount) { - throw Error(`unrecognized amount ${req.amount_str}.`); - } + const now = (new Date).getTime(); + const canonMint = canonicalizeBaseUrl(req.mint); - 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 - }; + 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, + }; - if (hresp.status != 200) { - resp.success = false; - return resp; - } - let historyEntry = { - type: "create-reserve", - timestamp: now, - detail: { - requestedAmount, - reservePub: reserveRecord.reserve_pub, - } - }; + const historyEntry = { + type: "create-reserve", + timestamp: now, + detail: { + requestedAmount: req.amount, + reservePub: reserveRecord.reserve_pub, + } + }; - resp.success = true; + return Query(this.db) + .put("reserves", reserveRecord) + .put("history", historyEntry) + .finish() + .then(() => { + let r: CreateReserveResponse = { + mint: canonMint, + reservePub: reservePub.toCrock(), + }; + return r; + }); + } - 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 { + 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 */ -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 */ +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 @@ - - Taler Wallet: Select Taler Provider - - - - - + + Taler Wallet: Select Taler Provider + + + + + - + -
- -

Select Taler Provider

-
+
+ +

Select Taler Provider

+
- + -
+
-
-

- You asked to withdraw (loading...) from your bank account. -

-

- 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. -

+
+

+ You asked to withdraw (loading...) from your + bank account. +

+

+ 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. +

-
-
- - -
- -
-
+
+
+ + +
+ +
+
-
- +
+ 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 */ -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 */ +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) + }; - const cb = (resp) => { - if (resp.success === true) { - document.location.href = resp.backlink; + if (!d.mint) { + // FIXME: indicate error instead! + throw Error("mint missing"); + } + + 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 */ - - function replacer(match, pIndent, pKey, pVal, pEnd) { - var key = ''; - var val = ''; - var str = ''; - var r = pIndent || ''; - if (pKey) - r = r + key + pKey.replace(/[": ]/g, '') + ': '; - if (pVal) - r = r + (pVal[0] == '"' ? str : val) + pVal + ''; - return r + (pEnd || ''); + var key = ''; + var val = ''; + var str = ''; + var r = pIndent || ''; + if (pKey) + r = r + key + pKey.replace(/[": ]/g, '') + ': '; + if (pVal) + r = r + (pVal[0] == '"' ? str : val) + pVal + ''; + return r + (pEnd || ''); } - - function prettyPrint(obj) { - var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg; - return JSON.stringify(obj, null, 3) - .replace(/&/g, '&').replace(/\\"/g, '"') - .replace(//g, '>') - .replace(jsonLine, replacer); + var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg; + return JSON.stringify(obj, null, 3) + .replace(/&/g, '&').replace(/\\"/g, '"') + .replace(//g, '>') + .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 + */ + + +function replacer(match, pIndent, pKey, pVal, pEnd) { + var key = ''; + var val = ''; + var str = ''; + var r = pIndent || ''; + if (pKey) + r = r + key + pKey.replace(/[": ]/g, '') + ': '; + if (pVal) + r = r + (pVal[0] == '"' ? str : val) + pVal + ''; + return r + (pEnd || ''); +} + + +function prettyPrint(obj) { + var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*)?([,[{])?$/mg; + return JSON.stringify(obj, null, 3) + .replace(/&/g, '&').replace(/\\"/g, '"') + .replace(//g, '>') + .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"