Merge branch 'master' of taler.net:/var/git/wallet-webex
This commit is contained in:
commit
51d6f9fd94
@ -16,6 +16,8 @@
|
||||
|
||||
/**
|
||||
* Entry point for the background page.
|
||||
*
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
@ -34,4 +36,4 @@ System.import("../lib/wallet/wxMessaging")
|
||||
.catch((e) => {
|
||||
console.log("wallet failed");
|
||||
console.error(e.stack);
|
||||
});
|
||||
});
|
||||
|
@ -13,24 +13,50 @@
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
/// <reference path="../lib/decl/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.
|
||||
*
|
||||
* @author Florian Dold
|
||||
*/
|
||||
/// <reference path="../lib/decl/chrome/chrome.d.ts" />
|
||||
"use strict";
|
||||
// Make sure we don't pollute the namespace too much.
|
||||
var TalerNotify;
|
||||
(function (TalerNotify) {
|
||||
var PROTOCOL_VERSION = 1;
|
||||
console.log("Taler injected");
|
||||
console.log("Taler injected", chrome.runtime.id);
|
||||
// FIXME: only do this for test wallets?
|
||||
// This is no security risk, since the extension ID for published
|
||||
// extension is publicly known.
|
||||
function subst(url, H_contract) {
|
||||
url = url.replace("${H_contract}", H_contract);
|
||||
url = url.replace("${$}", "$");
|
||||
return url;
|
||||
}
|
||||
var handlers = [];
|
||||
var port = chrome.runtime.connect();
|
||||
port.onDisconnect.addListener(function () {
|
||||
console.log("chrome runtime disconnected");
|
||||
for (var _i = 0, handlers_1 = handlers; _i < handlers_1.length; _i++) {
|
||||
var handler = handlers_1[_i];
|
||||
document.removeEventListener(handler.type, handler.listener);
|
||||
}
|
||||
});
|
||||
var $ = function (x) { return document.getElementById(x); };
|
||||
document.addEventListener("taler-probe", function (e) {
|
||||
function addHandler(type, listener) {
|
||||
document.addEventListener(type, listener);
|
||||
handlers.push({ type: type, listener: listener });
|
||||
}
|
||||
addHandler("taler-query-id", function (e) {
|
||||
var evt = new CustomEvent("taler-id", {
|
||||
detail: {
|
||||
id: chrome.runtime.id
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(evt);
|
||||
});
|
||||
addHandler("taler-probe", function (e) {
|
||||
var evt = new CustomEvent("taler-wallet-present", {
|
||||
detail: {
|
||||
walletProtocolVersion: PROTOCOL_VERSION
|
||||
@ -39,18 +65,18 @@ var TalerNotify;
|
||||
document.dispatchEvent(evt);
|
||||
console.log("handshake done");
|
||||
});
|
||||
document.addEventListener("taler-create-reserve", function (e) {
|
||||
addHandler("taler-create-reserve", function (e) {
|
||||
console.log("taler-create-reserve with " + JSON.stringify(e.detail));
|
||||
var params = {
|
||||
amount: JSON.stringify(e.detail.amount),
|
||||
callback_url: URI(e.detail.callback_url).absoluteTo(document.location.href),
|
||||
callback_url: URI(e.detail.callback_url)
|
||||
.absoluteTo(document.location.href),
|
||||
bank_url: document.location.href,
|
||||
suggested_mint: e.detail.suggested_mint,
|
||||
};
|
||||
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) {
|
||||
addHandler("taler-confirm-reserve", function (e) {
|
||||
console.log("taler-confirm-reserve with " + JSON.stringify(e.detail));
|
||||
var msg = {
|
||||
type: "confirm-reserve",
|
||||
@ -62,7 +88,8 @@ var TalerNotify;
|
||||
console.log("confirm reserve done");
|
||||
});
|
||||
});
|
||||
document.addEventListener("taler-contract", function (e) {
|
||||
// XXX: remove in a bit, just here for compatibility ...
|
||||
addHandler("taler-contract", function (e) {
|
||||
// XXX: the merchant should just give us the parsed data ...
|
||||
var offer = JSON.parse(e.detail);
|
||||
if (!offer.contract) {
|
||||
@ -96,7 +123,50 @@ var TalerNotify;
|
||||
}
|
||||
});
|
||||
});
|
||||
document.addEventListener('taler-execute-payment', function (e) {
|
||||
addHandler("taler-confirm-contract", function (e) {
|
||||
if (!e.detail.contract_wrapper) {
|
||||
console.error("contract wrapper missing");
|
||||
return;
|
||||
}
|
||||
var offer = e.detail.contract_wrapper;
|
||||
if (!offer.contract) {
|
||||
console.error("contract field missing");
|
||||
return;
|
||||
}
|
||||
var msg = {
|
||||
type: "check-repurchase",
|
||||
detail: {
|
||||
contract: offer.contract
|
||||
},
|
||||
};
|
||||
chrome.runtime.sendMessage(msg, function (resp) {
|
||||
if (resp.error) {
|
||||
console.error("wallet backend error", resp);
|
||||
return;
|
||||
}
|
||||
if (resp.isRepurchase) {
|
||||
console.log("doing repurchase");
|
||||
console.assert(resp.existingFulfillmentUrl);
|
||||
console.assert(resp.existingContractHash);
|
||||
window.location.href = subst(resp.existingFulfillmentUrl, resp.existingContractHash);
|
||||
}
|
||||
else {
|
||||
var uri = URI(chrome.extension.getURL("pages/confirm-contract.html"));
|
||||
var params = {
|
||||
offer: JSON.stringify(offer),
|
||||
merchantPageUrl: document.location.href,
|
||||
};
|
||||
var target = uri.query(params).href();
|
||||
if (e.detail.replace_navigation === true) {
|
||||
document.location.replace(target);
|
||||
}
|
||||
else {
|
||||
document.location.href = target;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
addHandler('taler-execute-payment', function (e) {
|
||||
console.log("got taler-execute-payment in content page");
|
||||
if (!e.detail.pay_url) {
|
||||
console.log("field 'pay_url' missing in taler-execute-payment event");
|
||||
|
@ -14,21 +14,28 @@
|
||||
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/// <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.
|
||||
*
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
|
||||
/// <reference path="../lib/decl/chrome/chrome.d.ts" />
|
||||
|
||||
"use strict";
|
||||
|
||||
// Make sure we don't pollute the namespace too much.
|
||||
namespace TalerNotify {
|
||||
const PROTOCOL_VERSION = 1;
|
||||
|
||||
console.log("Taler injected");
|
||||
console.log("Taler injected", chrome.runtime.id);
|
||||
|
||||
// FIXME: only do this for test wallets?
|
||||
// This is no security risk, since the extension ID for published
|
||||
// extension is publicly known.
|
||||
|
||||
function subst(url: string, H_contract) {
|
||||
url = url.replace("${H_contract}", H_contract);
|
||||
@ -36,9 +43,34 @@ namespace TalerNotify {
|
||||
return url;
|
||||
}
|
||||
|
||||
let handlers = [];
|
||||
|
||||
let port = chrome.runtime.connect();
|
||||
port.onDisconnect.addListener(() => {
|
||||
console.log("chrome runtime disconnected");
|
||||
for (let handler of handlers) {
|
||||
document.removeEventListener(handler.type, handler.listener);
|
||||
}
|
||||
});
|
||||
|
||||
let $ = (x) => document.getElementById(x);
|
||||
|
||||
document.addEventListener("taler-probe", function(e) {
|
||||
function addHandler(type, listener) {
|
||||
document.addEventListener(type, listener);
|
||||
handlers.push({type, listener});
|
||||
}
|
||||
|
||||
|
||||
addHandler("taler-query-id", function(e) {
|
||||
let evt = new CustomEvent("taler-id", {
|
||||
detail: {
|
||||
id: chrome.runtime.id
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(evt);
|
||||
});
|
||||
|
||||
addHandler("taler-probe", function(e) {
|
||||
let evt = new CustomEvent("taler-wallet-present", {
|
||||
detail: {
|
||||
walletProtocolVersion: PROTOCOL_VERSION
|
||||
@ -48,19 +80,19 @@ namespace TalerNotify {
|
||||
console.log("handshake done");
|
||||
});
|
||||
|
||||
document.addEventListener("taler-create-reserve", function(e: CustomEvent) {
|
||||
addHandler("taler-create-reserve", function(e: CustomEvent) {
|
||||
console.log("taler-create-reserve with " + JSON.stringify(e.detail));
|
||||
let params = {
|
||||
amount: JSON.stringify(e.detail.amount),
|
||||
callback_url: URI(e.detail.callback_url).absoluteTo(document.location.href),
|
||||
callback_url: URI(e.detail.callback_url)
|
||||
.absoluteTo(document.location.href),
|
||||
bank_url: document.location.href,
|
||||
suggested_mint: e.detail.suggested_mint,
|
||||
};
|
||||
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) {
|
||||
addHandler("taler-confirm-reserve", function(e: CustomEvent) {
|
||||
console.log("taler-confirm-reserve with " + JSON.stringify(e.detail));
|
||||
let msg = {
|
||||
type: "confirm-reserve",
|
||||
@ -74,7 +106,8 @@ namespace TalerNotify {
|
||||
});
|
||||
|
||||
|
||||
document.addEventListener("taler-contract", function(e: CustomEvent) {
|
||||
// XXX: remove in a bit, just here for compatibility ...
|
||||
addHandler("taler-contract", function(e: CustomEvent) {
|
||||
// XXX: the merchant should just give us the parsed data ...
|
||||
let offer = JSON.parse(e.detail);
|
||||
|
||||
@ -114,7 +147,56 @@ namespace TalerNotify {
|
||||
});
|
||||
|
||||
|
||||
document.addEventListener('taler-execute-payment', function(e: CustomEvent) {
|
||||
addHandler("taler-confirm-contract", function(e: CustomEvent) {
|
||||
if (!e.detail.contract_wrapper) {
|
||||
console.error("contract wrapper missing");
|
||||
return;
|
||||
}
|
||||
|
||||
let offer = e.detail.contract_wrapper;
|
||||
|
||||
if (!offer.contract) {
|
||||
console.error("contract field missing");
|
||||
return;
|
||||
}
|
||||
|
||||
let msg = {
|
||||
type: "check-repurchase",
|
||||
detail: {
|
||||
contract: offer.contract
|
||||
},
|
||||
};
|
||||
|
||||
chrome.runtime.sendMessage(msg, (resp) => {
|
||||
if (resp.error) {
|
||||
console.error("wallet backend error", resp);
|
||||
return;
|
||||
}
|
||||
if (resp.isRepurchase) {
|
||||
console.log("doing repurchase");
|
||||
console.assert(resp.existingFulfillmentUrl);
|
||||
console.assert(resp.existingContractHash);
|
||||
window.location.href = subst(resp.existingFulfillmentUrl,
|
||||
resp.existingContractHash);
|
||||
|
||||
} else {
|
||||
let uri = URI(chrome.extension.getURL("pages/confirm-contract.html"));
|
||||
let params = {
|
||||
offer: JSON.stringify(offer),
|
||||
merchantPageUrl: document.location.href,
|
||||
};
|
||||
let target = uri.query(params).href();
|
||||
if (e.detail.replace_navigation === true) {
|
||||
document.location.replace(target);
|
||||
} else {
|
||||
document.location.href = target;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
addHandler('taler-execute-payment', function(e: CustomEvent) {
|
||||
console.log("got taler-execute-payment in content page");
|
||||
if (!e.detail.pay_url) {
|
||||
console.log("field 'pay_url' missing in taler-execute-payment event");
|
||||
|
@ -25,6 +25,8 @@
|
||||
* development
|
||||
* - package: create Chrome extension zip file in
|
||||
* build/.
|
||||
*
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
const gulp = require("gulp");
|
||||
@ -56,6 +58,7 @@ const paths = {
|
||||
"img/*",
|
||||
"style/*.css",
|
||||
"lib/vendor/*",
|
||||
"lib/i18n-strings.js",
|
||||
"lib/emscripten/libwrapper.js",
|
||||
"lib/module-trampoline.js",
|
||||
"popup/**/*.{html,css}",
|
||||
|
2
lib/decl/mithril.d.ts
vendored
2
lib/decl/mithril.d.ts
vendored
@ -608,7 +608,7 @@ declare module _mithril {
|
||||
*
|
||||
* @see m.component
|
||||
*/
|
||||
controller: MithrilControllerFunction<T> |
|
||||
controller?: MithrilControllerFunction<T> |
|
||||
MithrilControllerConstructor<T>;
|
||||
|
||||
/**
|
||||
|
@ -8472,7 +8472,7 @@ function _TALER_WRALL_sign_deposit_permission($h_contract,$h_wire,$timestamp,$re
|
||||
}
|
||||
return (0)|0;
|
||||
}
|
||||
function _TALER_WR_verify_confirmation($h_contract,$h_wire,$timestamp,$refund,$0,$1,$amount_minus_fee,$coin_pub,$merchant_pub,$sig,$mint_pub) {
|
||||
function _TALER_WR_verify_confirmation($h_contract,$h_wire,$timestamp,$refund,$0,$1,$amount_minus_fee,$coin_pub,$merchant_pub,$sig,$exchange_pub) {
|
||||
$h_contract = $h_contract|0;
|
||||
$h_wire = $h_wire|0;
|
||||
$timestamp = $timestamp|0;
|
||||
@ -8483,7 +8483,7 @@ function _TALER_WR_verify_confirmation($h_contract,$h_wire,$timestamp,$refund,$0
|
||||
$coin_pub = $coin_pub|0;
|
||||
$merchant_pub = $merchant_pub|0;
|
||||
$sig = $sig|0;
|
||||
$mint_pub = $mint_pub|0;
|
||||
$exchange_pub = $exchange_pub|0;
|
||||
var $10 = 0, $11 = 0, $12 = 0, $13 = 0, $14 = 0, $15 = 0, $16 = 0, $17 = 0, $18 = 0, $19 = 0, $2 = 0, $20 = 0, $21 = 0, $22 = 0, $23 = 0, $24 = 0, $25 = 0, $26 = 0, $27 = 0, $28 = 0;
|
||||
var $29 = 0, $3 = 0, $30 = 0, $31 = 0, $32 = 0, $33 = 0, $34 = 0, $35 = 0, $36 = 0, $37 = 0, $38 = 0, $39 = 0, $4 = 0, $40 = 0, $41 = 0, $42 = 0, $43 = 0, $44 = 0, $45 = 0, $46 = 0;
|
||||
var $47 = 0, $48 = 0, $49 = 0, $5 = 0, $50 = 0, $51 = 0, $52 = 0, $53 = 0, $54 = 0, $55 = 0, $56 = 0, $57 = 0, $58 = 0, $59 = 0, $6 = 0, $60 = 0, $61 = 0, $62 = 0, $63 = 0, $64 = 0;
|
||||
@ -8514,7 +8514,7 @@ function _TALER_WR_verify_confirmation($h_contract,$h_wire,$timestamp,$refund,$0
|
||||
$9 = $coin_pub;
|
||||
$10 = $merchant_pub;
|
||||
$11 = $sig;
|
||||
$12 = $mint_pub;
|
||||
$12 = $exchange_pub;
|
||||
$19 = ((($dc)) + 8|0);
|
||||
$20 = $3;
|
||||
dest=$19; src=$20; stop=dest+64|0; do { HEAP8[dest>>0]=HEAP8[src>>0]|0; dest=dest+1|0; src=src+1|0; } while ((dest|0) < (stop|0));
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
/**
|
||||
* Boilerplate to initialize the module system and call main()
|
||||
*
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
@ -75,4 +77,4 @@ System.import(me)
|
||||
.catch((e) => {
|
||||
console.log("trampoline failed");
|
||||
console.error(e.stack);
|
||||
});
|
||||
});
|
||||
|
25
lib/vendor/mithril.js
vendored
25
lib/vendor/mithril.js
vendored
@ -1,3 +1,28 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Leo Horie
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
;(function (global, factory) { // eslint-disable-line
|
||||
"use strict"
|
||||
/* eslint-disable no-undef */
|
||||
|
@ -15,6 +15,12 @@
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* API to access the Taler crypto worker thread.
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
|
||||
import {PreCoin} from "./types";
|
||||
import {Reserve} from "./types";
|
||||
import {Denomination} from "./types";
|
||||
@ -90,4 +96,4 @@ export class CryptoApi {
|
||||
rsaUnblind(sig: string, bk: string, pk: string): Promise<string> {
|
||||
return this.doRpc("rsaUnblind", sig, bk, pk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@
|
||||
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import {Denomination} from "./types";
|
||||
/**
|
||||
* Web worker for crypto operations.
|
||||
* @author Florian Dold
|
||||
@ -28,6 +27,7 @@ import create = chrome.alarms.create;
|
||||
import {Offer} from "./wallet";
|
||||
import {CoinWithDenom} from "./wallet";
|
||||
import {CoinPaySig} from "./types";
|
||||
import {Denomination} from "./types";
|
||||
|
||||
|
||||
export function main(worker: Worker) {
|
||||
@ -101,7 +101,7 @@ namespace RpcFunctions {
|
||||
coinPub: coinPub.toCrock(),
|
||||
coinPriv: coinPriv.toCrock(),
|
||||
denomPub: denomPub.encode().toCrock(),
|
||||
mintBaseUrl: reserve.mint_base_url,
|
||||
exchangeBaseUrl: reserve.exchange_base_url,
|
||||
withdrawSig: sig.toCrock(),
|
||||
coinEv: ev.toCrock(),
|
||||
coinValue: denom.value
|
||||
|
@ -25,7 +25,7 @@
|
||||
*/
|
||||
|
||||
const DB_NAME = "taler";
|
||||
const DB_VERSION = 1;
|
||||
const DB_VERSION = 5;
|
||||
|
||||
/**
|
||||
* Return a promise that resolves
|
||||
@ -45,12 +45,13 @@ export function openTalerDb(): Promise<IDBDatabase> {
|
||||
console.log("DB: upgrade needed: oldVersion = " + e.oldVersion);
|
||||
switch (e.oldVersion) {
|
||||
case 0: // DB does not exist yet
|
||||
const mints = db.createObjectStore("mints", {keyPath: "baseUrl"});
|
||||
mints.createIndex("pubKey", "masterPublicKey");
|
||||
const exchanges = db.createObjectStore("exchanges",
|
||||
{keyPath: "baseUrl"});
|
||||
exchanges.createIndex("pubKey", "masterPublicKey");
|
||||
db.createObjectStore("reserves", {keyPath: "reserve_pub"});
|
||||
db.createObjectStore("denoms", {keyPath: "denomPub"});
|
||||
const coins = db.createObjectStore("coins", {keyPath: "coinPub"});
|
||||
coins.createIndex("mintBaseUrl", "mintBaseUrl");
|
||||
coins.createIndex("exchangeBaseUrl", "exchangeBaseUrl");
|
||||
const transactions = db.createObjectStore("transactions",
|
||||
{keyPath: "contractHash"});
|
||||
transactions.createIndex("repurchase",
|
||||
@ -68,6 +69,15 @@ export function openTalerDb(): Promise<IDBDatabase> {
|
||||
});
|
||||
history.createIndex("timestamp", "timestamp");
|
||||
break;
|
||||
default:
|
||||
if (e.oldVersion != DB_VERSION) {
|
||||
window.alert("Incompatible wallet dababase version, please reset" +
|
||||
" db.");
|
||||
chrome.browserAction.setBadgeText({text: "R!"});
|
||||
chrome.browserAction.setBadgeBackgroundColor({color: "#F00"});
|
||||
throw Error("incompatible DB");
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -18,6 +18,8 @@
|
||||
/**
|
||||
* Smaller helper functions that do not depend
|
||||
* on the emscripten machinery.
|
||||
*
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
import {AmountJson} from "./types";
|
||||
@ -36,7 +38,7 @@ export function amountToPretty(amount: AmountJson): string {
|
||||
|
||||
|
||||
/**
|
||||
* Canonicalize a base url, typically for the mint.
|
||||
* Canonicalize a base url, typically for the exchange.
|
||||
*
|
||||
* See http://api.taler.net/wallet.html#general
|
||||
*/
|
||||
@ -62,4 +64,4 @@ export function parsePrettyAmount(pretty: string): AmountJson {
|
||||
fraction: res[2] ? (parseFloat(`0.${res[2]}`) * 1e-6) : 0,
|
||||
currency: res[3]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,4 +81,4 @@ export class RequestException {
|
||||
constructor(detail) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,8 @@
|
||||
* are defined in types.ts are intended to be used by components
|
||||
* that do not depend on the whole wallet implementation (which depends on
|
||||
* emscripten).
|
||||
*
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
import {Checkable} from "./checkable";
|
||||
@ -43,11 +45,11 @@ export class AmountJson {
|
||||
@Checkable.Class
|
||||
export class CreateReserveResponse {
|
||||
/**
|
||||
* Mint URL where the bank should create the reserve.
|
||||
* Exchange URL where the bank should create the reserve.
|
||||
* The URL is canonicalized in the response.
|
||||
*/
|
||||
@Checkable.String
|
||||
mint: string;
|
||||
exchange: string;
|
||||
|
||||
@Checkable.String
|
||||
reservePub: string;
|
||||
@ -95,14 +97,14 @@ export class Denomination {
|
||||
}
|
||||
|
||||
|
||||
export interface IMintInfo {
|
||||
export interface IExchangeInfo {
|
||||
baseUrl: string;
|
||||
masterPublicKey: string;
|
||||
denoms: Denomination[];
|
||||
}
|
||||
|
||||
export interface ReserveCreationInfo {
|
||||
mintInfo: IMintInfo;
|
||||
exchangeInfo: IExchangeInfo;
|
||||
selectedDenoms: Denomination[];
|
||||
withdrawFee: AmountJson;
|
||||
overhead: AmountJson;
|
||||
@ -117,13 +119,13 @@ export interface PreCoin {
|
||||
blindingKey: string;
|
||||
withdrawSig: string;
|
||||
coinEv: string;
|
||||
mintBaseUrl: string;
|
||||
exchangeBaseUrl: string;
|
||||
coinValue: AmountJson;
|
||||
}
|
||||
|
||||
|
||||
export interface Reserve {
|
||||
mint_base_url: string
|
||||
exchange_base_url: string
|
||||
reserve_priv: string;
|
||||
reserve_pub: string;
|
||||
}
|
||||
@ -144,7 +146,70 @@ export interface Coin {
|
||||
denomPub: string;
|
||||
denomSig: string;
|
||||
currentAmount: AmountJson;
|
||||
mintBaseUrl: string;
|
||||
exchangeBaseUrl: string;
|
||||
}
|
||||
|
||||
|
||||
@Checkable.Class
|
||||
export class ExchangeHandle {
|
||||
@Checkable.String
|
||||
master_pub: string;
|
||||
|
||||
@Checkable.String
|
||||
url: string;
|
||||
|
||||
static checked: (obj: any) => ExchangeHandle;
|
||||
}
|
||||
|
||||
|
||||
@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(ExchangeHandle))
|
||||
exchanges: ExchangeHandle[];
|
||||
|
||||
@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;
|
||||
|
||||
@Checkable.Optional(Checkable.String)
|
||||
repurchase_correlation_id: string;
|
||||
|
||||
static checked: (obj: any) => Contract;
|
||||
}
|
||||
|
||||
|
||||
@ -266,4 +331,4 @@ export interface CheckRepurchaseResult {
|
||||
|
||||
export interface Notifier {
|
||||
notify();
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
import {AmountJson, CreateReserveResponse, IMintInfo, Denomination, Notifier} from "./types";
|
||||
import {AmountJson, CreateReserveResponse, IExchangeInfo, Denomination, Notifier} from "./types";
|
||||
import {HttpResponse, RequestException} from "./http";
|
||||
import {Query} from "./query";
|
||||
import {Checkable} from "./checkable";
|
||||
@ -33,6 +33,8 @@ import {CryptoApi} from "./cryptoApi";
|
||||
import {Coin} from "./types";
|
||||
import {PayCoinInfo} from "./types";
|
||||
import {CheckRepurchaseResult} from "./types";
|
||||
import {Contract} from "./types";
|
||||
import {ExchangeHandle} from "./types";
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -70,7 +72,7 @@ export class KeysJson {
|
||||
}
|
||||
|
||||
|
||||
class MintInfo implements IMintInfo {
|
||||
class ExchangeInfo implements IExchangeInfo {
|
||||
baseUrl: string;
|
||||
masterPublicKey: string;
|
||||
denoms: Denomination[];
|
||||
@ -89,15 +91,15 @@ class MintInfo implements IMintInfo {
|
||||
}
|
||||
}
|
||||
|
||||
static fresh(baseUrl: string): MintInfo {
|
||||
return new MintInfo({baseUrl});
|
||||
static fresh(baseUrl: string): ExchangeInfo {
|
||||
return new ExchangeInfo({baseUrl});
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge new key information into the mint info.
|
||||
* Merge new key information into the exchange info.
|
||||
* If the new key information is invalid (missing fields,
|
||||
* invalid signatures), an exception is thrown, but the
|
||||
* mint info is updated with the new information up until
|
||||
* exchange info is updated with the new information up until
|
||||
* the first error.
|
||||
*/
|
||||
mergeKeys(newKeys: KeysJson, cryptoApi: CryptoApi): Promise<void> {
|
||||
@ -160,10 +162,10 @@ export class CreateReserveRequest {
|
||||
amount: AmountJson;
|
||||
|
||||
/**
|
||||
* Mint URL where the bank should create the reserve.
|
||||
* Exchange URL where the bank should create the reserve.
|
||||
*/
|
||||
@Checkable.String
|
||||
mint: string;
|
||||
exchange: string;
|
||||
|
||||
static checked: (obj: any) => CreateReserveRequest;
|
||||
}
|
||||
@ -182,68 +184,6 @@ export class ConfirmReserveRequest {
|
||||
}
|
||||
|
||||
|
||||
@Checkable.Class
|
||||
export class MintHandle {
|
||||
@Checkable.String
|
||||
master_pub: string;
|
||||
|
||||
@Checkable.String
|
||||
url: string;
|
||||
|
||||
static checked: (obj: any) => MintHandle;
|
||||
}
|
||||
|
||||
|
||||
@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(MintHandle))
|
||||
mints: MintHandle[];
|
||||
|
||||
@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;
|
||||
|
||||
@Checkable.Optional(Checkable.String)
|
||||
repurchase_correlation_id: string;
|
||||
|
||||
static checked: (obj: any) => Contract;
|
||||
}
|
||||
|
||||
|
||||
@Checkable.Class
|
||||
export class Offer {
|
||||
@ -264,8 +204,8 @@ interface ConfirmPayRequest {
|
||||
offer: Offer;
|
||||
}
|
||||
|
||||
interface MintCoins {
|
||||
[mintUrl: string]: CoinWithDenom[];
|
||||
interface ExchangeCoins {
|
||||
[exchangeUrl: string]: CoinWithDenom[];
|
||||
}
|
||||
|
||||
|
||||
@ -402,22 +342,22 @@ export class Wallet {
|
||||
|
||||
|
||||
/**
|
||||
* Get mints and associated coins that are still spendable,
|
||||
* Get exchanges and associated coins that are still spendable,
|
||||
* but only if the sum the coins' remaining value exceeds the payment amount.
|
||||
*/
|
||||
private getPossibleMintCoins(paymentAmount: AmountJson,
|
||||
private getPossibleExchangeCoins(paymentAmount: AmountJson,
|
||||
depositFeeLimit: AmountJson,
|
||||
allowedMints: MintHandle[]): Promise<MintCoins> {
|
||||
// Mapping from mint base URL to list of coins together with their
|
||||
allowedExchanges: ExchangeHandle[]): Promise<ExchangeCoins> {
|
||||
// Mapping from exchange base URL to list of coins together with their
|
||||
// denomination
|
||||
let m: MintCoins = {};
|
||||
let m: ExchangeCoins = {};
|
||||
|
||||
function storeMintCoin(mc) {
|
||||
let mint: IMintInfo = mc[0];
|
||||
function storeExchangeCoin(mc) {
|
||||
let exchange: IExchangeInfo = mc[0];
|
||||
let coin: Coin = mc[1];
|
||||
let cd = {
|
||||
coin: coin,
|
||||
denom: mint.denoms.find((e) => e.denom_pub === coin.denomPub)
|
||||
denom: exchange.denoms.find((e) => e.denom_pub === coin.denomPub)
|
||||
};
|
||||
if (!cd.denom) {
|
||||
throw Error("denom not found (database inconsistent)");
|
||||
@ -426,36 +366,36 @@ export class Wallet {
|
||||
console.warn("same pubkey for different currencies");
|
||||
return;
|
||||
}
|
||||
let x = m[mint.baseUrl];
|
||||
let x = m[exchange.baseUrl];
|
||||
if (!x) {
|
||||
m[mint.baseUrl] = [cd];
|
||||
m[exchange.baseUrl] = [cd];
|
||||
} else {
|
||||
x.push(cd);
|
||||
}
|
||||
}
|
||||
|
||||
let ps = allowedMints.map((info) => {
|
||||
console.log("Checking for merchant's mint", JSON.stringify(info));
|
||||
let ps = allowedExchanges.map((info) => {
|
||||
console.log("Checking for merchant's exchange", JSON.stringify(info));
|
||||
return Query(this.db)
|
||||
.iter("mints", {indexName: "pubKey", only: info.master_pub})
|
||||
.indexJoin("coins", "mintBaseUrl", (mint) => mint.baseUrl)
|
||||
.reduce(storeMintCoin);
|
||||
.iter("exchanges", {indexName: "pubKey", only: info.master_pub})
|
||||
.indexJoin("coins", "exchangeBaseUrl", (exchange) => exchange.baseUrl)
|
||||
.reduce(storeExchangeCoin);
|
||||
});
|
||||
|
||||
return Promise.all(ps).then(() => {
|
||||
let ret: MintCoins = {};
|
||||
let ret: ExchangeCoins = {};
|
||||
|
||||
if (Object.keys(m).length == 0) {
|
||||
console.log("not suitable mints found");
|
||||
console.log("not suitable exchanges found");
|
||||
}
|
||||
|
||||
console.dir(m);
|
||||
|
||||
// We try to find the first mint where we have
|
||||
// We try to find the first exchange where we have
|
||||
// enough coins to cover the paymentAmount with fees
|
||||
// under depositFeeLimit
|
||||
|
||||
nextMint:
|
||||
nextExchange:
|
||||
for (let key in m) {
|
||||
let coins = m[key];
|
||||
// Sort by ascending deposit fee
|
||||
@ -479,12 +419,12 @@ export class Wallet {
|
||||
// FIXME: if the fees are too high, we have
|
||||
// to cover them ourselves ....
|
||||
console.log("too much fees");
|
||||
continue nextMint;
|
||||
continue nextExchange;
|
||||
}
|
||||
usableCoins.push(coins[i]);
|
||||
if (Amounts.cmp(accAmount, minAmount) >= 0) {
|
||||
ret[key] = usableCoins;
|
||||
continue nextMint;
|
||||
continue nextExchange;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -499,14 +439,14 @@ export class Wallet {
|
||||
*/
|
||||
private recordConfirmPay(offer: Offer,
|
||||
payCoinInfo: PayCoinInfo,
|
||||
chosenMint: string): Promise<void> {
|
||||
chosenExchange: string): Promise<void> {
|
||||
let payReq = {};
|
||||
payReq["amount"] = offer.contract.amount;
|
||||
payReq["coins"] = payCoinInfo.map((x) => x.sig);
|
||||
payReq["H_contract"] = offer.H_contract;
|
||||
payReq["max_fee"] = offer.contract.max_fee;
|
||||
payReq["merchant_sig"] = offer.merchant_sig;
|
||||
payReq["mint"] = URI(chosenMint).href();
|
||||
payReq["exchange"] = URI(chosenExchange).href();
|
||||
payReq["refund_deadline"] = offer.contract.refund_deadline;
|
||||
payReq["timestamp"] = offer.contract.timestamp;
|
||||
payReq["transaction_id"] = offer.contract.transaction_id;
|
||||
@ -549,9 +489,9 @@ export class Wallet {
|
||||
confirmPay(offer: Offer): Promise<any> {
|
||||
console.log("executing confirmPay");
|
||||
return Promise.resolve().then(() => {
|
||||
return this.getPossibleMintCoins(offer.contract.amount,
|
||||
return this.getPossibleExchangeCoins(offer.contract.amount,
|
||||
offer.contract.max_fee,
|
||||
offer.contract.mints)
|
||||
offer.contract.exchanges)
|
||||
}).then((mcs) => {
|
||||
if (Object.keys(mcs).length == 0) {
|
||||
console.log("not confirming payment, insufficient coins");
|
||||
@ -559,10 +499,10 @@ export class Wallet {
|
||||
error: "coins-insufficient",
|
||||
};
|
||||
}
|
||||
let mintUrl = Object.keys(mcs)[0];
|
||||
let exchangeUrl = Object.keys(mcs)[0];
|
||||
|
||||
return this.cryptoApi.signDeposit(offer, mcs[mintUrl])
|
||||
.then((ds) => this.recordConfirmPay(offer, ds, mintUrl))
|
||||
return this.cryptoApi.signDeposit(offer, mcs[exchangeUrl])
|
||||
.then((ds) => this.recordConfirmPay(offer, ds, exchangeUrl))
|
||||
.then(() => ({}));
|
||||
});
|
||||
}
|
||||
@ -599,11 +539,11 @@ export class Wallet {
|
||||
* then deplete the reserve, withdrawing coins until it is empty.
|
||||
*/
|
||||
private initReserve(reserveRecord) {
|
||||
this.updateMintFromUrl(reserveRecord.mint_base_url)
|
||||
.then((mint) =>
|
||||
this.updateReserve(reserveRecord.reserve_pub, mint)
|
||||
this.updateExchangeFromUrl(reserveRecord.exchange_base_url)
|
||||
.then((exchange) =>
|
||||
this.updateReserve(reserveRecord.reserve_pub, exchange)
|
||||
.then((reserve) => this.depleteReserve(reserve,
|
||||
mint)))
|
||||
exchange)))
|
||||
.then(() => {
|
||||
let depleted = {
|
||||
type: "depleted-reserve",
|
||||
@ -627,12 +567,12 @@ export class Wallet {
|
||||
createReserve(req: CreateReserveRequest): Promise<CreateReserveResponse> {
|
||||
return this.cryptoApi.createEddsaKeypair().then((keypair) => {
|
||||
const now = (new Date).getTime();
|
||||
const canonMint = canonicalizeBaseUrl(req.mint);
|
||||
const canonExchange = canonicalizeBaseUrl(req.exchange);
|
||||
|
||||
const reserveRecord = {
|
||||
reserve_pub: keypair.pub,
|
||||
reserve_priv: keypair.priv,
|
||||
mint_base_url: canonMint,
|
||||
exchange_base_url: canonExchange,
|
||||
created: now,
|
||||
last_query: null,
|
||||
current_amount: null,
|
||||
@ -656,7 +596,7 @@ export class Wallet {
|
||||
.finish()
|
||||
.then(() => {
|
||||
let r: CreateReserveResponse = {
|
||||
mint: canonMint,
|
||||
exchange: canonExchange,
|
||||
reservePub: keypair.pub,
|
||||
};
|
||||
return r;
|
||||
@ -668,7 +608,7 @@ export class Wallet {
|
||||
/**
|
||||
* 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
|
||||
* since the exchange 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
|
||||
@ -708,7 +648,7 @@ export class Wallet {
|
||||
wd.reserve_pub = pc.reservePub;
|
||||
wd.reserve_sig = pc.withdrawSig;
|
||||
wd.coin_ev = pc.coinEv;
|
||||
let reqUrl = URI("reserve/withdraw").absoluteTo(r.mint_base_url);
|
||||
let reqUrl = URI("reserve/withdraw").absoluteTo(r.exchange_base_url);
|
||||
return this.http.postJson(reqUrl, wd);
|
||||
})
|
||||
.then(resp => {
|
||||
@ -727,7 +667,7 @@ export class Wallet {
|
||||
denomPub: pc.denomPub,
|
||||
denomSig: denomSig,
|
||||
currentAmount: pc.coinValue,
|
||||
mintBaseUrl: pc.mintBaseUrl,
|
||||
exchangeBaseUrl: pc.exchangeBaseUrl,
|
||||
};
|
||||
return coin;
|
||||
|
||||
@ -775,8 +715,8 @@ export class Wallet {
|
||||
/**
|
||||
* Withdraw coins from a reserve until it is empty.
|
||||
*/
|
||||
private depleteReserve(reserve, mint: MintInfo): Promise<void> {
|
||||
let denomsAvailable: Denomination[] = copy(mint.denoms);
|
||||
private depleteReserve(reserve, exchange: ExchangeInfo): Promise<void> {
|
||||
let denomsAvailable: Denomination[] = copy(exchange.denoms);
|
||||
let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount,
|
||||
denomsAvailable);
|
||||
|
||||
@ -793,13 +733,13 @@ export class Wallet {
|
||||
|
||||
/**
|
||||
* Update the information about a reserve that is stored in the wallet
|
||||
* by quering the reserve's mint.
|
||||
* by quering the reserve's exchange.
|
||||
*/
|
||||
private updateReserve(reservePub: string, mint: MintInfo): Promise<Reserve> {
|
||||
private updateReserve(reservePub: string, exchange: ExchangeInfo): Promise<Reserve> {
|
||||
return Query(this.db)
|
||||
.get("reserves", reservePub)
|
||||
.then((reserve) => {
|
||||
let reqUrl = URI("reserve/status").absoluteTo(mint.baseUrl);
|
||||
let reqUrl = URI("reserve/status").absoluteTo(exchange.baseUrl);
|
||||
reqUrl.query({'reserve_pub': reservePub});
|
||||
return this.http.get(reqUrl).then(resp => {
|
||||
if (resp.status != 200) {
|
||||
@ -832,10 +772,10 @@ export class Wallet {
|
||||
|
||||
getReserveCreationInfo(baseUrl: string,
|
||||
amount: AmountJson): Promise<ReserveCreationInfo> {
|
||||
return this.updateMintFromUrl(baseUrl)
|
||||
.then((mintInfo: IMintInfo) => {
|
||||
return this.updateExchangeFromUrl(baseUrl)
|
||||
.then((exchangeInfo: IExchangeInfo) => {
|
||||
let selectedDenoms = getWithdrawDenomList(amount,
|
||||
mintInfo.denoms);
|
||||
exchangeInfo.denoms);
|
||||
|
||||
let acc = Amounts.getZero(amount.currency);
|
||||
for (let d of selectedDenoms) {
|
||||
@ -846,7 +786,7 @@ export class Wallet {
|
||||
d.fee_withdraw).amount)
|
||||
.reduce((a, b) => Amounts.add(a, b).amount);
|
||||
let ret: ReserveCreationInfo = {
|
||||
mintInfo,
|
||||
exchangeInfo,
|
||||
selectedDenoms,
|
||||
withdrawFee: acc,
|
||||
overhead: Amounts.sub(amount, actualCoinCost).amount,
|
||||
@ -857,37 +797,37 @@ export class Wallet {
|
||||
|
||||
|
||||
/**
|
||||
* Update or add mint DB entry by fetching the /keys information.
|
||||
* Update or add exchange DB entry by fetching the /keys information.
|
||||
* Optionally link the reserve entry to the new or existing
|
||||
* mint entry in then DB.
|
||||
* exchange entry in then DB.
|
||||
*/
|
||||
updateMintFromUrl(baseUrl): Promise<MintInfo> {
|
||||
updateExchangeFromUrl(baseUrl): Promise<ExchangeInfo> {
|
||||
baseUrl = canonicalizeBaseUrl(baseUrl);
|
||||
let reqUrl = URI("keys").absoluteTo(baseUrl);
|
||||
return this.http.get(reqUrl).then((resp) => {
|
||||
if (resp.status != 200) {
|
||||
throw Error("/keys request failed");
|
||||
}
|
||||
let mintKeysJson = KeysJson.checked(JSON.parse(resp.responseText));
|
||||
let exchangeKeysJson = KeysJson.checked(JSON.parse(resp.responseText));
|
||||
|
||||
return Query(this.db).get("mints", baseUrl).then((r) => {
|
||||
let mintInfo;
|
||||
return Query(this.db).get("exchanges", baseUrl).then((r) => {
|
||||
let exchangeInfo;
|
||||
console.dir(r);
|
||||
|
||||
if (!r) {
|
||||
mintInfo = MintInfo.fresh(baseUrl);
|
||||
console.log("making fresh mint");
|
||||
exchangeInfo = ExchangeInfo.fresh(baseUrl);
|
||||
console.log("making fresh exchange");
|
||||
} else {
|
||||
mintInfo = new MintInfo(r);
|
||||
console.log("using old mint");
|
||||
exchangeInfo = new ExchangeInfo(r);
|
||||
console.log("using old exchange");
|
||||
}
|
||||
|
||||
return mintInfo.mergeKeys(mintKeysJson, this.cryptoApi)
|
||||
return exchangeInfo.mergeKeys(exchangeKeysJson, this.cryptoApi)
|
||||
.then(() => {
|
||||
return Query(this.db)
|
||||
.put("mints", mintInfo)
|
||||
.put("exchanges", exchangeInfo)
|
||||
.finish()
|
||||
.then(() => mintInfo);
|
||||
.then(() => exchangeInfo);
|
||||
});
|
||||
|
||||
});
|
||||
@ -912,14 +852,17 @@ export class Wallet {
|
||||
|
||||
return Query(this.db)
|
||||
.iter("coins")
|
||||
.reduce(collectBalances, {});
|
||||
.reduce(collectBalances, {})
|
||||
.then(byCurrency => {
|
||||
return {balances: byCurrency};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrive the full event history for this wallet.
|
||||
*/
|
||||
getHistory(): Promise<any[]> {
|
||||
getHistory(): Promise<any> {
|
||||
function collect(x, acc) {
|
||||
acc.push(x);
|
||||
return acc;
|
||||
@ -928,6 +871,7 @@ export class Wallet {
|
||||
return Query(this.db)
|
||||
.iter("history", {indexName: "timestamp"})
|
||||
.reduce(collect, [])
|
||||
.then(acc => ({history: acc}));
|
||||
}
|
||||
|
||||
checkRepurchase(contract: Contract): Promise<CheckRepurchaseResult> {
|
||||
@ -954,4 +898,4 @@ export class Wallet {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import {ReserveCreationInfo} from "./types";
|
||||
|
||||
/**
|
||||
* Interface to the wallet through WebExtension messaging.
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
|
||||
@ -37,4 +38,4 @@ export function getReserveCreationInfo(baseUrl: string,
|
||||
resolve(resp);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import {Checkable} from "./checkable";
|
||||
import {AmountJson} from "./types";
|
||||
import Port = chrome.runtime.Port;
|
||||
import {Notifier} from "./types";
|
||||
import {Contract} from "./wallet";
|
||||
import {Contract} from "./types";
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -47,9 +47,11 @@ function makeHandlers(db: IDBDatabase,
|
||||
return exportDb(db);
|
||||
},
|
||||
["reset"]: function(detail) {
|
||||
let tx = db.transaction(db.objectStoreNames, 'readwrite');
|
||||
for (let i = 0; i < db.objectStoreNames.length; i++) {
|
||||
tx.objectStore(db.objectStoreNames[i]).clear();
|
||||
if (db) {
|
||||
let tx = db.transaction(db.objectStoreNames, 'readwrite');
|
||||
for (let i = 0; i < db.objectStoreNames.length; i++) {
|
||||
tx.objectStore(db.objectStoreNames[i]).clear();
|
||||
}
|
||||
}
|
||||
deleteDb();
|
||||
|
||||
@ -60,7 +62,7 @@ function makeHandlers(db: IDBDatabase,
|
||||
},
|
||||
["create-reserve"]: function(detail) {
|
||||
const d = {
|
||||
mint: detail.mint,
|
||||
exchange: detail.exchange,
|
||||
amount: detail.amount,
|
||||
};
|
||||
const req = CreateReserveRequest.checked(d);
|
||||
@ -96,11 +98,11 @@ function makeHandlers(db: IDBDatabase,
|
||||
["execute-payment"]: function(detail) {
|
||||
return wallet.executePayment(detail.H_contract);
|
||||
},
|
||||
["mint-info"]: function(detail) {
|
||||
["exchange-info"]: function(detail) {
|
||||
if (!detail.baseUrl) {
|
||||
return Promise.resolve({error: "bad url"});
|
||||
}
|
||||
return wallet.updateMintFromUrl(detail.baseUrl);
|
||||
return wallet.updateExchangeFromUrl(detail.baseUrl);
|
||||
},
|
||||
["reserve-creation-info"]: function(detail) {
|
||||
if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
|
||||
@ -193,6 +195,19 @@ class ChromeNotifier implements Notifier {
|
||||
export function wxMain() {
|
||||
chrome.browserAction.setBadgeText({text: ""});
|
||||
|
||||
chrome.tabs.query({}, function(tabs) {
|
||||
for (let tab of tabs) {
|
||||
if (!tab.url) {
|
||||
return;
|
||||
}
|
||||
let uri = URI(tab.url);
|
||||
if (uri.protocol() == "http" || uri.protocol() == "https") {
|
||||
console.log("injecting into existing tab", tab.id);
|
||||
chrome.tabs.executeScript(tab.id, {file: "content_scripts/notify.js"});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => {
|
||||
return openTalerDb();
|
||||
|
@ -2,7 +2,7 @@
|
||||
"description": "Privacy preserving and transparent payments",
|
||||
"manifest_version": 2,
|
||||
"name": "GNU Taler Wallet (git)",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.15",
|
||||
|
||||
"applications": {
|
||||
"gecko": {
|
||||
@ -12,6 +12,7 @@
|
||||
|
||||
"permissions": [
|
||||
"storage",
|
||||
"tabs",
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
],
|
||||
|
@ -14,12 +14,21 @@
|
||||
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Page shown to the user to confirm entering
|
||||
* a contract.
|
||||
*
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
|
||||
/// <reference path="../lib/decl/handlebars/handlebars.d.ts" />
|
||||
import MithrilComponent = _mithril.MithrilComponent;
|
||||
import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
|
||||
import m from "mithril";
|
||||
import {Contract} from "../lib/wallet/types";
|
||||
"use strict";
|
||||
|
||||
import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
|
||||
|
||||
declare var m: any;
|
||||
|
||||
function prettyAmount(amount) {
|
||||
let v = amount.value + amount.fraction / 1e6;
|
||||
@ -27,6 +36,35 @@ function prettyAmount(amount) {
|
||||
}
|
||||
|
||||
|
||||
const Details = {
|
||||
controller() {
|
||||
return {collapsed: m.prop(true)};
|
||||
},
|
||||
view(ctrl, contract: Contract) {
|
||||
if (ctrl.collapsed()) {
|
||||
return m("div", [
|
||||
m("button.linky", {
|
||||
onclick: () => {
|
||||
ctrl.collapsed(false);
|
||||
}
|
||||
}, "show more details")
|
||||
]);
|
||||
} else {
|
||||
return m("div", [
|
||||
m("button.linky", {
|
||||
onclick: () => {
|
||||
ctrl.collapsed(true);
|
||||
}
|
||||
}, "show less details"),
|
||||
m("div", [
|
||||
"Accepted exchanges:",
|
||||
m("ul", contract.exchanges.map(e => m("li", `${e.url}: ${e.master_pub}`)))
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function main() {
|
||||
let url = URI(document.location.href);
|
||||
let query: any = URI.parseQuery(url.query());
|
||||
@ -51,6 +89,7 @@ export function main() {
|
||||
`${p.description}: ${prettyAmount(p.price)}`))),
|
||||
m("button.confirm-pay", {onclick: doPayment}, i18n`Confirm Payment`),
|
||||
m("p", error ? error : []),
|
||||
m(Details, contract)
|
||||
];
|
||||
}
|
||||
};
|
||||
@ -79,4 +118,4 @@ export function main() {
|
||||
offer);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,7 @@
|
||||
<head>
|
||||
<title>Taler Wallet: Select Taler Provider</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../style/lang.css">
|
||||
<link rel="stylesheet" type="text/css" href="popup.css">
|
||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css">
|
||||
|
||||
<script src="../lib/vendor/URI.js"></script>
|
||||
<script src="../lib/vendor/mithril.js"></script>
|
||||
@ -27,7 +26,7 @@
|
||||
|
||||
<section id="main">
|
||||
<article>
|
||||
<div class="fade" id="mint-selection"></div>
|
||||
<div class="fade" id="exchange-selection"></div>
|
||||
</article>
|
||||
</section>
|
||||
</body>
|
||||
|
@ -14,12 +14,20 @@
|
||||
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Page shown to the user to confirm creation
|
||||
* of a reserve, usually requested by the bank.
|
||||
*
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
/// <reference path="../lib/decl/mithril.d.ts" />
|
||||
|
||||
import {amountToPretty, canonicalizeBaseUrl} from "../lib/wallet/helpers";
|
||||
import {AmountJson, CreateReserveResponse} from "../lib/wallet/types";
|
||||
import m from "mithril";
|
||||
import {IMintInfo} from "../lib/wallet/types";
|
||||
import {IExchangeInfo} from "../lib/wallet/types";
|
||||
import {ReserveCreationInfo, Amounts} from "../lib/wallet/types";
|
||||
import MithrilComponent = _mithril.MithrilComponent;
|
||||
import {Denomination} from "../lib/wallet/types";
|
||||
@ -60,7 +68,7 @@ class DelayTimer {
|
||||
class Controller {
|
||||
url = m.prop<string>();
|
||||
statusString = null;
|
||||
isValidMint = false;
|
||||
isValidExchange = false;
|
||||
reserveCreationInfo: ReserveCreationInfo = null;
|
||||
private timer: DelayTimer;
|
||||
private request: XMLHttpRequest;
|
||||
@ -68,12 +76,12 @@ class Controller {
|
||||
callbackUrl: string;
|
||||
detailCollapsed = m.prop<boolean>(true);
|
||||
|
||||
constructor(initialMintUrl: string, amount: AmountJson, callbackUrl: string) {
|
||||
constructor(initialExchangeUrl: string, amount: AmountJson, callbackUrl: string) {
|
||||
console.log("creating main controller");
|
||||
this.amount = amount;
|
||||
this.callbackUrl = callbackUrl;
|
||||
this.timer = new DelayTimer(800, () => this.update());
|
||||
this.url(initialMintUrl);
|
||||
this.url(initialExchangeUrl);
|
||||
this.update();
|
||||
}
|
||||
|
||||
@ -93,19 +101,19 @@ class Controller {
|
||||
|
||||
m.redraw(true);
|
||||
|
||||
console.log("doing get mint info");
|
||||
console.log("doing get exchange info");
|
||||
|
||||
getReserveCreationInfo(this.url(), this.amount)
|
||||
.then((r: ReserveCreationInfo) => {
|
||||
console.log("get mint info resolved");
|
||||
this.isValidMint = true;
|
||||
console.log("get exchange info resolved");
|
||||
this.isValidExchange = true;
|
||||
this.reserveCreationInfo = r;
|
||||
console.dir(r);
|
||||
this.statusString = "The mint base URL is valid!";
|
||||
this.statusString = "The exchange base URL is valid!";
|
||||
m.endComputation();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log("get mint info rejected");
|
||||
console.log("get exchange info rejected");
|
||||
if (e.hasOwnProperty("httpStatus")) {
|
||||
this.statusString = `request failed with status ${this.request.status}`;
|
||||
} else {
|
||||
@ -122,7 +130,7 @@ class Controller {
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.isValidMint = false;
|
||||
this.isValidExchange = false;
|
||||
this.statusString = null;
|
||||
this.reserveCreationInfo = null;
|
||||
if (this.request) {
|
||||
@ -131,8 +139,8 @@ class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
confirmReserve(mint: string, amount: AmountJson, callback_url: string) {
|
||||
const d = {mint, amount};
|
||||
confirmReserve(exchange: string, amount: AmountJson, callback_url: string) {
|
||||
const d = {exchange, amount};
|
||||
const cb = (rawResp) => {
|
||||
if (!rawResp) {
|
||||
throw Error("empty response");
|
||||
@ -140,7 +148,7 @@ class Controller {
|
||||
if (!rawResp.error) {
|
||||
const resp = CreateReserveResponse.checked(rawResp);
|
||||
let q = {
|
||||
mint: resp.mint,
|
||||
exchange: resp.exchange,
|
||||
reserve_pub: resp.reservePub,
|
||||
amount_value: amount.value,
|
||||
amount_fraction: amount.fraction,
|
||||
@ -190,7 +198,7 @@ function view(ctrl: Controller) {
|
||||
onclick: () => ctrl.confirmReserve(ctrl.url(),
|
||||
ctrl.amount,
|
||||
ctrl.callbackUrl),
|
||||
disabled: !ctrl.isValidMint
|
||||
disabled: !ctrl.isValidExchange
|
||||
},
|
||||
"Confirm exchange selection");
|
||||
|
||||
@ -256,30 +264,30 @@ function renderReserveCreationDetails(rci: ReserveCreationInfo) {
|
||||
}
|
||||
|
||||
|
||||
interface MintProbeResult {
|
||||
interface ExchangeProbeResult {
|
||||
keyInfo?: any;
|
||||
}
|
||||
|
||||
function probeMint(mintBaseUrl: string): Promise<MintProbeResult> {
|
||||
function probeExchange(exchangeBaseUrl: string): Promise<ExchangeProbeResult> {
|
||||
throw Error("not implemented");
|
||||
}
|
||||
|
||||
|
||||
function getSuggestedMint(currency: string): Promise<string> {
|
||||
function getSuggestedExchange(currency: string): Promise<string> {
|
||||
// TODO: make this request go to the wallet backend
|
||||
// Right now, this is a stub.
|
||||
const defaultMint = {
|
||||
const defaultExchange = {
|
||||
"KUDOS": "http://exchange.demo.taler.net",
|
||||
"PUDOS": "http://exchange.test.taler.net",
|
||||
};
|
||||
|
||||
let mint = defaultMint[currency];
|
||||
let exchange = defaultExchange[currency];
|
||||
|
||||
if (!mint) {
|
||||
mint = ""
|
||||
if (!exchange) {
|
||||
exchange = ""
|
||||
}
|
||||
|
||||
return Promise.resolve(mint);
|
||||
return Promise.resolve(exchange);
|
||||
}
|
||||
|
||||
|
||||
@ -290,11 +298,11 @@ export function main() {
|
||||
const callback_url = query.callback_url;
|
||||
const bank_url = query.bank_url;
|
||||
|
||||
getSuggestedMint(amount.currency)
|
||||
.then((suggestedMintUrl) => {
|
||||
const controller = () => new Controller(suggestedMintUrl, amount, callback_url);
|
||||
var MintSelection = {controller, view};
|
||||
m.mount(document.getElementById("mint-selection"), MintSelection);
|
||||
getSuggestedExchange(amount.currency)
|
||||
.then((suggestedExchangeUrl) => {
|
||||
const controller = () => new Controller(suggestedExchangeUrl, amount, callback_url);
|
||||
var ExchangeSelection = {controller, view};
|
||||
m.mount(document.getElementById("exchange-selection"), ExchangeSelection);
|
||||
})
|
||||
.catch((e) => {
|
||||
// TODO: provide more context information, maybe factor it out into a
|
||||
|
@ -15,6 +15,12 @@
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Wallet database dump for debugging.
|
||||
*
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
function replacer(match, pIndent, pKey, pVal, pEnd) {
|
||||
var key = '<span class=json-key>';
|
||||
var val = '<span class=json-value>';
|
||||
|
@ -20,6 +20,8 @@
|
||||
*
|
||||
* Note that duplicate message IDs are NOT merged, to get the same output as
|
||||
* you would from xgettext, just run msguniq.
|
||||
*
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
/// <reference path="../lib/decl/node.d.ts" />
|
||||
|
@ -1,3 +1,10 @@
|
||||
|
||||
/**
|
||||
* @author Gabor X. Toth
|
||||
* @author Marcello Stanisci
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
body {
|
||||
min-height: 20em;
|
||||
width: 30em;
|
||||
|
@ -15,6 +15,14 @@
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Popup shown to the user when they click
|
||||
* the Taler browser action button.
|
||||
*
|
||||
* @author Florian Dold
|
||||
*/
|
||||
|
||||
|
||||
/// <reference path="../lib/decl/mithril.d.ts" />
|
||||
/// <reference path="../lib/decl/lodash.d.ts" />
|
||||
|
||||
@ -87,6 +95,7 @@ namespace WalletBalance {
|
||||
|
||||
class Controller {
|
||||
myWallet;
|
||||
gotError = false;
|
||||
|
||||
constructor() {
|
||||
this.updateBalance();
|
||||
@ -96,9 +105,16 @@ namespace WalletBalance {
|
||||
|
||||
updateBalance() {
|
||||
m.startComputation();
|
||||
chrome.runtime.sendMessage({type: "balances"}, (wallet) => {
|
||||
console.log("got wallet", wallet);
|
||||
this.myWallet = wallet;
|
||||
chrome.runtime.sendMessage({type: "balances"}, (resp) => {
|
||||
if (resp.error) {
|
||||
this.gotError = true;
|
||||
console.error("could not retrieve balances", resp);
|
||||
m.endComputation();
|
||||
return;
|
||||
}
|
||||
this.gotError = false;
|
||||
console.log("got wallet", resp);
|
||||
this.myWallet = resp.balances;
|
||||
m.endComputation();
|
||||
});
|
||||
}
|
||||
@ -106,6 +122,9 @@ namespace WalletBalance {
|
||||
|
||||
export function view(ctrl: Controller) {
|
||||
let wallet = ctrl.myWallet;
|
||||
if (ctrl.gotError) {
|
||||
return i18n`Error: could not retrieve balance information.`;
|
||||
}
|
||||
if (!wallet) {
|
||||
throw Error("Could not retrieve wallet");
|
||||
}
|
||||
@ -192,6 +211,7 @@ namespace WalletHistory {
|
||||
|
||||
class Controller {
|
||||
myHistory;
|
||||
gotError = false;
|
||||
|
||||
constructor() {
|
||||
this.update();
|
||||
@ -201,8 +221,15 @@ namespace WalletHistory {
|
||||
update() {
|
||||
m.startComputation();
|
||||
chrome.runtime.sendMessage({type: "get-history"}, (resp) => {
|
||||
console.log("got history", history);
|
||||
this.myHistory = resp;
|
||||
if (resp.error) {
|
||||
this.gotError = true;
|
||||
console.error("could not retrieve history", resp);
|
||||
m.endComputation();
|
||||
return;
|
||||
}
|
||||
this.gotError = false;
|
||||
console.log("got history", resp.history);
|
||||
this.myHistory = resp.history;
|
||||
m.endComputation();
|
||||
});
|
||||
}
|
||||
@ -210,6 +237,9 @@ namespace WalletHistory {
|
||||
|
||||
export function view(ctrl: Controller) {
|
||||
let history = ctrl.myHistory;
|
||||
if (ctrl.gotError) {
|
||||
return i18n`Error: could not retrieve event history`;
|
||||
}
|
||||
if (!history) {
|
||||
throw Error("Could not retrieve history");
|
||||
}
|
||||
|
21
test/integration/tests.py
Executable file
21
test/integration/tests.py
Executable file
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import unittest
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
|
||||
class PythonOrgSearch(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.driver = webdriver.Chrome()
|
||||
|
||||
def test_taler_reachable(self):
|
||||
driver = self.driver
|
||||
driver.get("https://bank.demo.taler.net")
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
self.driver.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
Reference in New Issue
Block a user