diff options
Diffstat (limited to 'src/webex')
| -rw-r--r-- | src/webex/messages.ts | 6 | ||||
| -rw-r--r-- | src/webex/pages/popup.tsx | 3 | ||||
| -rw-r--r-- | src/webex/pages/reset-required.html | 30 | ||||
| -rw-r--r-- | src/webex/pages/reset-required.tsx | 73 | ||||
| -rw-r--r-- | src/webex/style/wallet.css | 4 | ||||
| -rw-r--r-- | src/webex/wxApi.ts | 35 | ||||
| -rw-r--r-- | src/webex/wxBackend.ts | 208 | 
7 files changed, 278 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"); | 
