pretty reset page

This commit is contained in:
Florian Dold 2017-06-05 03:20:28 +02:00
parent e95027f377
commit 2904591332
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
9 changed files with 280 additions and 81 deletions

View File

@ -49,7 +49,7 @@ export interface MessageMap {
request: { }; request: { };
response: void; response: void;
}; };
"reset": { "reset-db": {
request: { }; request: { };
response: void; response: void;
}; };
@ -164,6 +164,10 @@ export interface MessageMap {
request: { contractTermsHash: string; merchantSig: string }; request: { contractTermsHash: string; merchantSig: string };
response: void; response: void;
}; };
"check-upgrade": {
request: { };
response: void;
};
} }
/** /**

View File

@ -36,6 +36,7 @@ import {
} from "../../types"; } from "../../types";
import { abbrev, renderAmount } from "../renderHtml"; import { abbrev, renderAmount } from "../renderHtml";
import * as wxApi from "../wxApi";
import * as React from "react"; import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
@ -484,7 +485,7 @@ function reload() {
function confirmReset() { function confirmReset() {
if (confirm("Do you want to IRREVOCABLY DESTROY everything inside your" + if (confirm("Do you want to IRREVOCABLY DESTROY everything inside your" +
" wallet and LOSE ALL YOUR COINS?")) { " wallet and LOSE ALL YOUR COINS?")) {
chrome.runtime.sendMessage({type: "reset"}); wxApi.resetDb();
window.close(); window.close();
} }
} }

View File

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

View File

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

View File

@ -79,10 +79,6 @@ label {
padding-right: 1em; padding-right: 1em;
} }
label::after {
content: ":";
}
input.url { input.url {
width: 25em; width: 25em;
} }

View File

@ -39,6 +39,27 @@ import {
import { MessageType, MessageMap } from "./messages"; 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> { async function callBackend<T extends MessageType>(type: T, detail: MessageMap[T]["request"]): Promise<any> {
return new Promise<any>((resolve, reject) => { return new Promise<any>((resolve, reject) => {
@ -254,3 +275,17 @@ export function getTabCookie(contractTermsHash: string, merchantSig: string): Pr
export function generateNonce(): Promise<string> { export function generateNonce(): Promise<string> {
return callBackend("generate-nonce", { }); 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", { });
}

View File

@ -44,6 +44,7 @@ import {
import { ChromeBadge } from "./chromeBadge"; import { ChromeBadge } from "./chromeBadge";
import { MessageType } from "./messages"; import { MessageType } from "./messages";
import * as wxApi from "./wxApi";
import URI = require("urijs"); import URI = require("urijs");
import Port = chrome.runtime.Port; import Port = chrome.runtime.Port;
@ -60,23 +61,34 @@ const DB_NAME = "taler";
*/ */
const DB_VERSION = 18; const DB_VERSION = 18;
function handleMessage(db: IDBDatabase, const NeedsWallet = Symbol("NeedsWallet");
wallet: Wallet,
sender: MessageSender, function handleMessage(sender: MessageSender,
type: MessageType, detail: any): any { type: MessageType, detail: any): any {
function assertNotFound(t: never): never { function assertNotFound(t: never): never {
console.error(`Request type ${t as string} unknown`); console.error(`Request type ${t as string} unknown`);
console.error(`Request detail was ${detail}`); console.error(`Request detail was ${detail}`);
return { error: "request unknown", requestType: type } as never; return { error: "request unknown", requestType: type } as never;
} }
function needsWallet(): Wallet {
if (!currentWallet) {
throw NeedsWallet;
}
return currentWallet;
}
switch (type) { switch (type) {
case "balances": case "balances": {
return wallet.getBalances(); return needsWallet().getBalances();
case "dump-db": }
case "dump-db": {
const db = needsWallet().db;
return exportDb(db); return exportDb(db);
case "import-db": }
case "import-db": {
const db = needsWallet().db;
return importDb(db, detail.dump); return importDb(db, detail.dump);
case "get-tab-cookie": }
case "get-tab-cookie": {
if (!sender || !sender.tab || !sender.tab.id) { if (!sender || !sender.tab || !sender.tab.id) {
return Promise.resolve(); return Promise.resolve();
} }
@ -84,10 +96,13 @@ function handleMessage(db: IDBDatabase,
const info: any = paymentRequestCookies[id] as any; const info: any = paymentRequestCookies[id] as any;
delete paymentRequestCookies[id]; delete paymentRequestCookies[id];
return Promise.resolve(info); return Promise.resolve(info);
case "ping": }
case "ping": {
return Promise.resolve(); 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"); const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
// tslint:disable-next-line:prefer-for-of // tslint:disable-next-line:prefer-for-of
for (let i = 0; i < db.objectStoreNames.length; i++) { for (let i = 0; i < db.objectStoreNames.length; i++) {
@ -97,34 +112,42 @@ function handleMessage(db: IDBDatabase,
deleteDb(); deleteDb();
chrome.browserAction.setBadgeText({ text: "" }); chrome.browserAction.setBadgeText({ text: "" });
console.log("reset done"); console.log("reset done");
if (!currentWallet) {
reinitWallet();
}
return Promise.resolve({}); return Promise.resolve({});
}
case "create-reserve": { case "create-reserve": {
const d = { const d = {
amount: detail.amount, amount: detail.amount,
exchange: detail.exchange, exchange: detail.exchange,
}; };
const req = CreateReserveRequest.checked(d); const req = CreateReserveRequest.checked(d);
return wallet.createReserve(req); return needsWallet().createReserve(req);
} }
case "confirm-reserve": case "confirm-reserve": {
const d = { const d = {
reservePub: detail.reservePub, reservePub: detail.reservePub,
}; };
const req = ConfirmReserveRequest.checked(d); const req = ConfirmReserveRequest.checked(d);
return wallet.confirmReserve(req); return needsWallet().confirmReserve(req);
case "generate-nonce": }
return wallet.generateNonce(); case "generate-nonce": {
case "confirm-pay": return needsWallet().generateNonce();
}
case "confirm-pay": {
if (typeof detail.proposalId !== "number") { if (typeof detail.proposalId !== "number") {
throw Error("proposalId must be number"); throw Error("proposalId must be number");
} }
return wallet.confirmPay(detail.proposalId); return needsWallet().confirmPay(detail.proposalId);
case "check-pay": }
case "check-pay": {
if (typeof detail.proposalId !== "number") { if (typeof detail.proposalId !== "number") {
throw Error("proposalId must be number"); throw Error("proposalId must be number");
} }
return wallet.checkPay(detail.proposalId); return needsWallet().checkPay(detail.proposalId);
case "query-payment": }
case "query-payment": {
if (sender.tab && sender.tab.id) { if (sender.tab && sender.tab.id) {
rateLimitCache[sender.tab.id]++; rateLimitCache[sender.tab.id]++;
if (rateLimitCache[sender.tab.id] > 10) { if (rateLimitCache[sender.tab.id] > 10) {
@ -137,99 +160,120 @@ function handleMessage(db: IDBDatabase,
return Promise.resolve(msg); return Promise.resolve(msg);
} }
} }
return wallet.queryPayment(detail.url); return needsWallet().queryPayment(detail.url);
case "exchange-info": }
case "exchange-info": {
if (!detail.baseUrl) { if (!detail.baseUrl) {
return Promise.resolve({ error: "bad url" }); return Promise.resolve({ error: "bad url" });
} }
return wallet.updateExchangeFromUrl(detail.baseUrl); return needsWallet().updateExchangeFromUrl(detail.baseUrl);
case "currency-info": }
case "currency-info": {
if (!detail.name) { if (!detail.name) {
return Promise.resolve({ error: "name missing" }); return Promise.resolve({ error: "name missing" });
} }
return wallet.getCurrencyRecord(detail.name); return needsWallet().getCurrencyRecord(detail.name);
case "hash-contract": }
case "hash-contract": {
if (!detail.contract) { if (!detail.contract) {
return Promise.resolve({ error: "contract missing" }); return Promise.resolve({ error: "contract missing" });
} }
return wallet.hashContract(detail.contract).then((hash) => { return needsWallet().hashContract(detail.contract).then((hash) => {
return hash; return hash;
}); });
case "put-history-entry": }
case "put-history-entry": {
if (!detail.historyEntry) { if (!detail.historyEntry) {
return Promise.resolve({ error: "historyEntry missing" }); return Promise.resolve({ error: "historyEntry missing" });
} }
return wallet.putHistory(detail.historyEntry); return needsWallet().putHistory(detail.historyEntry);
case "save-proposal": }
case "save-proposal": {
console.log("handling save-proposal", detail); console.log("handling save-proposal", detail);
const checkedRecord = ProposalRecord.checked({ const checkedRecord = ProposalRecord.checked({
contractTerms: detail.data, contractTerms: detail.data,
contractTermsHash: detail.hash, contractTermsHash: detail.hash,
merchantSig: detail.sig, merchantSig: detail.sig,
}); });
return wallet.saveProposal(checkedRecord); return needsWallet().saveProposal(checkedRecord);
case "reserve-creation-info": }
case "reserve-creation-info": {
if (!detail.baseUrl || typeof detail.baseUrl !== "string") { if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
return Promise.resolve({ error: "bad url" }); return Promise.resolve({ error: "bad url" });
} }
const amount = AmountJson.checked(detail.amount); const amount = AmountJson.checked(detail.amount);
return wallet.getReserveCreationInfo(detail.baseUrl, amount); return needsWallet().getReserveCreationInfo(detail.baseUrl, amount);
case "get-history": }
case "get-history": {
// TODO: limit history length // TODO: limit history length
return wallet.getHistory(); return needsWallet().getHistory();
case "get-proposal": }
return wallet.getProposal(detail.proposalId); case "get-proposal": {
case "get-exchanges": return needsWallet().getProposal(detail.proposalId);
return wallet.getExchanges(); }
case "get-currencies": case "get-exchanges": {
return wallet.getCurrencies(); return needsWallet().getExchanges();
case "update-currency": }
return wallet.updateCurrency(detail.currencyRecord); case "get-currencies": {
case "get-reserves": return needsWallet().getCurrencies();
}
case "update-currency": {
return needsWallet().updateCurrency(detail.currencyRecord);
}
case "get-reserves": {
if (typeof detail.exchangeBaseUrl !== "string") { if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangeBaseUrl missing")); return Promise.reject(Error("exchangeBaseUrl missing"));
} }
return wallet.getReserves(detail.exchangeBaseUrl); return needsWallet().getReserves(detail.exchangeBaseUrl);
case "get-payback-reserves": }
return wallet.getPaybackReserves(); case "get-payback-reserves": {
case "withdraw-payback-reserve": return needsWallet().getPaybackReserves();
}
case "withdraw-payback-reserve": {
if (typeof detail.reservePub !== "string") { if (typeof detail.reservePub !== "string") {
return Promise.reject(Error("reservePub missing")); return Promise.reject(Error("reservePub missing"));
} }
return wallet.withdrawPaybackReserve(detail.reservePub); return needsWallet().withdrawPaybackReserve(detail.reservePub);
case "get-coins": }
case "get-coins": {
if (typeof detail.exchangeBaseUrl !== "string") { if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing")); return Promise.reject(Error("exchangBaseUrl missing"));
} }
return wallet.getCoins(detail.exchangeBaseUrl); return needsWallet().getCoins(detail.exchangeBaseUrl);
case "get-precoins": }
case "get-precoins": {
if (typeof detail.exchangeBaseUrl !== "string") { if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing")); return Promise.reject(Error("exchangBaseUrl missing"));
} }
return wallet.getPreCoins(detail.exchangeBaseUrl); return needsWallet().getPreCoins(detail.exchangeBaseUrl);
case "get-denoms": }
case "get-denoms": {
if (typeof detail.exchangeBaseUrl !== "string") { if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing")); return Promise.reject(Error("exchangBaseUrl missing"));
} }
return wallet.getDenoms(detail.exchangeBaseUrl); return needsWallet().getDenoms(detail.exchangeBaseUrl);
case "refresh-coin": }
case "refresh-coin": {
if (typeof detail.coinPub !== "string") { if (typeof detail.coinPub !== "string") {
return Promise.reject(Error("coinPub missing")); return Promise.reject(Error("coinPub missing"));
} }
return wallet.refresh(detail.coinPub); return needsWallet().refresh(detail.coinPub);
case "payback-coin": }
case "payback-coin": {
if (typeof detail.coinPub !== "string") { if (typeof detail.coinPub !== "string") {
return Promise.reject(Error("coinPub missing")); return Promise.reject(Error("coinPub missing"));
} }
return wallet.payback(detail.coinPub); return needsWallet().payback(detail.coinPub);
case "payment-failed": }
case "payment-failed": {
// For now we just update exchanges (maybe the exchange did something // For now we just update exchanges (maybe the exchange did something
// wrong and the keys were messed up). // wrong and the keys were messed up).
// FIXME: in the future we should look at what actually went wrong. // FIXME: in the future we should look at what actually went wrong.
console.error("payment reported as failed"); console.error("payment reported as failed");
wallet.updateExchanges(); needsWallet().updateExchanges();
return Promise.resolve(); return Promise.resolve();
case "payment-succeeded": }
case "payment-succeeded": {
const contractTermsHash = detail.contractTermsHash; const contractTermsHash = detail.contractTermsHash;
const merchantSig = detail.merchantSig; const merchantSig = detail.merchantSig;
if (!contractTermsHash) { if (!contractTermsHash) {
@ -238,7 +282,20 @@ function handleMessage(db: IDBDatabase,
if (!merchantSig) { if (!merchantSig) {
return Promise.reject(Error("merchantSig missing")); 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: default:
// Exhaustiveness check. // Exhaustiveness check.
// See https://www.typescriptlang.org/docs/handbook/advanced-types.html // 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 { try {
const p = handleMessage(wallet.db, wallet, sender, req.type, req.detail); const p = handleMessage(sender, req.type, req.detail);
const r = await p; const r = await p;
try { try {
sendResponse(r); sendResponse(r);
@ -428,6 +485,11 @@ function clearRateLimitCache() {
*/ */
let currentWallet: Wallet|undefined; let currentWallet: Wallet|undefined;
/**
* Last version if an outdated DB, if applicable.
*/
let oldDbVersion: number|undefined;
async function reinitWallet() { async function reinitWallet() {
if (currentWallet) { if (currentWallet) {
@ -548,13 +610,7 @@ export async function wxMain() {
// Handlers for messages coming directly from the content // Handlers for messages coming directly from the content
// script on the page // script on the page
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
const wallet = currentWallet; dispatch(req, sender, sendResponse);
if (!wallet) {
console.warn("wallet not available while handling message");
console.warn("dropped request message was", req);
return;
}
dispatch(wallet, req, sender, sendResponse);
return true; return true;
}); });
@ -619,8 +675,10 @@ function openTalerDb(): Promise<IDBDatabase> {
break; break;
default: default:
if (e.oldVersion !== DB_VERSION) { if (e.oldVersion !== DB_VERSION) {
window.alert("Incompatible wallet dababase version, please reset" + oldDbVersion = e.oldVersion;
" db."); chrome.tabs.create({
url: chrome.extension.getURL("/src/webex/pages/reset-required.html"),
});
chrome.browserAction.setBadgeText({text: "err"}); chrome.browserAction.setBadgeText({text: "err"});
chrome.browserAction.setBadgeBackgroundColor({color: "#F00"}); chrome.browserAction.setBadgeBackgroundColor({color: "#F00"});
throw Error("incompatible DB"); throw Error("incompatible DB");

View File

@ -63,6 +63,7 @@
"src/webex/pages/logs.tsx", "src/webex/pages/logs.tsx",
"src/webex/pages/payback.tsx", "src/webex/pages/payback.tsx",
"src/webex/pages/popup.tsx", "src/webex/pages/popup.tsx",
"src/webex/pages/reset-required.tsx",
"src/webex/pages/show-db.ts", "src/webex/pages/show-db.ts",
"src/webex/pages/tree.tsx", "src/webex/pages/tree.tsx",
"src/webex/renderHtml.tsx", "src/webex/renderHtml.tsx",

View File

@ -73,6 +73,7 @@ module.exports = function (env) {
"show-db": "./src/webex/pages/show-db.ts", "show-db": "./src/webex/pages/show-db.ts",
"tree": "./src/webex/pages/tree.tsx", "tree": "./src/webex/pages/tree.tsx",
"payback": "./src/webex/pages/payback.tsx", "payback": "./src/webex/pages/payback.tsx",
"reset-required": "./src/webex/pages/reset-required.tsx",
}, },
plugins: [ plugins: [
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({