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.
|
* Entry point for the background page.
|
||||||
|
*
|
||||||
|
* @author Florian Dold
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
@ -13,24 +13,50 @@
|
|||||||
You should have received a copy of the GNU General Public License along with
|
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/>
|
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
|
* Script that is injected into (all!) pages to allow them
|
||||||
* to interact with the GNU Taler wallet via DOM Events.
|
* 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.
|
// Make sure we don't pollute the namespace too much.
|
||||||
var TalerNotify;
|
var TalerNotify;
|
||||||
(function (TalerNotify) {
|
(function (TalerNotify) {
|
||||||
var PROTOCOL_VERSION = 1;
|
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) {
|
function subst(url, H_contract) {
|
||||||
url = url.replace("${H_contract}", H_contract);
|
url = url.replace("${H_contract}", H_contract);
|
||||||
url = url.replace("${$}", "$");
|
url = url.replace("${$}", "$");
|
||||||
return url;
|
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); };
|
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", {
|
var evt = new CustomEvent("taler-wallet-present", {
|
||||||
detail: {
|
detail: {
|
||||||
walletProtocolVersion: PROTOCOL_VERSION
|
walletProtocolVersion: PROTOCOL_VERSION
|
||||||
@ -39,18 +65,18 @@ var TalerNotify;
|
|||||||
document.dispatchEvent(evt);
|
document.dispatchEvent(evt);
|
||||||
console.log("handshake done");
|
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));
|
console.log("taler-create-reserve with " + JSON.stringify(e.detail));
|
||||||
var params = {
|
var params = {
|
||||||
amount: JSON.stringify(e.detail.amount),
|
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,
|
bank_url: document.location.href,
|
||||||
suggested_mint: e.detail.suggested_mint,
|
|
||||||
};
|
};
|
||||||
var uri = URI(chrome.extension.getURL("pages/confirm-create-reserve.html"));
|
var uri = URI(chrome.extension.getURL("pages/confirm-create-reserve.html"));
|
||||||
document.location.href = uri.query(params).href();
|
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));
|
console.log("taler-confirm-reserve with " + JSON.stringify(e.detail));
|
||||||
var msg = {
|
var msg = {
|
||||||
type: "confirm-reserve",
|
type: "confirm-reserve",
|
||||||
@ -62,7 +88,8 @@ var TalerNotify;
|
|||||||
console.log("confirm reserve done");
|
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 ...
|
// XXX: the merchant should just give us the parsed data ...
|
||||||
var offer = JSON.parse(e.detail);
|
var offer = JSON.parse(e.detail);
|
||||||
if (!offer.contract) {
|
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");
|
console.log("got taler-execute-payment in content page");
|
||||||
if (!e.detail.pay_url) {
|
if (!e.detail.pay_url) {
|
||||||
console.log("field 'pay_url' missing in taler-execute-payment event");
|
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/>
|
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
|
* Script that is injected into (all!) pages to allow them
|
||||||
* to interact with the GNU Taler wallet via DOM Events.
|
* 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.
|
// Make sure we don't pollute the namespace too much.
|
||||||
namespace TalerNotify {
|
namespace TalerNotify {
|
||||||
const PROTOCOL_VERSION = 1;
|
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) {
|
function subst(url: string, H_contract) {
|
||||||
url = url.replace("${H_contract}", H_contract);
|
url = url.replace("${H_contract}", H_contract);
|
||||||
@ -36,9 +43,34 @@ namespace TalerNotify {
|
|||||||
return url;
|
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);
|
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", {
|
let evt = new CustomEvent("taler-wallet-present", {
|
||||||
detail: {
|
detail: {
|
||||||
walletProtocolVersion: PROTOCOL_VERSION
|
walletProtocolVersion: PROTOCOL_VERSION
|
||||||
@ -48,19 +80,19 @@ namespace TalerNotify {
|
|||||||
console.log("handshake done");
|
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));
|
console.log("taler-create-reserve with " + JSON.stringify(e.detail));
|
||||||
let params = {
|
let params = {
|
||||||
amount: JSON.stringify(e.detail.amount),
|
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,
|
bank_url: document.location.href,
|
||||||
suggested_mint: e.detail.suggested_mint,
|
|
||||||
};
|
};
|
||||||
let uri = URI(chrome.extension.getURL("pages/confirm-create-reserve.html"));
|
let uri = URI(chrome.extension.getURL("pages/confirm-create-reserve.html"));
|
||||||
document.location.href = uri.query(params).href();
|
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));
|
console.log("taler-confirm-reserve with " + JSON.stringify(e.detail));
|
||||||
let msg = {
|
let msg = {
|
||||||
type: "confirm-reserve",
|
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 ...
|
// XXX: the merchant should just give us the parsed data ...
|
||||||
let offer = JSON.parse(e.detail);
|
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");
|
console.log("got taler-execute-payment in content page");
|
||||||
if (!e.detail.pay_url) {
|
if (!e.detail.pay_url) {
|
||||||
console.log("field 'pay_url' missing in taler-execute-payment event");
|
console.log("field 'pay_url' missing in taler-execute-payment event");
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
* development
|
* development
|
||||||
* - package: create Chrome extension zip file in
|
* - package: create Chrome extension zip file in
|
||||||
* build/.
|
* build/.
|
||||||
|
*
|
||||||
|
* @author Florian Dold
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
@ -56,6 +58,7 @@ const paths = {
|
|||||||
"img/*",
|
"img/*",
|
||||||
"style/*.css",
|
"style/*.css",
|
||||||
"lib/vendor/*",
|
"lib/vendor/*",
|
||||||
|
"lib/i18n-strings.js",
|
||||||
"lib/emscripten/libwrapper.js",
|
"lib/emscripten/libwrapper.js",
|
||||||
"lib/module-trampoline.js",
|
"lib/module-trampoline.js",
|
||||||
"popup/**/*.{html,css}",
|
"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
|
* @see m.component
|
||||||
*/
|
*/
|
||||||
controller: MithrilControllerFunction<T> |
|
controller?: MithrilControllerFunction<T> |
|
||||||
MithrilControllerConstructor<T>;
|
MithrilControllerConstructor<T>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8472,7 +8472,7 @@ function _TALER_WRALL_sign_deposit_permission($h_contract,$h_wire,$timestamp,$re
|
|||||||
}
|
}
|
||||||
return (0)|0;
|
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_contract = $h_contract|0;
|
||||||
$h_wire = $h_wire|0;
|
$h_wire = $h_wire|0;
|
||||||
$timestamp = $timestamp|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;
|
$coin_pub = $coin_pub|0;
|
||||||
$merchant_pub = $merchant_pub|0;
|
$merchant_pub = $merchant_pub|0;
|
||||||
$sig = $sig|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 $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 $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;
|
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;
|
$9 = $coin_pub;
|
||||||
$10 = $merchant_pub;
|
$10 = $merchant_pub;
|
||||||
$11 = $sig;
|
$11 = $sig;
|
||||||
$12 = $mint_pub;
|
$12 = $exchange_pub;
|
||||||
$19 = ((($dc)) + 8|0);
|
$19 = ((($dc)) + 8|0);
|
||||||
$20 = $3;
|
$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));
|
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()
|
* Boilerplate to initialize the module system and call main()
|
||||||
|
*
|
||||||
|
* @author Florian Dold
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
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
|
;(function (global, factory) { // eslint-disable-line
|
||||||
"use strict"
|
"use strict"
|
||||||
/* eslint-disable no-undef */
|
/* eslint-disable no-undef */
|
||||||
|
@ -15,6 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API to access the Taler crypto worker thread.
|
||||||
|
* @author Florian Dold
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {PreCoin} from "./types";
|
import {PreCoin} from "./types";
|
||||||
import {Reserve} from "./types";
|
import {Reserve} from "./types";
|
||||||
import {Denomination} from "./types";
|
import {Denomination} from "./types";
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
|
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Denomination} from "./types";
|
|
||||||
/**
|
/**
|
||||||
* Web worker for crypto operations.
|
* Web worker for crypto operations.
|
||||||
* @author Florian Dold
|
* @author Florian Dold
|
||||||
@ -28,6 +27,7 @@ import create = chrome.alarms.create;
|
|||||||
import {Offer} from "./wallet";
|
import {Offer} from "./wallet";
|
||||||
import {CoinWithDenom} from "./wallet";
|
import {CoinWithDenom} from "./wallet";
|
||||||
import {CoinPaySig} from "./types";
|
import {CoinPaySig} from "./types";
|
||||||
|
import {Denomination} from "./types";
|
||||||
|
|
||||||
|
|
||||||
export function main(worker: Worker) {
|
export function main(worker: Worker) {
|
||||||
@ -101,7 +101,7 @@ namespace RpcFunctions {
|
|||||||
coinPub: coinPub.toCrock(),
|
coinPub: coinPub.toCrock(),
|
||||||
coinPriv: coinPriv.toCrock(),
|
coinPriv: coinPriv.toCrock(),
|
||||||
denomPub: denomPub.encode().toCrock(),
|
denomPub: denomPub.encode().toCrock(),
|
||||||
mintBaseUrl: reserve.mint_base_url,
|
exchangeBaseUrl: reserve.exchange_base_url,
|
||||||
withdrawSig: sig.toCrock(),
|
withdrawSig: sig.toCrock(),
|
||||||
coinEv: ev.toCrock(),
|
coinEv: ev.toCrock(),
|
||||||
coinValue: denom.value
|
coinValue: denom.value
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const DB_NAME = "taler";
|
const DB_NAME = "taler";
|
||||||
const DB_VERSION = 1;
|
const DB_VERSION = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a promise that resolves
|
* Return a promise that resolves
|
||||||
@ -45,12 +45,13 @@ export function openTalerDb(): Promise<IDBDatabase> {
|
|||||||
console.log("DB: upgrade needed: oldVersion = " + e.oldVersion);
|
console.log("DB: upgrade needed: oldVersion = " + e.oldVersion);
|
||||||
switch (e.oldVersion) {
|
switch (e.oldVersion) {
|
||||||
case 0: // DB does not exist yet
|
case 0: // DB does not exist yet
|
||||||
const mints = db.createObjectStore("mints", {keyPath: "baseUrl"});
|
const exchanges = db.createObjectStore("exchanges",
|
||||||
mints.createIndex("pubKey", "masterPublicKey");
|
{keyPath: "baseUrl"});
|
||||||
|
exchanges.createIndex("pubKey", "masterPublicKey");
|
||||||
db.createObjectStore("reserves", {keyPath: "reserve_pub"});
|
db.createObjectStore("reserves", {keyPath: "reserve_pub"});
|
||||||
db.createObjectStore("denoms", {keyPath: "denomPub"});
|
db.createObjectStore("denoms", {keyPath: "denomPub"});
|
||||||
const coins = db.createObjectStore("coins", {keyPath: "coinPub"});
|
const coins = db.createObjectStore("coins", {keyPath: "coinPub"});
|
||||||
coins.createIndex("mintBaseUrl", "mintBaseUrl");
|
coins.createIndex("exchangeBaseUrl", "exchangeBaseUrl");
|
||||||
const transactions = db.createObjectStore("transactions",
|
const transactions = db.createObjectStore("transactions",
|
||||||
{keyPath: "contractHash"});
|
{keyPath: "contractHash"});
|
||||||
transactions.createIndex("repurchase",
|
transactions.createIndex("repurchase",
|
||||||
@ -68,6 +69,15 @@ export function openTalerDb(): Promise<IDBDatabase> {
|
|||||||
});
|
});
|
||||||
history.createIndex("timestamp", "timestamp");
|
history.createIndex("timestamp", "timestamp");
|
||||||
break;
|
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
|
* Smaller helper functions that do not depend
|
||||||
* on the emscripten machinery.
|
* on the emscripten machinery.
|
||||||
|
*
|
||||||
|
* @author Florian Dold
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AmountJson} from "./types";
|
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
|
* See http://api.taler.net/wallet.html#general
|
||||||
*/
|
*/
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
* are defined in types.ts are intended to be used by components
|
* are defined in types.ts are intended to be used by components
|
||||||
* that do not depend on the whole wallet implementation (which depends on
|
* that do not depend on the whole wallet implementation (which depends on
|
||||||
* emscripten).
|
* emscripten).
|
||||||
|
*
|
||||||
|
* @author Florian Dold
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Checkable} from "./checkable";
|
import {Checkable} from "./checkable";
|
||||||
@ -43,11 +45,11 @@ export class AmountJson {
|
|||||||
@Checkable.Class
|
@Checkable.Class
|
||||||
export class CreateReserveResponse {
|
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.
|
* The URL is canonicalized in the response.
|
||||||
*/
|
*/
|
||||||
@Checkable.String
|
@Checkable.String
|
||||||
mint: string;
|
exchange: string;
|
||||||
|
|
||||||
@Checkable.String
|
@Checkable.String
|
||||||
reservePub: string;
|
reservePub: string;
|
||||||
@ -95,14 +97,14 @@ export class Denomination {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IMintInfo {
|
export interface IExchangeInfo {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
masterPublicKey: string;
|
masterPublicKey: string;
|
||||||
denoms: Denomination[];
|
denoms: Denomination[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ReserveCreationInfo {
|
export interface ReserveCreationInfo {
|
||||||
mintInfo: IMintInfo;
|
exchangeInfo: IExchangeInfo;
|
||||||
selectedDenoms: Denomination[];
|
selectedDenoms: Denomination[];
|
||||||
withdrawFee: AmountJson;
|
withdrawFee: AmountJson;
|
||||||
overhead: AmountJson;
|
overhead: AmountJson;
|
||||||
@ -117,13 +119,13 @@ export interface PreCoin {
|
|||||||
blindingKey: string;
|
blindingKey: string;
|
||||||
withdrawSig: string;
|
withdrawSig: string;
|
||||||
coinEv: string;
|
coinEv: string;
|
||||||
mintBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
coinValue: AmountJson;
|
coinValue: AmountJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface Reserve {
|
export interface Reserve {
|
||||||
mint_base_url: string
|
exchange_base_url: string
|
||||||
reserve_priv: string;
|
reserve_priv: string;
|
||||||
reserve_pub: string;
|
reserve_pub: string;
|
||||||
}
|
}
|
||||||
@ -144,7 +146,70 @@ export interface Coin {
|
|||||||
denomPub: string;
|
denomPub: string;
|
||||||
denomSig: string;
|
denomSig: string;
|
||||||
currentAmount: AmountJson;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
* @author Florian Dold
|
* @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 {HttpResponse, RequestException} from "./http";
|
||||||
import {Query} from "./query";
|
import {Query} from "./query";
|
||||||
import {Checkable} from "./checkable";
|
import {Checkable} from "./checkable";
|
||||||
@ -33,6 +33,8 @@ import {CryptoApi} from "./cryptoApi";
|
|||||||
import {Coin} from "./types";
|
import {Coin} from "./types";
|
||||||
import {PayCoinInfo} from "./types";
|
import {PayCoinInfo} from "./types";
|
||||||
import {CheckRepurchaseResult} from "./types";
|
import {CheckRepurchaseResult} from "./types";
|
||||||
|
import {Contract} from "./types";
|
||||||
|
import {ExchangeHandle} from "./types";
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
@ -70,7 +72,7 @@ export class KeysJson {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class MintInfo implements IMintInfo {
|
class ExchangeInfo implements IExchangeInfo {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
masterPublicKey: string;
|
masterPublicKey: string;
|
||||||
denoms: Denomination[];
|
denoms: Denomination[];
|
||||||
@ -89,15 +91,15 @@ class MintInfo implements IMintInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fresh(baseUrl: string): MintInfo {
|
static fresh(baseUrl: string): ExchangeInfo {
|
||||||
return new MintInfo({baseUrl});
|
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,
|
* If the new key information is invalid (missing fields,
|
||||||
* invalid signatures), an exception is thrown, but the
|
* 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.
|
* the first error.
|
||||||
*/
|
*/
|
||||||
mergeKeys(newKeys: KeysJson, cryptoApi: CryptoApi): Promise<void> {
|
mergeKeys(newKeys: KeysJson, cryptoApi: CryptoApi): Promise<void> {
|
||||||
@ -160,10 +162,10 @@ export class CreateReserveRequest {
|
|||||||
amount: AmountJson;
|
amount: AmountJson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mint URL where the bank should create the reserve.
|
* Exchange URL where the bank should create the reserve.
|
||||||
*/
|
*/
|
||||||
@Checkable.String
|
@Checkable.String
|
||||||
mint: string;
|
exchange: string;
|
||||||
|
|
||||||
static checked: (obj: any) => CreateReserveRequest;
|
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
|
@Checkable.Class
|
||||||
export class Offer {
|
export class Offer {
|
||||||
@ -264,8 +204,8 @@ interface ConfirmPayRequest {
|
|||||||
offer: Offer;
|
offer: Offer;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MintCoins {
|
interface ExchangeCoins {
|
||||||
[mintUrl: string]: CoinWithDenom[];
|
[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.
|
* but only if the sum the coins' remaining value exceeds the payment amount.
|
||||||
*/
|
*/
|
||||||
private getPossibleMintCoins(paymentAmount: AmountJson,
|
private getPossibleExchangeCoins(paymentAmount: AmountJson,
|
||||||
depositFeeLimit: AmountJson,
|
depositFeeLimit: AmountJson,
|
||||||
allowedMints: MintHandle[]): Promise<MintCoins> {
|
allowedExchanges: ExchangeHandle[]): Promise<ExchangeCoins> {
|
||||||
// Mapping from mint base URL to list of coins together with their
|
// Mapping from exchange base URL to list of coins together with their
|
||||||
// denomination
|
// denomination
|
||||||
let m: MintCoins = {};
|
let m: ExchangeCoins = {};
|
||||||
|
|
||||||
function storeMintCoin(mc) {
|
function storeExchangeCoin(mc) {
|
||||||
let mint: IMintInfo = mc[0];
|
let exchange: IExchangeInfo = mc[0];
|
||||||
let coin: Coin = mc[1];
|
let coin: Coin = mc[1];
|
||||||
let cd = {
|
let cd = {
|
||||||
coin: coin,
|
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) {
|
if (!cd.denom) {
|
||||||
throw Error("denom not found (database inconsistent)");
|
throw Error("denom not found (database inconsistent)");
|
||||||
@ -426,36 +366,36 @@ export class Wallet {
|
|||||||
console.warn("same pubkey for different currencies");
|
console.warn("same pubkey for different currencies");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let x = m[mint.baseUrl];
|
let x = m[exchange.baseUrl];
|
||||||
if (!x) {
|
if (!x) {
|
||||||
m[mint.baseUrl] = [cd];
|
m[exchange.baseUrl] = [cd];
|
||||||
} else {
|
} else {
|
||||||
x.push(cd);
|
x.push(cd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ps = allowedMints.map((info) => {
|
let ps = allowedExchanges.map((info) => {
|
||||||
console.log("Checking for merchant's mint", JSON.stringify(info));
|
console.log("Checking for merchant's exchange", JSON.stringify(info));
|
||||||
return Query(this.db)
|
return Query(this.db)
|
||||||
.iter("mints", {indexName: "pubKey", only: info.master_pub})
|
.iter("exchanges", {indexName: "pubKey", only: info.master_pub})
|
||||||
.indexJoin("coins", "mintBaseUrl", (mint) => mint.baseUrl)
|
.indexJoin("coins", "exchangeBaseUrl", (exchange) => exchange.baseUrl)
|
||||||
.reduce(storeMintCoin);
|
.reduce(storeExchangeCoin);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.all(ps).then(() => {
|
return Promise.all(ps).then(() => {
|
||||||
let ret: MintCoins = {};
|
let ret: ExchangeCoins = {};
|
||||||
|
|
||||||
if (Object.keys(m).length == 0) {
|
if (Object.keys(m).length == 0) {
|
||||||
console.log("not suitable mints found");
|
console.log("not suitable exchanges found");
|
||||||
}
|
}
|
||||||
|
|
||||||
console.dir(m);
|
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
|
// enough coins to cover the paymentAmount with fees
|
||||||
// under depositFeeLimit
|
// under depositFeeLimit
|
||||||
|
|
||||||
nextMint:
|
nextExchange:
|
||||||
for (let key in m) {
|
for (let key in m) {
|
||||||
let coins = m[key];
|
let coins = m[key];
|
||||||
// Sort by ascending deposit fee
|
// Sort by ascending deposit fee
|
||||||
@ -479,12 +419,12 @@ export class Wallet {
|
|||||||
// FIXME: if the fees are too high, we have
|
// FIXME: if the fees are too high, we have
|
||||||
// to cover them ourselves ....
|
// to cover them ourselves ....
|
||||||
console.log("too much fees");
|
console.log("too much fees");
|
||||||
continue nextMint;
|
continue nextExchange;
|
||||||
}
|
}
|
||||||
usableCoins.push(coins[i]);
|
usableCoins.push(coins[i]);
|
||||||
if (Amounts.cmp(accAmount, minAmount) >= 0) {
|
if (Amounts.cmp(accAmount, minAmount) >= 0) {
|
||||||
ret[key] = usableCoins;
|
ret[key] = usableCoins;
|
||||||
continue nextMint;
|
continue nextExchange;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -499,14 +439,14 @@ export class Wallet {
|
|||||||
*/
|
*/
|
||||||
private recordConfirmPay(offer: Offer,
|
private recordConfirmPay(offer: Offer,
|
||||||
payCoinInfo: PayCoinInfo,
|
payCoinInfo: PayCoinInfo,
|
||||||
chosenMint: string): Promise<void> {
|
chosenExchange: string): Promise<void> {
|
||||||
let payReq = {};
|
let payReq = {};
|
||||||
payReq["amount"] = offer.contract.amount;
|
payReq["amount"] = offer.contract.amount;
|
||||||
payReq["coins"] = payCoinInfo.map((x) => x.sig);
|
payReq["coins"] = payCoinInfo.map((x) => x.sig);
|
||||||
payReq["H_contract"] = offer.H_contract;
|
payReq["H_contract"] = offer.H_contract;
|
||||||
payReq["max_fee"] = offer.contract.max_fee;
|
payReq["max_fee"] = offer.contract.max_fee;
|
||||||
payReq["merchant_sig"] = offer.merchant_sig;
|
payReq["merchant_sig"] = offer.merchant_sig;
|
||||||
payReq["mint"] = URI(chosenMint).href();
|
payReq["exchange"] = URI(chosenExchange).href();
|
||||||
payReq["refund_deadline"] = offer.contract.refund_deadline;
|
payReq["refund_deadline"] = offer.contract.refund_deadline;
|
||||||
payReq["timestamp"] = offer.contract.timestamp;
|
payReq["timestamp"] = offer.contract.timestamp;
|
||||||
payReq["transaction_id"] = offer.contract.transaction_id;
|
payReq["transaction_id"] = offer.contract.transaction_id;
|
||||||
@ -549,9 +489,9 @@ export class Wallet {
|
|||||||
confirmPay(offer: Offer): Promise<any> {
|
confirmPay(offer: Offer): Promise<any> {
|
||||||
console.log("executing confirmPay");
|
console.log("executing confirmPay");
|
||||||
return Promise.resolve().then(() => {
|
return Promise.resolve().then(() => {
|
||||||
return this.getPossibleMintCoins(offer.contract.amount,
|
return this.getPossibleExchangeCoins(offer.contract.amount,
|
||||||
offer.contract.max_fee,
|
offer.contract.max_fee,
|
||||||
offer.contract.mints)
|
offer.contract.exchanges)
|
||||||
}).then((mcs) => {
|
}).then((mcs) => {
|
||||||
if (Object.keys(mcs).length == 0) {
|
if (Object.keys(mcs).length == 0) {
|
||||||
console.log("not confirming payment, insufficient coins");
|
console.log("not confirming payment, insufficient coins");
|
||||||
@ -559,10 +499,10 @@ export class Wallet {
|
|||||||
error: "coins-insufficient",
|
error: "coins-insufficient",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let mintUrl = Object.keys(mcs)[0];
|
let exchangeUrl = Object.keys(mcs)[0];
|
||||||
|
|
||||||
return this.cryptoApi.signDeposit(offer, mcs[mintUrl])
|
return this.cryptoApi.signDeposit(offer, mcs[exchangeUrl])
|
||||||
.then((ds) => this.recordConfirmPay(offer, ds, mintUrl))
|
.then((ds) => this.recordConfirmPay(offer, ds, exchangeUrl))
|
||||||
.then(() => ({}));
|
.then(() => ({}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -599,11 +539,11 @@ export class Wallet {
|
|||||||
* then deplete the reserve, withdrawing coins until it is empty.
|
* then deplete the reserve, withdrawing coins until it is empty.
|
||||||
*/
|
*/
|
||||||
private initReserve(reserveRecord) {
|
private initReserve(reserveRecord) {
|
||||||
this.updateMintFromUrl(reserveRecord.mint_base_url)
|
this.updateExchangeFromUrl(reserveRecord.exchange_base_url)
|
||||||
.then((mint) =>
|
.then((exchange) =>
|
||||||
this.updateReserve(reserveRecord.reserve_pub, mint)
|
this.updateReserve(reserveRecord.reserve_pub, exchange)
|
||||||
.then((reserve) => this.depleteReserve(reserve,
|
.then((reserve) => this.depleteReserve(reserve,
|
||||||
mint)))
|
exchange)))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
let depleted = {
|
let depleted = {
|
||||||
type: "depleted-reserve",
|
type: "depleted-reserve",
|
||||||
@ -627,12 +567,12 @@ export class Wallet {
|
|||||||
createReserve(req: CreateReserveRequest): Promise<CreateReserveResponse> {
|
createReserve(req: CreateReserveRequest): Promise<CreateReserveResponse> {
|
||||||
return this.cryptoApi.createEddsaKeypair().then((keypair) => {
|
return this.cryptoApi.createEddsaKeypair().then((keypair) => {
|
||||||
const now = (new Date).getTime();
|
const now = (new Date).getTime();
|
||||||
const canonMint = canonicalizeBaseUrl(req.mint);
|
const canonExchange = canonicalizeBaseUrl(req.exchange);
|
||||||
|
|
||||||
const reserveRecord = {
|
const reserveRecord = {
|
||||||
reserve_pub: keypair.pub,
|
reserve_pub: keypair.pub,
|
||||||
reserve_priv: keypair.priv,
|
reserve_priv: keypair.priv,
|
||||||
mint_base_url: canonMint,
|
exchange_base_url: canonExchange,
|
||||||
created: now,
|
created: now,
|
||||||
last_query: null,
|
last_query: null,
|
||||||
current_amount: null,
|
current_amount: null,
|
||||||
@ -656,7 +596,7 @@ export class Wallet {
|
|||||||
.finish()
|
.finish()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
let r: CreateReserveResponse = {
|
let r: CreateReserveResponse = {
|
||||||
mint: canonMint,
|
exchange: canonExchange,
|
||||||
reservePub: keypair.pub,
|
reservePub: keypair.pub,
|
||||||
};
|
};
|
||||||
return r;
|
return r;
|
||||||
@ -668,7 +608,7 @@ export class Wallet {
|
|||||||
/**
|
/**
|
||||||
* Mark an existing reserve as confirmed. The wallet will start trying
|
* Mark an existing reserve as confirmed. The wallet will start trying
|
||||||
* to withdraw from that reserve. This may not immediately succeed,
|
* 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.
|
* bank confirmed its creation.
|
||||||
*
|
*
|
||||||
* A confirmed reserve should be shown to the user in the UI, while
|
* 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_pub = pc.reservePub;
|
||||||
wd.reserve_sig = pc.withdrawSig;
|
wd.reserve_sig = pc.withdrawSig;
|
||||||
wd.coin_ev = pc.coinEv;
|
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);
|
return this.http.postJson(reqUrl, wd);
|
||||||
})
|
})
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
@ -727,7 +667,7 @@ export class Wallet {
|
|||||||
denomPub: pc.denomPub,
|
denomPub: pc.denomPub,
|
||||||
denomSig: denomSig,
|
denomSig: denomSig,
|
||||||
currentAmount: pc.coinValue,
|
currentAmount: pc.coinValue,
|
||||||
mintBaseUrl: pc.mintBaseUrl,
|
exchangeBaseUrl: pc.exchangeBaseUrl,
|
||||||
};
|
};
|
||||||
return coin;
|
return coin;
|
||||||
|
|
||||||
@ -775,8 +715,8 @@ export class Wallet {
|
|||||||
/**
|
/**
|
||||||
* Withdraw coins from a reserve until it is empty.
|
* Withdraw coins from a reserve until it is empty.
|
||||||
*/
|
*/
|
||||||
private depleteReserve(reserve, mint: MintInfo): Promise<void> {
|
private depleteReserve(reserve, exchange: ExchangeInfo): Promise<void> {
|
||||||
let denomsAvailable: Denomination[] = copy(mint.denoms);
|
let denomsAvailable: Denomination[] = copy(exchange.denoms);
|
||||||
let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount,
|
let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount,
|
||||||
denomsAvailable);
|
denomsAvailable);
|
||||||
|
|
||||||
@ -793,13 +733,13 @@ export class Wallet {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the information about a reserve that is stored in the 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)
|
return Query(this.db)
|
||||||
.get("reserves", reservePub)
|
.get("reserves", reservePub)
|
||||||
.then((reserve) => {
|
.then((reserve) => {
|
||||||
let reqUrl = URI("reserve/status").absoluteTo(mint.baseUrl);
|
let reqUrl = URI("reserve/status").absoluteTo(exchange.baseUrl);
|
||||||
reqUrl.query({'reserve_pub': reservePub});
|
reqUrl.query({'reserve_pub': reservePub});
|
||||||
return this.http.get(reqUrl).then(resp => {
|
return this.http.get(reqUrl).then(resp => {
|
||||||
if (resp.status != 200) {
|
if (resp.status != 200) {
|
||||||
@ -832,10 +772,10 @@ export class Wallet {
|
|||||||
|
|
||||||
getReserveCreationInfo(baseUrl: string,
|
getReserveCreationInfo(baseUrl: string,
|
||||||
amount: AmountJson): Promise<ReserveCreationInfo> {
|
amount: AmountJson): Promise<ReserveCreationInfo> {
|
||||||
return this.updateMintFromUrl(baseUrl)
|
return this.updateExchangeFromUrl(baseUrl)
|
||||||
.then((mintInfo: IMintInfo) => {
|
.then((exchangeInfo: IExchangeInfo) => {
|
||||||
let selectedDenoms = getWithdrawDenomList(amount,
|
let selectedDenoms = getWithdrawDenomList(amount,
|
||||||
mintInfo.denoms);
|
exchangeInfo.denoms);
|
||||||
|
|
||||||
let acc = Amounts.getZero(amount.currency);
|
let acc = Amounts.getZero(amount.currency);
|
||||||
for (let d of selectedDenoms) {
|
for (let d of selectedDenoms) {
|
||||||
@ -846,7 +786,7 @@ export class Wallet {
|
|||||||
d.fee_withdraw).amount)
|
d.fee_withdraw).amount)
|
||||||
.reduce((a, b) => Amounts.add(a, b).amount);
|
.reduce((a, b) => Amounts.add(a, b).amount);
|
||||||
let ret: ReserveCreationInfo = {
|
let ret: ReserveCreationInfo = {
|
||||||
mintInfo,
|
exchangeInfo,
|
||||||
selectedDenoms,
|
selectedDenoms,
|
||||||
withdrawFee: acc,
|
withdrawFee: acc,
|
||||||
overhead: Amounts.sub(amount, actualCoinCost).amount,
|
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
|
* 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);
|
baseUrl = canonicalizeBaseUrl(baseUrl);
|
||||||
let reqUrl = URI("keys").absoluteTo(baseUrl);
|
let reqUrl = URI("keys").absoluteTo(baseUrl);
|
||||||
return this.http.get(reqUrl).then((resp) => {
|
return this.http.get(reqUrl).then((resp) => {
|
||||||
if (resp.status != 200) {
|
if (resp.status != 200) {
|
||||||
throw Error("/keys request failed");
|
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) => {
|
return Query(this.db).get("exchanges", baseUrl).then((r) => {
|
||||||
let mintInfo;
|
let exchangeInfo;
|
||||||
console.dir(r);
|
console.dir(r);
|
||||||
|
|
||||||
if (!r) {
|
if (!r) {
|
||||||
mintInfo = MintInfo.fresh(baseUrl);
|
exchangeInfo = ExchangeInfo.fresh(baseUrl);
|
||||||
console.log("making fresh mint");
|
console.log("making fresh exchange");
|
||||||
} else {
|
} else {
|
||||||
mintInfo = new MintInfo(r);
|
exchangeInfo = new ExchangeInfo(r);
|
||||||
console.log("using old mint");
|
console.log("using old exchange");
|
||||||
}
|
}
|
||||||
|
|
||||||
return mintInfo.mergeKeys(mintKeysJson, this.cryptoApi)
|
return exchangeInfo.mergeKeys(exchangeKeysJson, this.cryptoApi)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return Query(this.db)
|
return Query(this.db)
|
||||||
.put("mints", mintInfo)
|
.put("exchanges", exchangeInfo)
|
||||||
.finish()
|
.finish()
|
||||||
.then(() => mintInfo);
|
.then(() => exchangeInfo);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -912,14 +852,17 @@ export class Wallet {
|
|||||||
|
|
||||||
return Query(this.db)
|
return Query(this.db)
|
||||||
.iter("coins")
|
.iter("coins")
|
||||||
.reduce(collectBalances, {});
|
.reduce(collectBalances, {})
|
||||||
|
.then(byCurrency => {
|
||||||
|
return {balances: byCurrency};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrive the full event history for this wallet.
|
* Retrive the full event history for this wallet.
|
||||||
*/
|
*/
|
||||||
getHistory(): Promise<any[]> {
|
getHistory(): Promise<any> {
|
||||||
function collect(x, acc) {
|
function collect(x, acc) {
|
||||||
acc.push(x);
|
acc.push(x);
|
||||||
return acc;
|
return acc;
|
||||||
@ -928,6 +871,7 @@ export class Wallet {
|
|||||||
return Query(this.db)
|
return Query(this.db)
|
||||||
.iter("history", {indexName: "timestamp"})
|
.iter("history", {indexName: "timestamp"})
|
||||||
.reduce(collect, [])
|
.reduce(collect, [])
|
||||||
|
.then(acc => ({history: acc}));
|
||||||
}
|
}
|
||||||
|
|
||||||
checkRepurchase(contract: Contract): Promise<CheckRepurchaseResult> {
|
checkRepurchase(contract: Contract): Promise<CheckRepurchaseResult> {
|
||||||
|
@ -19,6 +19,7 @@ import {ReserveCreationInfo} from "./types";
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to the wallet through WebExtension messaging.
|
* Interface to the wallet through WebExtension messaging.
|
||||||
|
* @author Florian Dold
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import {Checkable} from "./checkable";
|
|||||||
import {AmountJson} from "./types";
|
import {AmountJson} from "./types";
|
||||||
import Port = chrome.runtime.Port;
|
import Port = chrome.runtime.Port;
|
||||||
import {Notifier} from "./types";
|
import {Notifier} from "./types";
|
||||||
import {Contract} from "./wallet";
|
import {Contract} from "./types";
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
@ -47,10 +47,12 @@ function makeHandlers(db: IDBDatabase,
|
|||||||
return exportDb(db);
|
return exportDb(db);
|
||||||
},
|
},
|
||||||
["reset"]: function(detail) {
|
["reset"]: function(detail) {
|
||||||
|
if (db) {
|
||||||
let tx = db.transaction(db.objectStoreNames, 'readwrite');
|
let tx = db.transaction(db.objectStoreNames, 'readwrite');
|
||||||
for (let i = 0; i < db.objectStoreNames.length; i++) {
|
for (let i = 0; i < db.objectStoreNames.length; i++) {
|
||||||
tx.objectStore(db.objectStoreNames[i]).clear();
|
tx.objectStore(db.objectStoreNames[i]).clear();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
deleteDb();
|
deleteDb();
|
||||||
|
|
||||||
chrome.browserAction.setBadgeText({text: ""});
|
chrome.browserAction.setBadgeText({text: ""});
|
||||||
@ -60,7 +62,7 @@ function makeHandlers(db: IDBDatabase,
|
|||||||
},
|
},
|
||||||
["create-reserve"]: function(detail) {
|
["create-reserve"]: function(detail) {
|
||||||
const d = {
|
const d = {
|
||||||
mint: detail.mint,
|
exchange: detail.exchange,
|
||||||
amount: detail.amount,
|
amount: detail.amount,
|
||||||
};
|
};
|
||||||
const req = CreateReserveRequest.checked(d);
|
const req = CreateReserveRequest.checked(d);
|
||||||
@ -96,11 +98,11 @@ function makeHandlers(db: IDBDatabase,
|
|||||||
["execute-payment"]: function(detail) {
|
["execute-payment"]: function(detail) {
|
||||||
return wallet.executePayment(detail.H_contract);
|
return wallet.executePayment(detail.H_contract);
|
||||||
},
|
},
|
||||||
["mint-info"]: function(detail) {
|
["exchange-info"]: function(detail) {
|
||||||
if (!detail.baseUrl) {
|
if (!detail.baseUrl) {
|
||||||
return Promise.resolve({error: "bad url"});
|
return Promise.resolve({error: "bad url"});
|
||||||
}
|
}
|
||||||
return wallet.updateMintFromUrl(detail.baseUrl);
|
return wallet.updateExchangeFromUrl(detail.baseUrl);
|
||||||
},
|
},
|
||||||
["reserve-creation-info"]: function(detail) {
|
["reserve-creation-info"]: function(detail) {
|
||||||
if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
|
if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
|
||||||
@ -193,6 +195,19 @@ class ChromeNotifier implements Notifier {
|
|||||||
export function wxMain() {
|
export function wxMain() {
|
||||||
chrome.browserAction.setBadgeText({text: ""});
|
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()
|
Promise.resolve()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return openTalerDb();
|
return openTalerDb();
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"description": "Privacy preserving and transparent payments",
|
"description": "Privacy preserving and transparent payments",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "GNU Taler Wallet (git)",
|
"name": "GNU Taler Wallet (git)",
|
||||||
"version": "0.5.11",
|
"version": "0.5.15",
|
||||||
|
|
||||||
"applications": {
|
"applications": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"storage",
|
"storage",
|
||||||
|
"tabs",
|
||||||
"http://*/*",
|
"http://*/*",
|
||||||
"https://*/*"
|
"https://*/*"
|
||||||
],
|
],
|
||||||
|
@ -14,12 +14,21 @@
|
|||||||
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
|
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" />
|
/// <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";
|
"use strict";
|
||||||
|
|
||||||
import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
|
|
||||||
|
|
||||||
declare var m: any;
|
|
||||||
|
|
||||||
function prettyAmount(amount) {
|
function prettyAmount(amount) {
|
||||||
let v = amount.value + amount.fraction / 1e6;
|
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() {
|
export function main() {
|
||||||
let url = URI(document.location.href);
|
let url = URI(document.location.href);
|
||||||
let query: any = URI.parseQuery(url.query());
|
let query: any = URI.parseQuery(url.query());
|
||||||
@ -51,6 +89,7 @@ export function main() {
|
|||||||
`${p.description}: ${prettyAmount(p.price)}`))),
|
`${p.description}: ${prettyAmount(p.price)}`))),
|
||||||
m("button.confirm-pay", {onclick: doPayment}, i18n`Confirm Payment`),
|
m("button.confirm-pay", {onclick: doPayment}, i18n`Confirm Payment`),
|
||||||
m("p", error ? error : []),
|
m("p", error ? error : []),
|
||||||
|
m(Details, contract)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -4,8 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Taler Wallet: Select Taler Provider</title>
|
<title>Taler Wallet: Select Taler Provider</title>
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="../style/lang.css">
|
<link rel="stylesheet" type="text/css" href="../style/wallet.css">
|
||||||
<link rel="stylesheet" type="text/css" href="popup.css">
|
|
||||||
|
|
||||||
<script src="../lib/vendor/URI.js"></script>
|
<script src="../lib/vendor/URI.js"></script>
|
||||||
<script src="../lib/vendor/mithril.js"></script>
|
<script src="../lib/vendor/mithril.js"></script>
|
||||||
@ -27,7 +26,7 @@
|
|||||||
|
|
||||||
<section id="main">
|
<section id="main">
|
||||||
<article>
|
<article>
|
||||||
<div class="fade" id="mint-selection"></div>
|
<div class="fade" id="exchange-selection"></div>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
</body>
|
</body>
|
||||||
|
@ -14,12 +14,20 @@
|
|||||||
TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
|
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" />
|
/// <reference path="../lib/decl/mithril.d.ts" />
|
||||||
|
|
||||||
import {amountToPretty, canonicalizeBaseUrl} from "../lib/wallet/helpers";
|
import {amountToPretty, canonicalizeBaseUrl} from "../lib/wallet/helpers";
|
||||||
import {AmountJson, CreateReserveResponse} from "../lib/wallet/types";
|
import {AmountJson, CreateReserveResponse} from "../lib/wallet/types";
|
||||||
import m from "mithril";
|
import m from "mithril";
|
||||||
import {IMintInfo} from "../lib/wallet/types";
|
import {IExchangeInfo} from "../lib/wallet/types";
|
||||||
import {ReserveCreationInfo, Amounts} from "../lib/wallet/types";
|
import {ReserveCreationInfo, Amounts} from "../lib/wallet/types";
|
||||||
import MithrilComponent = _mithril.MithrilComponent;
|
import MithrilComponent = _mithril.MithrilComponent;
|
||||||
import {Denomination} from "../lib/wallet/types";
|
import {Denomination} from "../lib/wallet/types";
|
||||||
@ -60,7 +68,7 @@ class DelayTimer {
|
|||||||
class Controller {
|
class Controller {
|
||||||
url = m.prop<string>();
|
url = m.prop<string>();
|
||||||
statusString = null;
|
statusString = null;
|
||||||
isValidMint = false;
|
isValidExchange = false;
|
||||||
reserveCreationInfo: ReserveCreationInfo = null;
|
reserveCreationInfo: ReserveCreationInfo = null;
|
||||||
private timer: DelayTimer;
|
private timer: DelayTimer;
|
||||||
private request: XMLHttpRequest;
|
private request: XMLHttpRequest;
|
||||||
@ -68,12 +76,12 @@ class Controller {
|
|||||||
callbackUrl: string;
|
callbackUrl: string;
|
||||||
detailCollapsed = m.prop<boolean>(true);
|
detailCollapsed = m.prop<boolean>(true);
|
||||||
|
|
||||||
constructor(initialMintUrl: string, amount: AmountJson, callbackUrl: string) {
|
constructor(initialExchangeUrl: string, amount: AmountJson, callbackUrl: string) {
|
||||||
console.log("creating main controller");
|
console.log("creating main controller");
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.callbackUrl = callbackUrl;
|
this.callbackUrl = callbackUrl;
|
||||||
this.timer = new DelayTimer(800, () => this.update());
|
this.timer = new DelayTimer(800, () => this.update());
|
||||||
this.url(initialMintUrl);
|
this.url(initialExchangeUrl);
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,19 +101,19 @@ class Controller {
|
|||||||
|
|
||||||
m.redraw(true);
|
m.redraw(true);
|
||||||
|
|
||||||
console.log("doing get mint info");
|
console.log("doing get exchange info");
|
||||||
|
|
||||||
getReserveCreationInfo(this.url(), this.amount)
|
getReserveCreationInfo(this.url(), this.amount)
|
||||||
.then((r: ReserveCreationInfo) => {
|
.then((r: ReserveCreationInfo) => {
|
||||||
console.log("get mint info resolved");
|
console.log("get exchange info resolved");
|
||||||
this.isValidMint = true;
|
this.isValidExchange = true;
|
||||||
this.reserveCreationInfo = r;
|
this.reserveCreationInfo = r;
|
||||||
console.dir(r);
|
console.dir(r);
|
||||||
this.statusString = "The mint base URL is valid!";
|
this.statusString = "The exchange base URL is valid!";
|
||||||
m.endComputation();
|
m.endComputation();
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log("get mint info rejected");
|
console.log("get exchange info rejected");
|
||||||
if (e.hasOwnProperty("httpStatus")) {
|
if (e.hasOwnProperty("httpStatus")) {
|
||||||
this.statusString = `request failed with status ${this.request.status}`;
|
this.statusString = `request failed with status ${this.request.status}`;
|
||||||
} else {
|
} else {
|
||||||
@ -122,7 +130,7 @@ class Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.isValidMint = false;
|
this.isValidExchange = false;
|
||||||
this.statusString = null;
|
this.statusString = null;
|
||||||
this.reserveCreationInfo = null;
|
this.reserveCreationInfo = null;
|
||||||
if (this.request) {
|
if (this.request) {
|
||||||
@ -131,8 +139,8 @@ class Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmReserve(mint: string, amount: AmountJson, callback_url: string) {
|
confirmReserve(exchange: string, amount: AmountJson, callback_url: string) {
|
||||||
const d = {mint, amount};
|
const d = {exchange, amount};
|
||||||
const cb = (rawResp) => {
|
const cb = (rawResp) => {
|
||||||
if (!rawResp) {
|
if (!rawResp) {
|
||||||
throw Error("empty response");
|
throw Error("empty response");
|
||||||
@ -140,7 +148,7 @@ class Controller {
|
|||||||
if (!rawResp.error) {
|
if (!rawResp.error) {
|
||||||
const resp = CreateReserveResponse.checked(rawResp);
|
const resp = CreateReserveResponse.checked(rawResp);
|
||||||
let q = {
|
let q = {
|
||||||
mint: resp.mint,
|
exchange: resp.exchange,
|
||||||
reserve_pub: resp.reservePub,
|
reserve_pub: resp.reservePub,
|
||||||
amount_value: amount.value,
|
amount_value: amount.value,
|
||||||
amount_fraction: amount.fraction,
|
amount_fraction: amount.fraction,
|
||||||
@ -190,7 +198,7 @@ function view(ctrl: Controller) {
|
|||||||
onclick: () => ctrl.confirmReserve(ctrl.url(),
|
onclick: () => ctrl.confirmReserve(ctrl.url(),
|
||||||
ctrl.amount,
|
ctrl.amount,
|
||||||
ctrl.callbackUrl),
|
ctrl.callbackUrl),
|
||||||
disabled: !ctrl.isValidMint
|
disabled: !ctrl.isValidExchange
|
||||||
},
|
},
|
||||||
"Confirm exchange selection");
|
"Confirm exchange selection");
|
||||||
|
|
||||||
@ -256,30 +264,30 @@ function renderReserveCreationDetails(rci: ReserveCreationInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface MintProbeResult {
|
interface ExchangeProbeResult {
|
||||||
keyInfo?: any;
|
keyInfo?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
function probeMint(mintBaseUrl: string): Promise<MintProbeResult> {
|
function probeExchange(exchangeBaseUrl: string): Promise<ExchangeProbeResult> {
|
||||||
throw Error("not implemented");
|
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
|
// TODO: make this request go to the wallet backend
|
||||||
// Right now, this is a stub.
|
// Right now, this is a stub.
|
||||||
const defaultMint = {
|
const defaultExchange = {
|
||||||
"KUDOS": "http://exchange.demo.taler.net",
|
"KUDOS": "http://exchange.demo.taler.net",
|
||||||
"PUDOS": "http://exchange.test.taler.net",
|
"PUDOS": "http://exchange.test.taler.net",
|
||||||
};
|
};
|
||||||
|
|
||||||
let mint = defaultMint[currency];
|
let exchange = defaultExchange[currency];
|
||||||
|
|
||||||
if (!mint) {
|
if (!exchange) {
|
||||||
mint = ""
|
exchange = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(mint);
|
return Promise.resolve(exchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -290,11 +298,11 @@ export function main() {
|
|||||||
const callback_url = query.callback_url;
|
const callback_url = query.callback_url;
|
||||||
const bank_url = query.bank_url;
|
const bank_url = query.bank_url;
|
||||||
|
|
||||||
getSuggestedMint(amount.currency)
|
getSuggestedExchange(amount.currency)
|
||||||
.then((suggestedMintUrl) => {
|
.then((suggestedExchangeUrl) => {
|
||||||
const controller = () => new Controller(suggestedMintUrl, amount, callback_url);
|
const controller = () => new Controller(suggestedExchangeUrl, amount, callback_url);
|
||||||
var MintSelection = {controller, view};
|
var ExchangeSelection = {controller, view};
|
||||||
m.mount(document.getElementById("mint-selection"), MintSelection);
|
m.mount(document.getElementById("exchange-selection"), ExchangeSelection);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
// TODO: provide more context information, maybe factor it out into a
|
// 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) {
|
function replacer(match, pIndent, pKey, pVal, pEnd) {
|
||||||
var key = '<span class=json-key>';
|
var key = '<span class=json-key>';
|
||||||
var val = '<span class=json-value>';
|
var val = '<span class=json-value>';
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
*
|
*
|
||||||
* Note that duplicate message IDs are NOT merged, to get the same output as
|
* Note that duplicate message IDs are NOT merged, to get the same output as
|
||||||
* you would from xgettext, just run msguniq.
|
* you would from xgettext, just run msguniq.
|
||||||
|
*
|
||||||
|
* @author Florian Dold
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// <reference path="../lib/decl/node.d.ts" />
|
/// <reference path="../lib/decl/node.d.ts" />
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* @author Gabor X. Toth
|
||||||
|
* @author Marcello Stanisci
|
||||||
|
* @author Florian Dold
|
||||||
|
*/
|
||||||
|
|
||||||
body {
|
body {
|
||||||
min-height: 20em;
|
min-height: 20em;
|
||||||
width: 30em;
|
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/mithril.d.ts" />
|
||||||
/// <reference path="../lib/decl/lodash.d.ts" />
|
/// <reference path="../lib/decl/lodash.d.ts" />
|
||||||
|
|
||||||
@ -87,6 +95,7 @@ namespace WalletBalance {
|
|||||||
|
|
||||||
class Controller {
|
class Controller {
|
||||||
myWallet;
|
myWallet;
|
||||||
|
gotError = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.updateBalance();
|
this.updateBalance();
|
||||||
@ -96,9 +105,16 @@ namespace WalletBalance {
|
|||||||
|
|
||||||
updateBalance() {
|
updateBalance() {
|
||||||
m.startComputation();
|
m.startComputation();
|
||||||
chrome.runtime.sendMessage({type: "balances"}, (wallet) => {
|
chrome.runtime.sendMessage({type: "balances"}, (resp) => {
|
||||||
console.log("got wallet", wallet);
|
if (resp.error) {
|
||||||
this.myWallet = wallet;
|
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();
|
m.endComputation();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -106,6 +122,9 @@ namespace WalletBalance {
|
|||||||
|
|
||||||
export function view(ctrl: Controller) {
|
export function view(ctrl: Controller) {
|
||||||
let wallet = ctrl.myWallet;
|
let wallet = ctrl.myWallet;
|
||||||
|
if (ctrl.gotError) {
|
||||||
|
return i18n`Error: could not retrieve balance information.`;
|
||||||
|
}
|
||||||
if (!wallet) {
|
if (!wallet) {
|
||||||
throw Error("Could not retrieve wallet");
|
throw Error("Could not retrieve wallet");
|
||||||
}
|
}
|
||||||
@ -192,6 +211,7 @@ namespace WalletHistory {
|
|||||||
|
|
||||||
class Controller {
|
class Controller {
|
||||||
myHistory;
|
myHistory;
|
||||||
|
gotError = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.update();
|
this.update();
|
||||||
@ -201,8 +221,15 @@ namespace WalletHistory {
|
|||||||
update() {
|
update() {
|
||||||
m.startComputation();
|
m.startComputation();
|
||||||
chrome.runtime.sendMessage({type: "get-history"}, (resp) => {
|
chrome.runtime.sendMessage({type: "get-history"}, (resp) => {
|
||||||
console.log("got history", history);
|
if (resp.error) {
|
||||||
this.myHistory = resp;
|
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();
|
m.endComputation();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -210,6 +237,9 @@ namespace WalletHistory {
|
|||||||
|
|
||||||
export function view(ctrl: Controller) {
|
export function view(ctrl: Controller) {
|
||||||
let history = ctrl.myHistory;
|
let history = ctrl.myHistory;
|
||||||
|
if (ctrl.gotError) {
|
||||||
|
return i18n`Error: could not retrieve event history`;
|
||||||
|
}
|
||||||
if (!history) {
|
if (!history) {
|
||||||
throw Error("Could not retrieve 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