fix performance and UI issues with tipping
This commit is contained in:
parent
97f6e68ce3
commit
d9683861f9
29
src/query.ts
29
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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 });
|
||||
}
|
||||
|
||||
|
@ -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,22 +104,8 @@ class TipDisplay extends React.Component<TipDisplayProps, TipDisplayState> {
|
||||
acceptTip(this.props.tipToken);
|
||||
}
|
||||
|
||||
renderButtons() {
|
||||
return (
|
||||
<form className="pure-form">
|
||||
<button
|
||||
className="pure-button pure-button-primary"
|
||||
type="button"
|
||||
onClick={() => this.accept()}>
|
||||
{ this.state.working
|
||||
? <span><object className="svg-icon svg-baseline" data="/img/spinner-bars.svg" /> </span>
|
||||
: null }
|
||||
Accept tip
|
||||
</button>
|
||||
{" "}
|
||||
<button className="pure-button" type="button" onClick={() => { window.close(); }}>Discard tip</button>
|
||||
</form>
|
||||
);
|
||||
discard() {
|
||||
this.setState({ discarded: true });
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
@ -122,16 +113,52 @@ class TipDisplay extends React.Component<TipDisplayProps, TipDisplayState> {
|
||||
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>
|
||||
: null }
|
||||
Accept 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>
|
||||
);
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user