gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNUnet-SVN] [taler-wallet-webex] branch master updated: fix performance


From: gnunet
Subject: [GNUnet-SVN] [taler-wallet-webex] branch master updated: fix performance and UI issues with tipping
Date: Thu, 01 Feb 2018 07:19:17 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-webex.

The following commit(s) were added to refs/heads/master by this push:
     new d9683861 fix performance and UI issues with tipping
d9683861 is described below

commit d9683861f98277e7121ff47d2bd223c2d0c2cd34
Author: Florian Dold <address@hidden>
AuthorDate: Thu Feb 1 07:19:03 2018 +0100

    fix performance and UI issues with tipping
---
 src/query.ts                         | 29 ++++++++++++++++
 src/wallet.ts                        | 57 ++++++++++++++++++++-----------
 src/walletTypes.ts                   |  8 +++--
 src/webex/pages/confirm-contract.tsx |  2 +-
 src/webex/pages/tip.tsx              | 65 +++++++++++++++++++++++++-----------
 5 files changed, 119 insertions(+), 42 deletions(-)

diff --git a/src/query.ts b/src/query.ts
index e45596c6..f21f8202 100644
--- a/src/query.ts
+++ b/src/query.ts
@@ -697,6 +697,31 @@ export class QueryRoot {
     return this;
   }
 
+  /**
+   * Put an object into a store or return an existing record.
+   */
+  putOrGetExisting<T>(store: Store<T>, val: T, key: IDBValidKey): Promise<T> {
+    this.checkFinished();
+    const {resolve, promise} = openPromise();
+    const doPutOrGet = (tx: IDBTransaction) => {
+      const objstore = tx.objectStore(store.name);
+      const req = objstore.get(key);
+      req.onsuccess = () => {
+        if (req.result !== undefined) {
+          resolve(req.result);
+        } else {
+          const req2 = objstore.add(val);
+          req2.onsuccess = () => {
+            resolve(val);
+          };
+        }
+      };
+    };
+    this.scheduleFinish();
+    this.addWork(doPutOrGet, store.name, true);
+    return promise;
+  }
+
 
   putWithResult<T>(store: Store<T>, val: T): Promise<IDBValidKey> {
     this.checkFinished();
@@ -892,8 +917,12 @@ export class QueryRoot {
         resolve();
       };
       tx.onabort = () => {
+        console.warn(`aborted ${mode} transaction on stores [${[... 
this.stores]}]`);
         reject(Error("transaction aborted"));
       };
+      tx.onerror = (e) => {
+        console.warn(`error in transaction`, (e.target as any).error);
+      };
       for (const w of this.work) {
         w(tx);
       }
diff --git a/src/wallet.ts b/src/wallet.ts
index c4308b8d..95e7246f 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -316,6 +316,7 @@ export class Wallet {
   private timerGroup: TimerGroup;
   private speculativePayData: SpeculativePayData | undefined;
   private cachedNextUrl: { [fulfillmentUrl: string]: NextUrlResult } = {};
+  private activeTipOperations: { [s: string]: Promise<TipRecord> } = {};
 
   /**
    * Set of identifiers for running operations.
@@ -2744,20 +2745,34 @@ export class Wallet {
     return feeAcc;
   }
 
-  /**
-   * Workaround for merchant bug (#5258)
-   */
-  private tipPickupWorkaround: { [tipId: string]: boolean } = {};
 
   async processTip(tipToken: TipToken): Promise<TipRecord> {
+    const merchantDomain = new URI(tipToken.pickup_url).origin();
+    const key = tipToken.tip_id + merchantDomain;
+
+    if (this.activeTipOperations[key]) {
+      return this.activeTipOperations[key];
+    }
+    const p = this.processTipImpl(tipToken);
+    this.activeTipOperations[key] = p
+    try {
+      return await p;
+    } finally {
+      delete this.activeTipOperations[key];
+    }
+  }
+
+
+  private async processTipImpl(tipToken: TipToken): Promise<TipRecord> {
     console.log("got tip token", tipToken);
 
+    const merchantDomain = new URI(tipToken.pickup_url).origin();
+
     const deadlineSec = getTalerStampSec(tipToken.expiration);
     if (!deadlineSec) {
       throw Error("tipping failed (invalid expiration)");
     }
 
-    const merchantDomain = new URI(tipToken.pickup_url).origin();
     let tipRecord = await this.q().get(Stores.tips, [tipToken.tip_id, 
merchantDomain]);
 
     if (tipRecord && tipRecord.pickedUp) {
@@ -2783,21 +2798,16 @@ export class Wallet {
       tipId: tipToken.tip_id,
     };
 
+    let merchantResp;
+
+    tipRecord = await this.q().putOrGetExisting(Stores.tips, tipRecord, 
[tipRecord.tipId, merchantDomain]);
+
     // Planchets in the form that the merchant expects
     const planchetsDetail: TipPlanchetDetail[] = tipRecord.planchets.map((p) 
=> ({
       coin_ev: p.coinEv,
       denom_pub_hash: p.denomPubHash,
     }));
 
-    let merchantResp;
-
-    await this.q().put(Stores.tips, tipRecord).finish();
-
-    if (this.tipPickupWorkaround[tipRecord.tipId]) {
-      // Be careful to not accidentally download twice (#5258)
-      return tipRecord;
-    }
-
     try {
       const config = {
         validateStatus: (s: number) => s === 200,
@@ -2809,8 +2819,6 @@ export class Wallet {
       throw e;
     }
 
-    this.tipPickupWorkaround[tipToken.tip_id] = true;
-
     const response = TipResponse.checked(merchantResp.data);
 
     if (response.reserve_sigs.length !== tipRecord.planchets.length) {
@@ -2880,11 +2888,20 @@ export class Wallet {
 
 
   async getTipStatus(tipToken: TipToken): Promise<TipStatus> {
-    const tipRecord = await this.processTip(tipToken);
-    const rci = await this.getReserveCreationInfo(tipRecord.exchangeUrl, 
tipRecord.amount);
+    const tipId = tipToken.tip_id;
+    const merchantDomain = new URI(tipToken.pickup_url).origin();
+    let tipRecord = await this.q().get(Stores.tips, [tipId, merchantDomain]);
+    const amount = Amounts.parseOrThrow(tipToken.amount);
+    const exchangeUrl = tipToken.exchange_url;
+    this.processTip(tipToken);
+    const nextUrl = tipToken.next_url;
     const tipStatus: TipStatus = {
-      rci,
-      tip: tipRecord,
+      accepted: !!tipRecord && tipRecord.accepted,
+      amount,
+      exchangeUrl,
+      merchantDomain,
+      nextUrl,
+      tipRecord,
     };
     return tipStatus;
   }
diff --git a/src/walletTypes.ts b/src/walletTypes.ts
index edcf6583..562d12df 100644
--- a/src/walletTypes.ts
+++ b/src/walletTypes.ts
@@ -436,8 +436,12 @@ export interface CoinWithDenom {
  * Status of processing a tip.
  */
 export interface TipStatus {
-  tip: TipRecord;
-  rci?: ReserveCreationInfo;
+  accepted: boolean;
+  amount: AmountJson;
+  nextUrl: string;
+  merchantDomain: string;
+  exchangeUrl: string;
+  tipRecord?: TipRecord;
 }
 
 
diff --git a/src/webex/pages/confirm-contract.tsx 
b/src/webex/pages/confirm-contract.tsx
index 78e90ee0..b851bf1d 100644
--- a/src/webex/pages/confirm-contract.tsx
+++ b/src/webex/pages/confirm-contract.tsx
@@ -260,7 +260,7 @@ class ContractPrompt extends 
React.Component<ContractPromptProps, ContractPrompt
       return;
     }
     console.log("payResult", payResult);
-    document.location.href = payResult.nextUrl;
+    document.location.replace(payResult.nextUrl);
     this.setState({ holdCheck: true });
   }
 
diff --git a/src/webex/pages/tip.tsx b/src/webex/pages/tip.tsx
index 578ae6aa..f21bc0ea 100644
--- a/src/webex/pages/tip.tsx
+++ b/src/webex/pages/tip.tsx
@@ -31,6 +31,7 @@ import * as i18n from "../../i18n";
 import {
   acceptTip,
   getTipStatus,
+  getReserveCreationInfo,
 } from "../wxApi";
 
 import {
@@ -40,7 +41,7 @@ import {
 
 import * as Amounts from "../../amounts";
 import { TipToken } from "../../talerTypes";
-import { TipStatus } from "../../walletTypes";
+import { ReserveCreationInfo, TipStatus } from "../../walletTypes";
 
 interface TipDisplayProps {
   tipToken: TipToken;
@@ -48,18 +49,22 @@ interface TipDisplayProps {
 
 interface TipDisplayState {
   tipStatus?: TipStatus;
+  rci?: ReserveCreationInfo;
   working: boolean;
+  discarded: boolean;
 }
 
 class TipDisplay extends React.Component<TipDisplayProps, TipDisplayState> {
   constructor(props: TipDisplayProps) {
     super(props);
-    this.state = { working: false };
+    this.state = { working: false, discarded: false };
   }
 
   async update() {
     const tipStatus = await getTipStatus(this.props.tipToken);
     this.setState({ tipStatus });
+    const rci = await getReserveCreationInfo(tipStatus.exchangeUrl, 
tipStatus.amount);
+    this.setState({ rci });
   }
 
   componentDidMount() {
@@ -74,8 +79,8 @@ class TipDisplay extends React.Component<TipDisplayProps, 
TipDisplayState> {
     this.update();
   }
 
-  renderExchangeInfo(ts: TipStatus) {
-    const rci = ts.rci;
+  renderExchangeInfo() {
+    const rci = this.state.rci;
     if (!rci) {
       return <p>Waiting for info about exchange ...</p>;
     }
@@ -99,12 +104,30 @@ class TipDisplay extends React.Component<TipDisplayProps, 
TipDisplayState> {
     acceptTip(this.props.tipToken);
   }
 
-  renderButtons() {
-    return (
+  discard() {
+    this.setState({ discarded: true });
+  }
+
+  render(): JSX.Element {
+    const ts = this.state.tipStatus;
+    if (!ts) {
+      return <p>Processing ...</p>;
+    }
+
+    const renderAccepted = () => (
+      <>
+        <p>You've accepted this tip! <a href={ts.nextUrl}>Go back to 
merchant</a></p>
+        {this.renderExchangeInfo()}
+      </>
+    );
+
+    const renderButtons = () => (
+      <>
       <form className="pure-form">
         <button
             className="pure-button pure-button-primary"
             type="button"
+            disabled={!(this.state.rci && this.state.tipStatus)}
             onClick={() => this.accept()}>
           { this.state.working
             ? <span><object className="svg-icon svg-baseline" 
data="/img/spinner-bars.svg" /> </span>
@@ -112,26 +135,30 @@ class TipDisplay extends React.Component<TipDisplayProps, 
TipDisplayState> {
           Accept tip
         </button>
         {" "}
-        <button className="pure-button" type="button" onClick={() => { 
window.close(); }}>Discard tip</button>
+        <button className="pure-button" type="button" onClick={() => 
this.discard()}>
+          Discard tip
+        </button>
       </form>
+      { this.renderExchangeInfo() }
+      </>
+    );
+
+    const renderDiscarded = () => (
+      <p>You've discarded this tip. <a href={ts.nextUrl}>Go back to 
merchant.</a></p>
     );
-  }
 
-  render(): JSX.Element {
-    const ts = this.state.tipStatus;
-    if (!ts) {
-      return <p>Processing ...</p>;
-    }
     return (
       <div>
         <h2>Tip Received!</h2>
-        <p>You received a tip of 
<strong>{renderAmount(ts.tip.amount)}</strong> from <span> </span>
-        <strong>{ts.tip.merchantDomain}</strong>.</p>
-        {ts.tip.accepted
-          ? <p>You've accepted this tip! <a href={ts.tip.nextUrl}>Go back to 
merchant</a></p>
-          : this.renderButtons()
+        <p>You received a tip of <strong>{renderAmount(ts.amount)}</strong> 
from <span> </span>
+        <strong>{ts.merchantDomain}</strong>.</p>
+        {
+          this.state.discarded
+          ? renderDiscarded()
+          : ts.accepted
+          ? renderAccepted()
+          : renderButtons()
         }
-        {this.renderExchangeInfo(ts)}
       </div>
     );
   }

-- 
To stop receiving notification emails like this one, please contact
address@hidden



reply via email to

[Prev in Thread] Current Thread [Next in Thread]