diff options
author | tg(x) <*@tg-x.net> | 2016-03-02 16:53:14 +0100 |
---|---|---|
committer | tg(x) <*@tg-x.net> | 2016-03-02 16:53:14 +0100 |
commit | 51d6f9fd94d05741e733b01be915afeb5572f355 (patch) | |
tree | 024ef8cab1db8543574c6be33edf9dc2da0ef6ac | |
parent | cd331e1855fd4c7033e43622ec0a963fefe37bdb (diff) | |
parent | 13a8f36cad4d5d2ffec788ff6b4d5b61ed4ddff9 (diff) |
Merge branch 'master' of taler.net:/var/git/wallet-webex
-rw-r--r-- | background/main.ts | 4 | ||||
-rw-r--r-- | content_scripts/notify.js | 90 | ||||
-rw-r--r-- | content_scripts/notify.ts | 106 | ||||
-rw-r--r-- | gulpfile.js | 3 | ||||
-rw-r--r-- | lib/decl/mithril.d.ts | 2 | ||||
-rw-r--r-- | lib/emscripten/libwrapper.js | 6 | ||||
-rw-r--r-- | lib/module-trampoline.js | 4 | ||||
-rw-r--r-- | lib/vendor/mithril.js | 25 | ||||
-rw-r--r-- | lib/wallet/cryptoApi.ts | 8 | ||||
-rw-r--r-- | lib/wallet/cryptoLib.ts | 4 | ||||
-rw-r--r-- | lib/wallet/db.ts | 18 | ||||
-rw-r--r-- | lib/wallet/helpers.ts | 6 | ||||
-rw-r--r-- | lib/wallet/http.ts | 2 | ||||
-rw-r--r-- | lib/wallet/types.ts | 81 | ||||
-rw-r--r-- | lib/wallet/wallet.ts | 214 | ||||
-rw-r--r-- | lib/wallet/wxApi.ts | 3 | ||||
-rw-r--r-- | lib/wallet/wxMessaging.ts | 29 | ||||
-rw-r--r-- | manifest.json | 3 | ||||
-rw-r--r-- | pages/confirm-contract.tsx | 47 | ||||
-rw-r--r-- | pages/confirm-create-reserve.html | 5 | ||||
-rw-r--r-- | pages/confirm-create-reserve.tsx | 62 | ||||
-rw-r--r-- | pages/show-db.ts | 6 | ||||
-rw-r--r-- | pogen/pogen.ts | 2 | ||||
-rw-r--r-- | popup/popup.css | 7 | ||||
-rw-r--r-- | popup/popup.tsx | 40 | ||||
-rwxr-xr-x | test/integration/tests.py | 21 |
26 files changed, 569 insertions, 229 deletions
diff --git a/background/main.ts b/background/main.ts index 746d81a60..724c4c978 100644 --- a/background/main.ts +++ b/background/main.ts @@ -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); - });
\ No newline at end of file + }); diff --git a/content_scripts/notify.js b/content_scripts/notify.js index d81732f10..2df089719 100644 --- a/content_scripts/notify.js +++ b/content_scripts/notify.js @@ -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"); diff --git a/content_scripts/notify.ts b/content_scripts/notify.ts index d300dbc03..92c9eee1e 100644 --- a/content_scripts/notify.ts +++ b/content_scripts/notify.ts @@ -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"); diff --git a/gulpfile.js b/gulpfile.js index fc0134457..6c8801862 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -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}", diff --git a/lib/decl/mithril.d.ts b/lib/decl/mithril.d.ts index c87102260..c45633490 100644 --- a/lib/decl/mithril.d.ts +++ b/lib/decl/mithril.d.ts @@ -608,7 +608,7 @@ declare module _mithril { * * @see m.component */ - controller: MithrilControllerFunction<T> | + controller?: MithrilControllerFunction<T> | MithrilControllerConstructor<T>; /** diff --git a/lib/emscripten/libwrapper.js b/lib/emscripten/libwrapper.js index 66b78e910..ef2384029 100644 --- a/lib/emscripten/libwrapper.js +++ b/lib/emscripten/libwrapper.js @@ -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)); diff --git a/lib/module-trampoline.js b/lib/module-trampoline.js index 22173aa42..1f0247f35 100644 --- a/lib/module-trampoline.js +++ b/lib/module-trampoline.js @@ -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); - });
\ No newline at end of file + }); diff --git a/lib/vendor/mithril.js b/lib/vendor/mithril.js index d023ac34c..4b225299e 100644 --- a/lib/vendor/mithril.js +++ b/lib/vendor/mithril.js @@ -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 */ diff --git a/lib/wallet/cryptoApi.ts b/lib/wallet/cryptoApi.ts index 300b928db..3cbed89c3 100644 --- a/lib/wallet/cryptoApi.ts +++ b/lib/wallet/cryptoApi.ts @@ -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); } -}
\ No newline at end of file +} diff --git a/lib/wallet/cryptoLib.ts b/lib/wallet/cryptoLib.ts index 869ddbaff..769cb3356 100644 --- a/lib/wallet/cryptoLib.ts +++ b/lib/wallet/cryptoLib.ts @@ -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 diff --git a/lib/wallet/db.ts b/lib/wallet/db.ts index c7621c5ff..9374aa447 100644 --- a/lib/wallet/db.ts +++ b/lib/wallet/db.ts @@ -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; } }; }); diff --git a/lib/wallet/helpers.ts b/lib/wallet/helpers.ts index 99913e558..477fce33f 100644 --- a/lib/wallet/helpers.ts +++ b/lib/wallet/helpers.ts @@ -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] } -}
\ No newline at end of file +} diff --git a/lib/wallet/http.ts b/lib/wallet/http.ts index 3f7244e40..3bc80f40a 100644 --- a/lib/wallet/http.ts +++ b/lib/wallet/http.ts @@ -81,4 +81,4 @@ export class RequestException { constructor(detail) { } -}
\ No newline at end of file +} diff --git a/lib/wallet/types.ts b/lib/wallet/types.ts index 9c7b21b7c..feab1bcb1 100644 --- a/lib/wallet/types.ts +++ b/lib/wallet/types.ts @@ -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(); -}
\ No newline at end of file +} diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts index 92fb92a4a..8291f653a 100644 --- a/lib/wallet/wallet.ts +++ b/lib/wallet/wallet.ts @@ -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 { } }); } -}
\ No newline at end of file +} diff --git a/lib/wallet/wxApi.ts b/lib/wallet/wxApi.ts index 9871b6e7f..2b69ff82e 100644 --- a/lib/wallet/wxApi.ts +++ b/lib/wallet/wxApi.ts @@ -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); }); }); -}
\ No newline at end of file +} diff --git a/lib/wallet/wxMessaging.ts b/lib/wallet/wxMessaging.ts index 740873d88..daa9ec197 100644 --- a/lib/wallet/wxMessaging.ts +++ b/lib/wallet/wxMessaging.ts @@ -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(); diff --git a/manifest.json b/manifest.json index 9cdd86fa4..b83a68533 100644 --- a/manifest.json +++ b/manifest.json @@ -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://*/*" ], diff --git a/pages/confirm-contract.tsx b/pages/confirm-contract.tsx index ff6ffba64..0c7419c6c 100644 --- a/pages/confirm-contract.tsx +++ b/pages/confirm-contract.tsx @@ -14,12 +14,21 @@ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> */ -/// <reference path="../lib/decl/handlebars/handlebars.d.ts" /> -"use strict"; +/** + * 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"; -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); }); } -}
\ No newline at end of file +} diff --git a/pages/confirm-create-reserve.html b/pages/confirm-create-reserve.html index 9893a7a3d..88d01d2b1 100644 --- a/pages/confirm-create-reserve.html +++ b/pages/confirm-create-reserve.html @@ -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> diff --git a/pages/confirm-create-reserve.tsx b/pages/confirm-create-reserve.tsx index 224a3e895..5ed97db04 100644 --- a/pages/confirm-create-reserve.tsx +++ b/pages/confirm-create-reserve.tsx @@ -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 diff --git a/pages/show-db.ts b/pages/show-db.ts index 1c414dde7..c99d2f2b3 100644 --- a/pages/show-db.ts +++ b/pages/show-db.ts @@ -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>'; diff --git a/pogen/pogen.ts b/pogen/pogen.ts index 77f7ea3dc..5ff182e7c 100644 --- a/pogen/pogen.ts +++ b/pogen/pogen.ts @@ -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" /> diff --git a/popup/popup.css b/popup/popup.css index 53c9f97ed..7218b7baf 100644 --- a/popup/popup.css +++ b/popup/popup.css @@ -1,3 +1,10 @@ + +/** + * @author Gabor X. Toth + * @author Marcello Stanisci + * @author Florian Dold + */ + body { min-height: 20em; width: 30em; diff --git a/popup/popup.tsx b/popup/popup.tsx index 963556c32..be33f870a 100644 --- a/popup/popup.tsx +++ b/popup/popup.tsx @@ -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"); } diff --git a/test/integration/tests.py b/test/integration/tests.py new file mode 100755 index 000000000..123f100ff --- /dev/null +++ b/test/integration/tests.py @@ -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() |