new reserve creation protocol

This commit is contained in:
Florian Dold 2016-02-09 21:56:06 +01:00
parent 42a0076f59
commit 5e85cd8b8f
19 changed files with 719 additions and 292 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
*.zip
*.xpi
_build/

View File

@ -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";

View File

@ -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);

View File

@ -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" />
/**
* 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 ...

View File

@ -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}",
],
};

View File

@ -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;
}
}

View File

@ -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) => {

View File

@ -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;
/**
* 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;
}

View File

@ -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,70 +407,80 @@ 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);
/**
* Create a reserve, but do not flag it as confirmed yet.
*/
createReserve(req: CreateReserveRequest): Promise<CreateReserveResponse> {
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 = {
const reserveRecord = {
reserve_pub: reservePub.toCrock(),
reserve_priv: reservePriv.toCrock(),
mint_base_url: mintBaseUrl,
mint_base_url: canonMint,
created: now,
last_query: null,
current_amount: null,
// XXX: set to actual amount
requested_amount: null
requested_amount: req.amount,
confirmed: false,
};
if (hresp.status != 200) {
resp.success = false;
return resp;
}
let historyEntry = {
const historyEntry = {
type: "create-reserve",
timestamp: now,
detail: {
requestedAmount,
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;
});
}
/**
* 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(reserveRecord);
return resp;
this.initReserve(r);
});
});
}

View File

@ -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() {

View File

@ -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});

View File

@ -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}`;
}

View File

@ -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": {

View File

@ -1,43 +1,50 @@
<!doctype html>
<html>
<head>
<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>
</head>
<body>
<body>
<header>
<header>
<div id="logo"></div>
<h1>Select Taler Provider</h1>
</header>
</header>
<aside class="sidebar" id="left">
</aside>
<aside class="sidebar" id="left">
</aside>
<section id="main">
<section id="main">
<article>
<p>
You asked to withdraw <span id="show-amount">(loading...)</span> from your bank account.
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.
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>
<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>
</section>
</body>
</html>

View File

@ -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";
}
}
});

View File

@ -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)
};
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);
});
}

View File

@ -13,8 +13,6 @@
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>';
@ -26,8 +24,6 @@ function replacer(match, pIndent, pKey, pVal, pEnd) {
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)
@ -35,10 +31,9 @@ function prettyPrint(obj) {
.replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(jsonLine, replacer);
}
document.addEventListener("DOMContentLoaded", (e) => {
chrome.runtime.sendMessage({type:'dump-db'}, (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

View File

@ -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);
});
});

View File

@ -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"