fix small react issues

This commit is contained in:
Florian Dold 2016-11-13 10:17:39 +01:00
parent b2128609ac
commit eb84d5747a
11 changed files with 148 additions and 22341 deletions

View File

@ -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() { function init() {
chrome.runtime.sendMessage({type: "get-tab-cookie"}, (resp) => { chrome.runtime.sendMessage({type: "get-tab-cookie"}, (resp) => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
@ -270,12 +289,12 @@ namespace TalerNotify {
} }
}; };
await putHistory(historyEntry); await putHistory(historyEntry);
let offerId = await saveOffer(offer);
const uri = URI(chrome.extension.getURL( const uri = URI(chrome.extension.getURL(
"pages/confirm-contract.html")); "pages/confirm-contract.html"));
const params = { const params = {
offer: JSON.stringify(offer), offerId: offerId.toString(),
merchantPageUrl: document.location.href,
}; };
const target = uri.query(params).href(); const target = uri.query(params).href();
if (msg.replace_navigation === true) { if (msg.replace_navigation === true) {

18525
lib/vendor/react-dom.js vendored

File diff suppressed because it is too large Load Diff

1
lib/vendor/react-dom.js vendored Symbolic link
View File

@ -0,0 +1 @@
react-dom.min.js

3783
lib/vendor/react.js vendored

File diff suppressed because it is too large Load Diff

1
lib/vendor/react.js vendored Symbolic link
View File

@ -0,0 +1 @@
react.min.js

View File

@ -26,7 +26,7 @@ import {IExchangeInfo} from "./types";
*/ */
const DB_NAME = "taler"; const DB_NAME = "taler";
const DB_VERSION = 10; const DB_VERSION = 11;
import {Stores} from "./wallet"; import {Stores} from "./wallet";
import {Store, Index} from "./query"; import {Store, Index} from "./query";
@ -114,4 +114,4 @@ export function exportDb(db: IDBDatabase): Promise<any> {
export function deleteDb() { export function deleteDb() {
indexedDB.deleteDatabase(DB_NAME); indexedDB.deleteDatabase(DB_NAME);
} }

View File

@ -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 { mutate<T>(store: Store<T>, key: any, f: (v: T) => T): QueryRoot {
let doPut = (tx: IDBTransaction) => { let doPut = (tx: IDBTransaction) => {
let reqGet = tx.objectStore(store.name).get(key); let reqGet = tx.objectStore(store.name).get(key);

View File

@ -34,15 +34,15 @@ export function renderContract(contract: Contract): JSX.Element {
return ( return (
<div> <div>
<p>{ <p>
i18n.parts`${merchantName} The merchant {merchantName}
wants to enter a contract over ${amount} wants to enter a contract over {amount}{" "}
with you.`} with you.
</p> </p>
<p>{i18n`You are about to purchase:`}</p> <p>{i18n`You are about to purchase:`}</p>
<ul> <ul>
{contract.products.map( {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> </ul>
</div> </div>

View File

@ -142,6 +142,15 @@ export class Offer {
@Checkable.String @Checkable.String
H_contract: 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; static checked: (obj: any) => Offer;
} }
@ -297,6 +306,15 @@ export namespace Stores {
timestampIndex = new Index<number,HistoryRecord>(this, "timestamp", "timestamp"); 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> { class TransactionsStore extends Store<Transaction> {
constructor() { constructor() {
super("transactions", {keyPath: "contractHash"}); super("transactions", {keyPath: "contractHash"});
@ -314,6 +332,7 @@ export namespace Stores {
export let coins: CoinsStore = new CoinsStore(); export let coins: CoinsStore = new CoinsStore();
export let refresh: Store<RefreshSession> = new Store<RefreshSession>("refresh", {keyPath: "meltCoinPub"}); export let refresh: Store<RefreshSession> = new Store<RefreshSession>("refresh", {keyPath: "meltCoinPub"});
export let history: HistoryStore = new HistoryStore(); export let history: HistoryStore = new HistoryStore();
export let offers: OffersStore = new OffersStore();
export let precoins: Store<PreCoin> = new Store<PreCoin>("precoins", {keyPath: "coinPub"}); 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, * Add a contract to the wallet and sign coins,
* but do not send them yet. * but do not send them yet.
@ -1525,6 +1556,12 @@ export class Wallet {
return {history}; return {history};
} }
async getOffer(offerId: number): Promise<any> {
let offer = await this.q() .get(Stores.offers, offerId);
return offer;
}
async getExchanges(): Promise<IExchangeInfo[]> { async getExchanges(): Promise<IExchangeInfo[]> {
return this.q() return this.q()
.iter<IExchangeInfo>(Stores.exchanges) .iter<IExchangeInfo>(Stores.exchanges)

View File

@ -168,6 +168,14 @@ function makeHandlers(db: IDBDatabase,
} }
return wallet.putHistory(detail.historyEntry); 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) { ["reserve-creation-info"]: function (detail, sender) {
if (!detail.baseUrl || typeof detail.baseUrl !== "string") { if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
return Promise.resolve({ error: "bad url" }); return Promise.resolve({ error: "bad url" });
@ -183,6 +191,9 @@ function makeHandlers(db: IDBDatabase,
// TODO: limit history length // TODO: limit history length
return wallet.getHistory(); return wallet.getHistory();
}, },
["get-offer"]: function (detail, sender) {
return wallet.getOffer(detail.offerId);
},
["get-exchanges"]: function (detail, sender) { ["get-exchanges"]: function (detail, sender) {
return wallet.getExchanges(); return wallet.getExchanges();
}, },

View File

@ -24,6 +24,7 @@
import {substituteFulfillmentUrl} from "../lib/wallet/helpers"; import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
import {Contract, AmountJson, IExchangeInfo} from "../lib/wallet/types"; import {Contract, AmountJson, IExchangeInfo} from "../lib/wallet/types";
import {Offer} from "../lib/wallet/wallet";
import {renderContract, prettyAmount} from "../lib/wallet/renderHtml"; import {renderContract, prettyAmount} from "../lib/wallet/renderHtml";
"use strict"; "use strict";
import {getExchanges} from "../lib/wallet/wxApi"; import {getExchanges} from "../lib/wallet/wxApi";
@ -43,20 +44,17 @@ interface DetailProps {
class Details extends React.Component<DetailProps, DetailState> { class Details extends React.Component<DetailProps, DetailState> {
constructor(props: DetailProps) { constructor(props: DetailProps) {
super(props); super(props);
this.setState({ console.log("new Details component created");
this.state = {
collapsed: props.collapsed, collapsed: props.collapsed,
exchanges: null exchanges: null
}); };
console.log("initial state:", this.state); console.log("initial state:", this.state);
this.update(); this.update();
} }
componentWillReceiveProps(props: DetailProps) {
this.setState({collapsed: props.collapsed} as any);
}
async update() { async update() {
let exchanges = await getExchanges(); let exchanges = await getExchanges();
this.setState({exchanges} as any); this.setState({exchanges} as any);
@ -100,10 +98,11 @@ class Details extends React.Component<DetailProps, DetailState> {
} }
interface ContractPromptProps { interface ContractPromptProps {
offer: any; offerId: number;
} }
interface ContractPromptState { interface ContractPromptState {
offer: any;
error: string|null; error: string|null;
payDisabled: boolean; payDisabled: boolean;
} }
@ -112,12 +111,14 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
constructor() { constructor() {
super(); super();
this.state = { this.state = {
offer: undefined,
error: null, error: null,
payDisabled: true, payDisabled: true,
} }
} }
componentWillMount() { componentWillMount() {
this.update();
this.checkPayment(); this.checkPayment();
} }
@ -125,11 +126,31 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
// FIXME: abort running ops // 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() { checkPayment() {
let msg = { let msg = {
type: 'check-pay', type: 'check-pay',
detail: { detail: {
offer: this.props.offer offer: this.state.offer
} }
}; };
chrome.runtime.sendMessage(msg, (resp) => { chrome.runtime.sendMessage(msg, (resp) => {
@ -149,12 +170,12 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
this.state.error = null; this.state.error = null;
} }
this.setState({} as any); this.setState({} as any);
window.setTimeout(() => this.checkPayment(), 300); window.setTimeout(() => this.checkPayment(), 500);
}); });
} }
doPayment() { doPayment() {
let d = {offer: this.props.offer}; let d = {offer: this.state.offer};
chrome.runtime.sendMessage({type: 'confirm-pay', detail: d}, (resp) => { chrome.runtime.sendMessage({type: 'confirm-pay', detail: d}, (resp) => {
if (resp.error) { if (resp.error) {
console.log("confirm-pay error", JSON.stringify(resp)); console.log("confirm-pay error", JSON.stringify(resp));
@ -173,22 +194,29 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
let c = d.offer.contract; let c = d.offer.contract;
console.log("contract", c); console.log("contract", c);
document.location.href = substituteFulfillmentUrl(c.fulfillment_url, document.location.href = substituteFulfillmentUrl(c.fulfillment_url,
this.props.offer); this.state.offer);
}); });
} }
render() { render() {
let c = this.props.offer.contract; if (!this.state.offer) {
return <span>...</span>;
}
let c = this.state.offer.contract;
return ( return (
<div> <div>
{renderContract(c)} <div>
{renderContract(c)}
</div>
<button onClick={() => this.doPayment()} <button onClick={() => this.doPayment()}
disabled={this.state.payDisabled} disabled={this.state.payDisabled}
className="accept"> className="accept">
Confirm payment Confirm payment
</button> </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}/> <Details contract={c} collapsed={!this.state.error}/>
</div> </div>
); );
@ -199,10 +227,8 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
export function main() { export function main() {
let url = URI(document.location.href); let url = URI(document.location.href);
let query: any = URI.parseQuery(url.query()); let query: any = URI.parseQuery(url.query());
let offer = JSON.parse(query.offer); let offerId = JSON.parse(query.offerId);
console.dir(offer);
let contract = offer.contract;
ReactDOM.render(<ContractPrompt offer={offer}/>, document.getElementById( ReactDOM.render(<ContractPrompt offerId={offerId}/>, document.getElementById(
"contract")!); "contract")!);
} }

View File

@ -30,4 +30,7 @@
} }
</style> </style>
<body>
<div id="container"></div>
</body>
</html> </html>

View File

@ -358,7 +358,7 @@ class ExchangeView extends React.Component<ExchangeProps, void> {
} }
interface ExchangesListState { interface ExchangesListState {
exchanges: IExchangeInfo[]; exchanges?: IExchangeInfo[];
} }
class ExchangesList extends React.Component<any, ExchangesListState> { class ExchangesList extends React.Component<any, ExchangesListState> {
@ -371,8 +371,8 @@ class ExchangesList extends React.Component<any, ExchangesListState> {
this.update(); this.update();
} }
}); });
this.update(); this.update();
this.state = {} as any;
} }
async update() { async update() {
@ -382,18 +382,19 @@ class ExchangesList extends React.Component<any, ExchangesListState> {
} }
render(): JSX.Element { render(): JSX.Element {
if (!this.state.exchanges) { let exchanges = this.state.exchanges;
if (!exchanges) {
return <span>...</span>; return <span>...</span>;
} }
return ( return (
<div className="tree-item"> <div className="tree-item">
Exchanges ({this.state.exchanges.length.toString()}): Exchanges ({exchanges.length.toString()}):
{this.state.exchanges.map(e => <ExchangeView exchange={e} />)} {exchanges.map(e => <ExchangeView exchange={e} />)}
</div> </div>
); );
} }
} }
export function main() { export function main() {
ReactDOM.render(<ExchangesList />, document.body); ReactDOM.render(<ExchangesList />, document.getElementById("container")!);
} }