taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

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:
Mcontent_scripts/notify.ts | 23+++++++++++++++++++++--
Tlib/vendor/react-dom.js | 0
Tlib/vendor/react.js | 0
Mlib/wallet/db.ts | 5++---
Mlib/wallet/query.ts | 16++++++++++++++++
Mlib/wallet/renderHtml.tsx | 10+++++-----
Mlib/wallet/wallet.ts | 37+++++++++++++++++++++++++++++++++++++
Mlib/wallet/wxMessaging.ts | 11+++++++++++
Mpages/confirm-contract.tsx | 62++++++++++++++++++++++++++++++++++++++++++++------------------
Mpages/tree.html | 3+++
Mpages/tree.tsx | 13+++++++------
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")!); }