commit eb84d5747aac0de781d64fb9cdbf2da13006d85e
parent b2128609ac8159a14224deba399144b3400c8c20
Author: Florian Dold <florian.dold@gmail.com>
Date: Sun, 13 Nov 2016 10:17:39 +0100
fix small react issues
Diffstat:
11 files changed, 146 insertions(+), 34 deletions(-)
diff --git a/content_scripts/notify.ts b/content_scripts/notify.ts
@@ -102,6 +102,25 @@ namespace TalerNotify {
});
}
+ function saveOffer(offer: any): Promise<number> {
+ const walletMsg = {
+ type: "save-offer",
+ detail: {
+ offer: {
+ contract: offer.contract,
+ merchant_sig: offer.merchant_sig,
+ H_contract: offer.H_contract,
+ offer_time: new Date().getTime() / 1000
+ },
+ },
+ };
+ return new Promise((resolve, reject) => {
+ chrome.runtime.sendMessage(walletMsg, (resp: any) => {
+ resolve(resp);
+ });
+ });
+ }
+
function init() {
chrome.runtime.sendMessage({type: "get-tab-cookie"}, (resp) => {
if (chrome.runtime.lastError) {
@@ -270,12 +289,12 @@ namespace TalerNotify {
}
};
await putHistory(historyEntry);
+ let offerId = await saveOffer(offer);
const uri = URI(chrome.extension.getURL(
"pages/confirm-contract.html"));
const params = {
- offer: JSON.stringify(offer),
- merchantPageUrl: document.location.href,
+ offerId: offerId.toString(),
};
const target = uri.query(params).href();
if (msg.replace_navigation === true) {
diff --git a/lib/vendor/react-dom.js b/lib/vendor/react-dom.js
diff --git a/lib/vendor/react.js b/lib/vendor/react.js
diff --git a/lib/wallet/db.ts b/lib/wallet/db.ts
@@ -26,7 +26,7 @@ import {IExchangeInfo} from "./types";
*/
const DB_NAME = "taler";
-const DB_VERSION = 10;
+const DB_VERSION = 11;
import {Stores} from "./wallet";
import {Store, Index} from "./query";
@@ -114,4 +114,4 @@ export function exportDb(db: IDBDatabase): Promise<any> {
export function deleteDb() {
indexedDB.deleteDatabase(DB_NAME);
-}
-\ No newline at end of file
+}
diff --git a/lib/wallet/query.ts b/lib/wallet/query.ts
@@ -423,6 +423,22 @@ export class QueryRoot implements PromiseLike<void> {
}
+ putWithResult<T>(store: Store<T>, val: T): Promise<IDBValidKey> {
+ const {resolve, promise} = openPromise();
+ let doPutWithResult = (tx: IDBTransaction) => {
+ let req = tx.objectStore(store.name).put(val);
+ req.onsuccess = () => {
+ resolve(req.result);
+ }
+ this.scheduleFinish();
+ };
+ this.addWork(doPutWithResult, store.name, true);
+ return Promise.resolve()
+ .then(() => this.finish())
+ .then(() => promise);
+ }
+
+
mutate<T>(store: Store<T>, key: any, f: (v: T) => T): QueryRoot {
let doPut = (tx: IDBTransaction) => {
let reqGet = tx.objectStore(store.name).get(key);
diff --git a/lib/wallet/renderHtml.tsx b/lib/wallet/renderHtml.tsx
@@ -34,15 +34,15 @@ export function renderContract(contract: Contract): JSX.Element {
return (
<div>
- <p>{
- i18n.parts`${merchantName}
- wants to enter a contract over ${amount}
- with you.`}
+ <p>
+ The merchant {merchantName}
+ wants to enter a contract over {amount}{" "}
+ with you.
</p>
<p>{i18n`You are about to purchase:`}</p>
<ul>
{contract.products.map(
- (p: any) => (<li>{`${p.description}: ${prettyAmount(p.price)}`}</li>))
+ (p: any, i: number) => (<li key={i}>{`${p.description}: ${prettyAmount(p.price)}`}</li>))
}
</ul>
</div>
diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts
@@ -142,6 +142,15 @@ export class Offer {
@Checkable.String
H_contract: string;
+ @Checkable.Number
+ offer_time: number;
+
+ /**
+ * Serial ID when the offer is stored in the wallet DB.
+ */
+ @Checkable.Optional(Checkable.Number)
+ id?: number;
+
static checked: (obj: any) => Offer;
}
@@ -297,6 +306,15 @@ export namespace Stores {
timestampIndex = new Index<number,HistoryRecord>(this, "timestamp", "timestamp");
}
+ class OffersStore extends Store<Offer> {
+ constructor() {
+ super("offers", {
+ keyPath: "id",
+ autoIncrement: true
+ });
+ }
+ }
+
class TransactionsStore extends Store<Transaction> {
constructor() {
super("transactions", {keyPath: "contractHash"});
@@ -314,6 +332,7 @@ export namespace Stores {
export let coins: CoinsStore = new CoinsStore();
export let refresh: Store<RefreshSession> = new Store<RefreshSession>("refresh", {keyPath: "meltCoinPub"});
export let history: HistoryStore = new HistoryStore();
+ export let offers: OffersStore = new OffersStore();
export let precoins: Store<PreCoin> = new Store<PreCoin>("precoins", {keyPath: "coinPub"});
}
@@ -585,6 +604,18 @@ export class Wallet {
}
+ async saveOffer(offer: Offer): Promise<number> {
+ console.log(`saving offer in wallet.ts`);
+ let id = await this.q().putWithResult(Stores.offers, offer);
+ this.notifier.notify();
+ console.log(`saved offer with id ${id}`);
+ if (typeof id !== "number") {
+ throw Error("db schema wrong");
+ }
+ return id;
+ }
+
+
/**
* Add a contract to the wallet and sign coins,
* but do not send them yet.
@@ -1525,6 +1556,12 @@ export class Wallet {
return {history};
}
+
+ async getOffer(offerId: number): Promise<any> {
+ let offer = await this.q() .get(Stores.offers, offerId);
+ return offer;
+ }
+
async getExchanges(): Promise<IExchangeInfo[]> {
return this.q()
.iter<IExchangeInfo>(Stores.exchanges)
diff --git a/lib/wallet/wxMessaging.ts b/lib/wallet/wxMessaging.ts
@@ -168,6 +168,14 @@ function makeHandlers(db: IDBDatabase,
}
return wallet.putHistory(detail.historyEntry);
},
+ ["save-offer"]: function (detail: any) {
+ let offer = detail.offer;
+ if (!offer) {
+ return Promise.resolve({ error: "offer missing" });
+ }
+ console.log("handling safe-offer");
+ return wallet.saveOffer(offer);
+ },
["reserve-creation-info"]: function (detail, sender) {
if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
return Promise.resolve({ error: "bad url" });
@@ -183,6 +191,9 @@ function makeHandlers(db: IDBDatabase,
// TODO: limit history length
return wallet.getHistory();
},
+ ["get-offer"]: function (detail, sender) {
+ return wallet.getOffer(detail.offerId);
+ },
["get-exchanges"]: function (detail, sender) {
return wallet.getExchanges();
},
diff --git a/pages/confirm-contract.tsx b/pages/confirm-contract.tsx
@@ -24,6 +24,7 @@
import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
import {Contract, AmountJson, IExchangeInfo} from "../lib/wallet/types";
+import {Offer} from "../lib/wallet/wallet";
import {renderContract, prettyAmount} from "../lib/wallet/renderHtml";
"use strict";
import {getExchanges} from "../lib/wallet/wxApi";
@@ -43,20 +44,17 @@ interface DetailProps {
class Details extends React.Component<DetailProps, DetailState> {
constructor(props: DetailProps) {
super(props);
- this.setState({
+ console.log("new Details component created");
+ this.state = {
collapsed: props.collapsed,
exchanges: null
- });
+ };
console.log("initial state:", this.state);
this.update();
}
- componentWillReceiveProps(props: DetailProps) {
- this.setState({collapsed: props.collapsed} as any);
- }
-
async update() {
let exchanges = await getExchanges();
this.setState({exchanges} as any);
@@ -100,10 +98,11 @@ class Details extends React.Component<DetailProps, DetailState> {
}
interface ContractPromptProps {
- offer: any;
+ offerId: number;
}
interface ContractPromptState {
+ offer: any;
error: string|null;
payDisabled: boolean;
}
@@ -112,12 +111,14 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
constructor() {
super();
this.state = {
+ offer: undefined,
error: null,
payDisabled: true,
}
}
componentWillMount() {
+ this.update();
this.checkPayment();
}
@@ -125,11 +126,31 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
// FIXME: abort running ops
}
+ async update() {
+ let offer = await this.getOffer();
+ this.setState({offer} as any);
+ this.checkPayment();
+ }
+
+ getOffer(): Promise<Offer> {
+ return new Promise((resolve, reject) => {
+ let msg = {
+ type: 'get-offer',
+ detail: {
+ offerId: this.props.offerId
+ }
+ };
+ chrome.runtime.sendMessage(msg, (resp) => {
+ resolve(resp);
+ });
+ })
+ }
+
checkPayment() {
let msg = {
type: 'check-pay',
detail: {
- offer: this.props.offer
+ offer: this.state.offer
}
};
chrome.runtime.sendMessage(msg, (resp) => {
@@ -149,12 +170,12 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
this.state.error = null;
}
this.setState({} as any);
- window.setTimeout(() => this.checkPayment(), 300);
+ window.setTimeout(() => this.checkPayment(), 500);
});
}
doPayment() {
- let d = {offer: this.props.offer};
+ let d = {offer: this.state.offer};
chrome.runtime.sendMessage({type: 'confirm-pay', detail: d}, (resp) => {
if (resp.error) {
console.log("confirm-pay error", JSON.stringify(resp));
@@ -173,22 +194,29 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
let c = d.offer.contract;
console.log("contract", c);
document.location.href = substituteFulfillmentUrl(c.fulfillment_url,
- this.props.offer);
+ this.state.offer);
});
}
render() {
- let c = this.props.offer.contract;
+ if (!this.state.offer) {
+ return <span>...</span>;
+ }
+ let c = this.state.offer.contract;
return (
<div>
- {renderContract(c)}
+ <div>
+ {renderContract(c)}
+ </div>
<button onClick={() => this.doPayment()}
disabled={this.state.payDisabled}
className="accept">
Confirm payment
</button>
- {(this.state.error ? <p className="errorbox">{this.state.error}</p> : <p />)}
+ <div>
+ {(this.state.error ? <p className="errorbox">{this.state.error}</p> : <p />)}
+ </div>
<Details contract={c} collapsed={!this.state.error}/>
</div>
);
@@ -199,10 +227,8 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
export function main() {
let url = URI(document.location.href);
let query: any = URI.parseQuery(url.query());
- let offer = JSON.parse(query.offer);
- console.dir(offer);
- let contract = offer.contract;
+ let offerId = JSON.parse(query.offerId);
- ReactDOM.render(<ContractPrompt offer={offer}/>, document.getElementById(
+ ReactDOM.render(<ContractPrompt offerId={offerId}/>, document.getElementById(
"contract")!);
}
diff --git a/pages/tree.html b/pages/tree.html
@@ -30,4 +30,7 @@
}
</style>
+ <body>
+ <div id="container"></div>
+ </body>
</html>
diff --git a/pages/tree.tsx b/pages/tree.tsx
@@ -358,7 +358,7 @@ class ExchangeView extends React.Component<ExchangeProps, void> {
}
interface ExchangesListState {
- exchanges: IExchangeInfo[];
+ exchanges?: IExchangeInfo[];
}
class ExchangesList extends React.Component<any, ExchangesListState> {
@@ -371,8 +371,8 @@ class ExchangesList extends React.Component<any, ExchangesListState> {
this.update();
}
});
-
this.update();
+ this.state = {} as any;
}
async update() {
@@ -382,18 +382,19 @@ class ExchangesList extends React.Component<any, ExchangesListState> {
}
render(): JSX.Element {
- if (!this.state.exchanges) {
+ let exchanges = this.state.exchanges;
+ if (!exchanges) {
return <span>...</span>;
}
return (
<div className="tree-item">
- Exchanges ({this.state.exchanges.length.toString()}):
- {this.state.exchanges.map(e => <ExchangeView exchange={e} />)}
+ Exchanges ({exchanges.length.toString()}):
+ {exchanges.map(e => <ExchangeView exchange={e} />)}
</div>
);
}
}
export function main() {
- ReactDOM.render(<ExchangesList />, document.body);
+ ReactDOM.render(<ExchangesList />, document.getElementById("container")!);
}