aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/webex/messages.ts6
-rw-r--r--src/webex/pages/popup.tsx3
-rw-r--r--src/webex/pages/reset-required.html30
-rw-r--r--src/webex/pages/reset-required.tsx73
-rw-r--r--src/webex/style/wallet.css4
-rw-r--r--src/webex/wxApi.ts35
-rw-r--r--src/webex/wxBackend.ts208
-rw-r--r--tsconfig.json1
-rw-r--r--webpack.config.js1
9 files changed, 280 insertions, 81 deletions
diff --git a/src/webex/messages.ts b/src/webex/messages.ts
index 27ff9a5b6..bf9ca00b0 100644
--- a/src/webex/messages.ts
+++ b/src/webex/messages.ts
@@ -49,7 +49,7 @@ export interface MessageMap {
request: { };
response: void;
};
- "reset": {
+ "reset-db": {
request: { };
response: void;
};
@@ -164,6 +164,10 @@ export interface MessageMap {
request: { contractTermsHash: string; merchantSig: string };
response: void;
};
+ "check-upgrade": {
+ request: { };
+ response: void;
+ };
}
/**
diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx
index 831147f1e..f1f0353ad 100644
--- a/src/webex/pages/popup.tsx
+++ b/src/webex/pages/popup.tsx
@@ -36,6 +36,7 @@ import {
} from "../../types";
import { abbrev, renderAmount } from "../renderHtml";
+import * as wxApi from "../wxApi";
import * as React from "react";
import * as ReactDOM from "react-dom";
@@ -484,7 +485,7 @@ function reload() {
function confirmReset() {
if (confirm("Do you want to IRREVOCABLY DESTROY everything inside your" +
" wallet and LOSE ALL YOUR COINS?")) {
- chrome.runtime.sendMessage({type: "reset"});
+ wxApi.resetDb();
window.close();
}
}
diff --git a/src/webex/pages/reset-required.html b/src/webex/pages/reset-required.html
new file mode 100644
index 000000000..72b176b4d
--- /dev/null
+++ b/src/webex/pages/reset-required.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="UTF-8">
+ <title>Taler Wallet: Select Taler Provider</title>
+
+ <link rel="icon" href="/img/icon.png">
+ <link rel="stylesheet" type="text/css" href="../style/wallet.css">
+ <link rel="stylesheet" type="text/css" href="../style/pure.css">
+
+ <script src="/dist/page-common-bundle.js"></script>
+ <script src="/dist/reset-required-bundle.js"></script>
+
+ <style>
+ body {
+ font-size: 100%;
+ overflow-y: scroll;
+ }
+ </style>
+
+</head>
+
+<body>
+ <section id="main">
+ <div id="container"></div>
+ </section>
+</body>
+
+</html>
diff --git a/src/webex/pages/reset-required.tsx b/src/webex/pages/reset-required.tsx
new file mode 100644
index 000000000..90ea51abe
--- /dev/null
+++ b/src/webex/pages/reset-required.tsx
@@ -0,0 +1,73 @@
+/*
+ This file is part of TALER
+ (C) 2017 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+
+/**
+ * Page to inform the user when a database reset is required.
+ *
+ * @author Florian Dold
+ */
+
+import * as React from "react";
+import * as ReactDOM from "react-dom";
+
+import * as wxApi from "../wxApi";
+
+class State {
+ checked: boolean;
+ resetRequired: boolean;
+}
+
+
+class ResetNotification extends React.Component<any, State> {
+ constructor(props: any) {
+ super(props);
+ this.state = {checked: false, resetRequired: true};
+ setInterval(() => this.update(), 500);
+ }
+ async update() {
+ const res = await wxApi.checkUpgrade();
+ this.setState({resetRequired: res.dbResetRequired});
+ }
+ render() {
+ if (this.state.resetRequired) {
+ return (
+ <div>
+ <h1>Manual Reset Reqired</h1>
+ <p>The wallet's database in your browser is incompatible with the currently installed wallet. Please reset manually.</p>
+ <p>Once the database format has stabilized, we will provide automatic upgrades.</p>
+ <input id="check" type="checkbox" checked={this.state.checked} onChange={(e) => this.setState({checked: e.target.checked})} />{" "}
+ <label htmlFor="check">
+ I understand that I will lose all my data
+ </label>
+ <br />
+ <button className="pure-button" disabled={!this.state.checked} onClick={() => wxApi.resetDb()}>Reset</button>
+ </div>
+ );
+ }
+ return (
+ <div>
+ <h1>Everything is fine!</h1>
+ A reset is not required anymore, you can close this page.
+ </div>
+ );
+ }
+}
+
+
+document.addEventListener("DOMContentLoaded", () => {
+ ReactDOM.render(<ResetNotification />, document.getElementById( "container")!);
+});
diff --git a/src/webex/style/wallet.css b/src/webex/style/wallet.css
index 7bfb99e6c..5773eb396 100644
--- a/src/webex/style/wallet.css
+++ b/src/webex/style/wallet.css
@@ -79,10 +79,6 @@ label {
padding-right: 1em;
}
-label::after {
- content: ":";
-}
-
input.url {
width: 25em;
}
diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts
index 1968b6575..a064b4133 100644
--- a/src/webex/wxApi.ts
+++ b/src/webex/wxApi.ts
@@ -39,6 +39,27 @@ import {
import { MessageType, MessageMap } from "./messages";
+/**
+ * Response with information about available version upgrades.
+ */
+export interface UpgradeResponse {
+ /**
+ * Is a reset required because of a new DB version
+ * that can't be atomatically upgraded?
+ */
+ dbResetRequired: boolean;
+
+ /**
+ * Current database version.
+ */
+ currentDbVersion: string;
+
+ /**
+ * Old db version (if applicable).
+ */
+ oldDbVersion: string;
+}
+
async function callBackend<T extends MessageType>(type: T, detail: MessageMap[T]["request"]): Promise<any> {
return new Promise<any>((resolve, reject) => {
@@ -254,3 +275,17 @@ export function getTabCookie(contractTermsHash: string, merchantSig: string): Pr
export function generateNonce(): Promise<string> {
return callBackend("generate-nonce", { });
}
+
+/**
+ * Check upgrade information
+ */
+export function checkUpgrade(): Promise<UpgradeResponse> {
+ return callBackend("check-upgrade", { });
+}
+
+/**
+ * Reset database
+ */
+export function resetDb(): Promise<void> {
+ return callBackend("reset-db", { });
+}
diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts
index 35fa0b573..0bd4a211e 100644
--- a/src/webex/wxBackend.ts
+++ b/src/webex/wxBackend.ts
@@ -44,6 +44,7 @@ import {
import { ChromeBadge } from "./chromeBadge";
import { MessageType } from "./messages";
+import * as wxApi from "./wxApi";
import URI = require("urijs");
import Port = chrome.runtime.Port;
@@ -60,23 +61,34 @@ const DB_NAME = "taler";
*/
const DB_VERSION = 18;
-function handleMessage(db: IDBDatabase,
- wallet: Wallet,
- sender: MessageSender,
+const NeedsWallet = Symbol("NeedsWallet");
+
+function handleMessage(sender: MessageSender,
type: MessageType, detail: any): any {
function assertNotFound(t: never): never {
console.error(`Request type ${t as string} unknown`);
console.error(`Request detail was ${detail}`);
return { error: "request unknown", requestType: type } as never;
}
+ function needsWallet(): Wallet {
+ if (!currentWallet) {
+ throw NeedsWallet;
+ }
+ return currentWallet;
+ }
switch (type) {
- case "balances":
- return wallet.getBalances();
- case "dump-db":
+ case "balances": {
+ return needsWallet().getBalances();
+ }
+ case "dump-db": {
+ const db = needsWallet().db;
return exportDb(db);
- case "import-db":
+ }
+ case "import-db": {
+ const db = needsWallet().db;
return importDb(db, detail.dump);
- case "get-tab-cookie":
+ }
+ case "get-tab-cookie": {
if (!sender || !sender.tab || !sender.tab.id) {
return Promise.resolve();
}
@@ -84,10 +96,13 @@ function handleMessage(db: IDBDatabase,
const info: any = paymentRequestCookies[id] as any;
delete paymentRequestCookies[id];
return Promise.resolve(info);
- case "ping":
+ }
+ case "ping": {
return Promise.resolve();
- case "reset":
- if (db) {
+ }
+ case "reset-db": {
+ if (currentWallet) {
+ const db = currentWallet.db;
const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < db.objectStoreNames.length; i++) {
@@ -97,34 +112,42 @@ function handleMessage(db: IDBDatabase,
deleteDb();
chrome.browserAction.setBadgeText({ text: "" });
console.log("reset done");
+ if (!currentWallet) {
+ reinitWallet();
+ }
return Promise.resolve({});
+ }
case "create-reserve": {
const d = {
amount: detail.amount,
exchange: detail.exchange,
};
const req = CreateReserveRequest.checked(d);
- return wallet.createReserve(req);
+ return needsWallet().createReserve(req);
}
- case "confirm-reserve":
+ case "confirm-reserve": {
const d = {
reservePub: detail.reservePub,
};
const req = ConfirmReserveRequest.checked(d);
- return wallet.confirmReserve(req);
- case "generate-nonce":
- return wallet.generateNonce();
- case "confirm-pay":
+ return needsWallet().confirmReserve(req);
+ }
+ case "generate-nonce": {
+ return needsWallet().generateNonce();
+ }
+ case "confirm-pay": {
if (typeof detail.proposalId !== "number") {
throw Error("proposalId must be number");
}
- return wallet.confirmPay(detail.proposalId);
- case "check-pay":
+ return needsWallet().confirmPay(detail.proposalId);
+ }
+ case "check-pay": {
if (typeof detail.proposalId !== "number") {
throw Error("proposalId must be number");
}
- return wallet.checkPay(detail.proposalId);
- case "query-payment":
+ return needsWallet().checkPay(detail.proposalId);
+ }
+ case "query-payment": {
if (sender.tab && sender.tab.id) {
rateLimitCache[sender.tab.id]++;
if (rateLimitCache[sender.tab.id] > 10) {
@@ -137,99 +160,120 @@ function handleMessage(db: IDBDatabase,
return Promise.resolve(msg);
}
}
- return wallet.queryPayment(detail.url);
- case "exchange-info":
+ return needsWallet().queryPayment(detail.url);
+ }
+ case "exchange-info": {
if (!detail.baseUrl) {
return Promise.resolve({ error: "bad url" });
}
- return wallet.updateExchangeFromUrl(detail.baseUrl);
- case "currency-info":
+ return needsWallet().updateExchangeFromUrl(detail.baseUrl);
+ }
+ case "currency-info": {
if (!detail.name) {
return Promise.resolve({ error: "name missing" });
}
- return wallet.getCurrencyRecord(detail.name);
- case "hash-contract":
+ return needsWallet().getCurrencyRecord(detail.name);
+ }
+ case "hash-contract": {
if (!detail.contract) {
return Promise.resolve({ error: "contract missing" });
}
- return wallet.hashContract(detail.contract).then((hash) => {
+ return needsWallet().hashContract(detail.contract).then((hash) => {
return hash;
});
- case "put-history-entry":
+ }
+ case "put-history-entry": {
if (!detail.historyEntry) {
return Promise.resolve({ error: "historyEntry missing" });
}
- return wallet.putHistory(detail.historyEntry);
- case "save-proposal":
+ return needsWallet().putHistory(detail.historyEntry);
+ }
+ case "save-proposal": {
console.log("handling save-proposal", detail);
const checkedRecord = ProposalRecord.checked({
contractTerms: detail.data,
contractTermsHash: detail.hash,
merchantSig: detail.sig,
});
- return wallet.saveProposal(checkedRecord);
- case "reserve-creation-info":
+ return needsWallet().saveProposal(checkedRecord);
+ }
+ case "reserve-creation-info": {
if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
return Promise.resolve({ error: "bad url" });
}
const amount = AmountJson.checked(detail.amount);
- return wallet.getReserveCreationInfo(detail.baseUrl, amount);
- case "get-history":
+ return needsWallet().getReserveCreationInfo(detail.baseUrl, amount);
+ }
+ case "get-history": {
// TODO: limit history length
- return wallet.getHistory();
- case "get-proposal":
- return wallet.getProposal(detail.proposalId);
- case "get-exchanges":
- return wallet.getExchanges();
- case "get-currencies":
- return wallet.getCurrencies();
- case "update-currency":
- return wallet.updateCurrency(detail.currencyRecord);
- case "get-reserves":
+ return needsWallet().getHistory();
+ }
+ case "get-proposal": {
+ return needsWallet().getProposal(detail.proposalId);
+ }
+ case "get-exchanges": {
+ return needsWallet().getExchanges();
+ }
+ case "get-currencies": {
+ return needsWallet().getCurrencies();
+ }
+ case "update-currency": {
+ return needsWallet().updateCurrency(detail.currencyRecord);
+ }
+ case "get-reserves": {
if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangeBaseUrl missing"));
}
- return wallet.getReserves(detail.exchangeBaseUrl);
- case "get-payback-reserves":
- return wallet.getPaybackReserves();
- case "withdraw-payback-reserve":
+ return needsWallet().getReserves(detail.exchangeBaseUrl);
+ }
+ case "get-payback-reserves": {
+ return needsWallet().getPaybackReserves();
+ }
+ case "withdraw-payback-reserve": {
if (typeof detail.reservePub !== "string") {
return Promise.reject(Error("reservePub missing"));
}
- return wallet.withdrawPaybackReserve(detail.reservePub);
- case "get-coins":
+ return needsWallet().withdrawPaybackReserve(detail.reservePub);
+ }
+ case "get-coins": {
if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing"));
}
- return wallet.getCoins(detail.exchangeBaseUrl);
- case "get-precoins":
+ return needsWallet().getCoins(detail.exchangeBaseUrl);
+ }
+ case "get-precoins": {
if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing"));
}
- return wallet.getPreCoins(detail.exchangeBaseUrl);
- case "get-denoms":
+ return needsWallet().getPreCoins(detail.exchangeBaseUrl);
+ }
+ case "get-denoms": {
if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing"));
}
- return wallet.getDenoms(detail.exchangeBaseUrl);
- case "refresh-coin":
+ return needsWallet().getDenoms(detail.exchangeBaseUrl);
+ }
+ case "refresh-coin": {
if (typeof detail.coinPub !== "string") {
return Promise.reject(Error("coinPub missing"));
}
- return wallet.refresh(detail.coinPub);
- case "payback-coin":
+ return needsWallet().refresh(detail.coinPub);
+ }
+ case "payback-coin": {
if (typeof detail.coinPub !== "string") {
return Promise.reject(Error("coinPub missing"));
}
- return wallet.payback(detail.coinPub);
- case "payment-failed":
+ return needsWallet().payback(detail.coinPub);
+ }
+ case "payment-failed": {
// For now we just update exchanges (maybe the exchange did something
// wrong and the keys were messed up).
// FIXME: in the future we should look at what actually went wrong.
console.error("payment reported as failed");
- wallet.updateExchanges();
+ needsWallet().updateExchanges();
return Promise.resolve();
- case "payment-succeeded":
+ }
+ case "payment-succeeded": {
const contractTermsHash = detail.contractTermsHash;
const merchantSig = detail.merchantSig;
if (!contractTermsHash) {
@@ -238,7 +282,20 @@ function handleMessage(db: IDBDatabase,
if (!merchantSig) {
return Promise.reject(Error("merchantSig missing"));
}
- return wallet.paymentSucceeded(contractTermsHash, merchantSig);
+ return needsWallet().paymentSucceeded(contractTermsHash, merchantSig);
+ }
+ case "check-upgrade": {
+ let dbResetRequired = false;
+ if (!currentWallet) {
+ dbResetRequired = true;
+ }
+ const resp: wxApi.UpgradeResponse = {
+ dbResetRequired,
+ currentDbVersion: DB_VERSION.toString(),
+ oldDbVersion: (oldDbVersion || "unknown").toString(),
+ }
+ return resp;
+ }
default:
// Exhaustiveness check.
// See https://www.typescriptlang.org/docs/handbook/advanced-types.html
@@ -246,9 +303,9 @@ function handleMessage(db: IDBDatabase,
}
}
-async function dispatch(wallet: Wallet, req: any, sender: any, sendResponse: any): Promise<void> {
+async function dispatch(req: any, sender: any, sendResponse: any): Promise<void> {
try {
- const p = handleMessage(wallet.db, wallet, sender, req.type, req.detail);
+ const p = handleMessage(sender, req.type, req.detail);
const r = await p;
try {
sendResponse(r);
@@ -428,6 +485,11 @@ function clearRateLimitCache() {
*/
let currentWallet: Wallet|undefined;
+/**
+ * Last version if an outdated DB, if applicable.
+ */
+let oldDbVersion: number|undefined;
+
async function reinitWallet() {
if (currentWallet) {
@@ -548,13 +610,7 @@ export async function wxMain() {
// Handlers for messages coming directly from the content
// script on the page
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
- const wallet = currentWallet;
- if (!wallet) {
- console.warn("wallet not available while handling message");
- console.warn("dropped request message was", req);
- return;
- }
- dispatch(wallet, req, sender, sendResponse);
+ dispatch(req, sender, sendResponse);
return true;
});
@@ -619,8 +675,10 @@ function openTalerDb(): Promise<IDBDatabase> {
break;
default:
if (e.oldVersion !== DB_VERSION) {
- window.alert("Incompatible wallet dababase version, please reset" +
- " db.");
+ oldDbVersion = e.oldVersion;
+ chrome.tabs.create({
+ url: chrome.extension.getURL("/src/webex/pages/reset-required.html"),
+ });
chrome.browserAction.setBadgeText({text: "err"});
chrome.browserAction.setBadgeBackgroundColor({color: "#F00"});
throw Error("incompatible DB");
diff --git a/tsconfig.json b/tsconfig.json
index 7bcf7d495..349b5969a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -63,6 +63,7 @@
"src/webex/pages/logs.tsx",
"src/webex/pages/payback.tsx",
"src/webex/pages/popup.tsx",
+ "src/webex/pages/reset-required.tsx",
"src/webex/pages/show-db.ts",
"src/webex/pages/tree.tsx",
"src/webex/renderHtml.tsx",
diff --git a/webpack.config.js b/webpack.config.js
index 60311aaa2..34342748b 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -73,6 +73,7 @@ module.exports = function (env) {
"show-db": "./src/webex/pages/show-db.ts",
"tree": "./src/webex/pages/tree.tsx",
"payback": "./src/webex/pages/payback.tsx",
+ "reset-required": "./src/webex/pages/reset-required.tsx",
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({