less ad-hoc messaging, fix some lint warnings

This commit is contained in:
Florian Dold 2017-05-29 16:27:53 +02:00
parent d0e0c6baf2
commit 1c3346cd53
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
12 changed files with 266 additions and 264 deletions

View File

@ -27,6 +27,7 @@ import {
AmountJson, AmountJson,
CoinRecord, CoinRecord,
DenominationRecord, DenominationRecord,
OfferRecord,
PayCoinInfo, PayCoinInfo,
PaybackRequest, PaybackRequest,
PreCoinRecord, PreCoinRecord,
@ -37,7 +38,6 @@ import {
import { import {
CoinWithDenom, CoinWithDenom,
OfferRecord,
} from "../wallet"; } from "../wallet";
import * as timer from "../timer"; import * as timer from "../timer";

View File

@ -29,6 +29,7 @@ import {
CoinRecord, CoinRecord,
CoinStatus, CoinStatus,
DenominationRecord, DenominationRecord,
OfferRecord,
PayCoinInfo, PayCoinInfo,
PaybackRequest, PaybackRequest,
PreCoinRecord, PreCoinRecord,
@ -39,7 +40,6 @@ import {
} from "../types"; } from "../types";
import { import {
CoinWithDenom, CoinWithDenom,
OfferRecord,
} from "../wallet"; } from "../wallet";
import { import {

View File

@ -40,7 +40,7 @@ if (!strings[lang]) {
console.log(`language ${lang} not found, defaulting to english`); console.log(`language ${lang} not found, defaulting to english`);
} }
let jed = new jedLib.Jed(strings[lang]); const jed = new jedLib.Jed(strings[lang]);
/** /**
@ -62,8 +62,8 @@ function toI18nString(strings: ReadonlyArray<string>) {
* Internationalize a string template with arbitrary serialized values. * Internationalize a string template with arbitrary serialized values.
*/ */
export function str(strings: TemplateStringsArray, ...values: any[]) { export function str(strings: TemplateStringsArray, ...values: any[]) {
let str = toI18nString(strings); const str = toI18nString(strings);
let tr = jed.translate(str).ifPlural(1, str).fetch(...values); const tr = jed.translate(str).ifPlural(1, str).fetch(...values);
return tr; return tr;
} }
@ -75,7 +75,7 @@ interface TranslateSwitchProps {
function stringifyChildren(children: any): string { function stringifyChildren(children: any): string {
let n = 1; let n = 1;
let ss = React.Children.map(children, (c) => { const ss = React.Children.map(children, (c) => {
if (typeof c === "string") { if (typeof c === "string") {
return c; return c;
} }
@ -113,23 +113,23 @@ interface TranslateProps {
*/ */
export class Translate extends React.Component<TranslateProps,void> { export class Translate extends React.Component<TranslateProps,void> {
render(): JSX.Element { render(): JSX.Element {
let s = stringifyChildren(this.props.children); const s = stringifyChildren(this.props.children);
let tr = jed.ngettext(s, s, 1).split(/%(\d+)\$s/).filter((e: any, i: number) => i % 2 == 0); const tr = jed.ngettext(s, s, 1).split(/%(\d+)\$s/).filter((e: any, i: number) => i % 2 == 0);
let childArray = React.Children.toArray(this.props.children!); const childArray = React.Children.toArray(this.props.children!);
for (let i = 0; i < childArray.length - 1; ++i) { for (let i = 0; i < childArray.length - 1; ++i) {
if ((typeof childArray[i]) == "string" && (typeof childArray[i+1]) == "string") { if ((typeof childArray[i]) == "string" && (typeof childArray[i+1]) == "string") {
childArray[i+1] = (childArray[i] as string).concat(childArray[i+1] as string); childArray[i+1] = (childArray[i] as string).concat(childArray[i+1] as string);
childArray.splice(i,1); childArray.splice(i,1);
} }
} }
let result = []; const result = [];
while (childArray.length > 0) { while (childArray.length > 0) {
let x = childArray.shift(); const x = childArray.shift();
if (x === undefined) { if (x === undefined) {
continue; continue;
} }
if (typeof x === "string") { if (typeof x === "string") {
let t = tr.shift(); const t = tr.shift();
result.push(t); result.push(t);
} else { } else {
result.push(x); result.push(x);
@ -159,7 +159,7 @@ export class TranslateSwitch extends React.Component<TranslateSwitchProps,void>{
render(): JSX.Element { render(): JSX.Element {
let singular: React.ReactElement<TranslationPluralProps> | undefined; let singular: React.ReactElement<TranslationPluralProps> | undefined;
let plural: React.ReactElement<TranslationPluralProps> | undefined; let plural: React.ReactElement<TranslationPluralProps> | undefined;
let children = this.props.children; const children = this.props.children;
if (children) { if (children) {
React.Children.forEach(children, (child: any) => { React.Children.forEach(children, (child: any) => {
if (child.type == TranslatePlural) { if (child.type == TranslatePlural) {
@ -192,23 +192,23 @@ interface TranslationPluralProps {
*/ */
export class TranslatePlural extends React.Component<TranslationPluralProps,void> { export class TranslatePlural extends React.Component<TranslationPluralProps,void> {
render(): JSX.Element { render(): JSX.Element {
let s = stringifyChildren(this.props.children); const s = stringifyChildren(this.props.children);
let tr = jed.ngettext(s, s, 1).split(/%(\d+)\$s/).filter((e: any, i: number) => i % 2 == 0); const tr = jed.ngettext(s, s, 1).split(/%(\d+)\$s/).filter((e: any, i: number) => i % 2 == 0);
let childArray = React.Children.toArray(this.props.children!); const childArray = React.Children.toArray(this.props.children!);
for (let i = 0; i < childArray.length - 1; ++i) { for (let i = 0; i < childArray.length - 1; ++i) {
if ((typeof childArray[i]) == "string" && (typeof childArray[i + 1]) == "string") { if ((typeof childArray[i]) == "string" && (typeof childArray[i + 1]) == "string") {
childArray[i+i] = childArray[i] as string + childArray[i + 1] as string; childArray[i+i] = childArray[i] as string + childArray[i + 1] as string;
childArray.splice(i,1); childArray.splice(i,1);
} }
} }
let result = []; const result = [];
while (childArray.length > 0) { while (childArray.length > 0) {
let x = childArray.shift(); const x = childArray.shift();
if (x === undefined) { if (x === undefined) {
continue; continue;
} }
if (typeof x === "string") { if (typeof x === "string") {
let t = tr.shift(); const t = tr.shift();
result.push(t); result.push(t);
} else { } else {
result.push(x); result.push(x);
@ -224,23 +224,23 @@ export class TranslatePlural extends React.Component<TranslationPluralProps,void
*/ */
export class TranslateSingular extends React.Component<TranslationPluralProps,void> { export class TranslateSingular extends React.Component<TranslationPluralProps,void> {
render(): JSX.Element { render(): JSX.Element {
let s = stringifyChildren(this.props.children); const s = stringifyChildren(this.props.children);
let tr = jed.ngettext(s, s, 1).split(/%(\d+)\$s/).filter((e: any, i: number) => i % 2 == 0); const tr = jed.ngettext(s, s, 1).split(/%(\d+)\$s/).filter((e: any, i: number) => i % 2 == 0);
let childArray = React.Children.toArray(this.props.children!); const childArray = React.Children.toArray(this.props.children!);
for (let i = 0; i < childArray.length - 1; ++i) { for (let i = 0; i < childArray.length - 1; ++i) {
if ((typeof childArray[i]) == "string" && (typeof childArray[i + 1]) == "string") { if ((typeof childArray[i]) == "string" && (typeof childArray[i + 1]) == "string") {
childArray[i+i] = childArray[i] as string + childArray[i + 1] as string; childArray[i+i] = childArray[i] as string + childArray[i + 1] as string;
childArray.splice(i,1); childArray.splice(i,1);
} }
} }
let result = []; const result = [];
while (childArray.length > 0) { while (childArray.length > 0) {
let x = childArray.shift(); const x = childArray.shift();
if (x === undefined) { if (x === undefined) {
continue; continue;
} }
if (typeof x === "string") { if (typeof x === "string") {
let t = tr.shift(); const t = tr.shift();
result.push(t); result.push(t);
} else { } else {
result.push(x); result.push(x);

View File

@ -1074,6 +1074,51 @@ export class Contract {
} }
/**
* Offer record, stored in the wallet's database.
*/
@Checkable.Class()
export class OfferRecord {
/**
* The contract that was offered by the merchant.
*/
@Checkable.Value(Contract)
contract: Contract;
/**
* Signature by the merchant over the contract details.
*/
@Checkable.String
merchant_sig: string;
/**
* Hash of the contract terms.
*/
@Checkable.String
H_contract: string;
/**
* Time when the offer was made.
*/
@Checkable.Number
offer_time: number;
/**
* Serial ID when the offer is stored in the wallet DB.
*/
@Checkable.Optional(Checkable.Number)
id?: number;
/**
* Verify that a value matches the schema of this class and convert it into a
* member.
*/
static checked: (obj: any) => OfferRecord;
}
/** /**
* Wire fee for one wire method as stored in the * Wire fee for one wire method as stored in the
* wallet's database. * wallet's database.
@ -1333,3 +1378,10 @@ export interface Notifier {
export function mkAmount(value: number, fraction: number, currency: string): AmountJson { export function mkAmount(value: number, fraction: number, currency: string): AmountJson {
return {value, fraction, currency}; return {value, fraction, currency};
} }
/**
* Possible responses for checkPay.
*/
export type CheckPayResult = "paid" | "payment-possible" | "insufficient-balance";
export type ConfirmPayResult = "paid" | "insufficient-balance";

View File

@ -50,9 +50,11 @@ import {
Amounts, Amounts,
Auditor, Auditor,
AuditorRecord, AuditorRecord,
CheckPayResult,
CoinPaySig, CoinPaySig,
CoinRecord, CoinRecord,
CoinStatus, CoinStatus,
ConfirmPayResult,
Contract, Contract,
CreateReserveResponse, CreateReserveResponse,
CurrencyRecord, CurrencyRecord,
@ -63,6 +65,7 @@ import {
ExchangeRecord, ExchangeRecord,
ExchangeWireFeesRecord, ExchangeWireFeesRecord,
Notifier, Notifier,
OfferRecord,
PayCoinInfo, PayCoinInfo,
PaybackConfirmation, PaybackConfirmation,
PreCoinRecord, PreCoinRecord,
@ -271,48 +274,6 @@ export class ConfirmReserveRequest {
} }
/**
* Offer record, stored in the wallet's database.
*/
@Checkable.Class()
export class OfferRecord {
/**
* The contract that was offered by the merchant.
*/
@Checkable.Value(Contract)
contract: Contract;
/**
* Signature by the merchant over the contract details.
*/
@Checkable.String
merchant_sig: string;
/**
* Hash of the contract terms.
*/
@Checkable.String
H_contract: string;
/**
* Time when the offer was made.
*/
@Checkable.Number
offer_time: number;
/**
* Serial ID when the offer is stored in the wallet DB.
*/
@Checkable.Optional(Checkable.Number)
id?: number;
/**
* Verify that a value matches the schema of this class and convert it into a
* member.
*/
static checked: (obj: any) => OfferRecord;
}
/** /**
* Activity history record. * Activity history record.
*/ */
@ -981,14 +942,14 @@ export class Wallet {
* 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.
*/ */
async confirmPay(offer: OfferRecord): Promise<any> { async confirmPay(offer: OfferRecord): Promise<ConfirmPayResult> {
console.log("executing confirmPay"); console.log("executing confirmPay");
const transaction = await this.q().get(Stores.transactions, offer.H_contract); const transaction = await this.q().get(Stores.transactions, offer.H_contract);
if (transaction) { if (transaction) {
// Already payed ... // Already payed ...
return {}; return "paid";
} }
const res = await this.getCoinsForPayment({ const res = await this.getCoinsForPayment({
@ -1007,29 +968,25 @@ export class Wallet {
if (!res) { if (!res) {
console.log("not confirming payment, insufficient coins"); console.log("not confirming payment, insufficient coins");
return { return "insufficient-balance";
error: "coins-insufficient",
};
} }
const {exchangeUrl, cds} = res; const {exchangeUrl, cds} = res;
const ds = await this.cryptoApi.signDeposit(offer, cds); const ds = await this.cryptoApi.signDeposit(offer, cds);
await this.recordConfirmPay(offer, await this.recordConfirmPay(offer, ds, exchangeUrl);
ds, return "paid";
exchangeUrl);
return {};
} }
/** /**
* Add a contract to the wallet and sign coins, * Check if payment for an offer is possible, or if the offer has already
* but do not send them yet. * been payed for.
*/ */
async checkPay(offer: OfferRecord): Promise<any> { async checkPay(offer: OfferRecord): Promise<CheckPayResult> {
// First check if we already payed for it. // First check if we already payed for it.
const transaction = await this.q().get(Stores.transactions, offer.H_contract); const transaction = await this.q().get(Stores.transactions, offer.H_contract);
if (transaction) { if (transaction) {
return {isPayed: true}; return "insufficient-balance";
} }
// If not already payed, check if we could pay for it. // If not already payed, check if we could pay for it.
@ -1046,11 +1003,9 @@ export class Wallet {
if (!res) { if (!res) {
console.log("not confirming payment, insufficient coins"); console.log("not confirming payment, insufficient coins");
return { return "insufficient-balance";
error: "coins-insufficient",
};
} }
return {isPayed: false}; return "payment-possible";
} }

View File

@ -24,11 +24,15 @@
* Imports. * Imports.
*/ */
import * as i18n from "../../i18n"; import * as i18n from "../../i18n";
import { Contract, AmountJson, ExchangeRecord } from "../../types"; import {
import { OfferRecord } from "../../wallet"; AmountJson,
Contract,
ExchangeRecord,
OfferRecord,
} from "../../types";
import { renderContract } from "../renderHtml"; import { renderContract } from "../renderHtml";
import { getExchanges } from "../wxApi"; import * as wxApi from "../wxApi";
import * as React from "react"; import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
@ -125,39 +129,21 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
} }
async update() { async update() {
let offer = await this.getOffer(); let offer = await wxApi.getOffer(this.props.offerId);
this.setState({offer} as any); this.setState({offer} as any);
this.checkPayment(); this.checkPayment();
let exchanges = await getExchanges(); let exchanges = await wxApi.getExchanges();
this.setState({exchanges} as any); this.setState({exchanges} as any);
} }
getOffer(): Promise<OfferRecord> { async checkPayment() {
return new Promise<OfferRecord>((resolve, reject) => { let offer = this.state.offer;
let msg = { if (!offer) {
type: 'get-offer', return;
detail: {
offerId: this.props.offerId
}
};
chrome.runtime.sendMessage(msg, (resp) => {
resolve(resp);
});
})
} }
const payStatus = await wxApi.checkPay(offer);
checkPayment() { if (payStatus === "insufficient-balance") {
let msg = {
type: 'check-pay',
detail: {
offer: this.state.offer
}
};
chrome.runtime.sendMessage(msg, (resp) => {
if (resp.error) {
console.log("check-pay error", JSON.stringify(resp));
switch (resp.error) {
case "coins-insufficient":
let msgInsufficient = i18n.str`You have insufficient funds of the requested currency in your wallet.`; let msgInsufficient = i18n.str`You have insufficient funds of the requested currency in your wallet.`;
let msgNoMatch = i18n.str`You do not have any funds from an exchange that is accepted by this merchant. None of the exchanges accepted by the merchant is known to your wallet.`; let msgNoMatch = i18n.str`You do not have any funds from an exchange that is accepted by this merchant. None of the exchanges accepted by the merchant is known to your wallet.`;
if (this.state.exchanges && this.state.offer) { if (this.state.exchanges && this.state.offer) {
@ -171,39 +157,28 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
} else { } else {
this.setState({error: msgInsufficient}); this.setState({error: msgInsufficient});
} }
break;
default:
this.setState({error: `Error: ${resp.error}`});
break;
}
this.setState({payDisabled: true}); this.setState({payDisabled: true});
} else { } else {
this.setState({payDisabled: false, error: null}); this.setState({payDisabled: false, error: null});
} }
this.setState({} as any);
window.setTimeout(() => this.checkPayment(), 500); window.setTimeout(() => this.checkPayment(), 500);
});
} }
doPayment() { async doPayment() {
let d = {offer: this.state.offer}; let offer = this.state.offer;
chrome.runtime.sendMessage({type: 'confirm-pay', detail: d}, (resp) => { if (!offer) {
if (resp.error) {
console.log("confirm-pay error", JSON.stringify(resp));
switch (resp.error) {
case "coins-insufficient":
this.setState({error: "You do not have enough coins of the requested currency."});
break;
default:
this.setState({error: `Error: ${resp.error}`});
break;
}
return; return;
} }
let c = d.offer!.contract; const payStatus = await wxApi.confirmPay(offer);
console.log("contract", c); switch (payStatus) {
document.location.href = c.fulfillment_url; case "insufficient-balance":
}); this.checkPayment();
return;
case "paid":
console.log("contract", offer.contract);
document.location.href = offer.contract.fulfillment_url;
break;
}
} }

View File

@ -23,15 +23,23 @@
*/ */
import {amountToPretty, canonicalizeBaseUrl} from "../../helpers"; import {amountToPretty, canonicalizeBaseUrl} from "../../helpers";
import {
AmountJson, CreateReserveResponse,
ReserveCreationInfo, Amounts,
Denomination, DenominationRecord, CurrencyRecord
} from "../../types";
import * as i18n from "../../i18n"; import * as i18n from "../../i18n";
import {
AmountJson,
Amounts,
CreateReserveResponse,
CurrencyRecord,
Denomination,
DenominationRecord,
ReserveCreationInfo,
} from "../../types";
import {getReserveCreationInfo, getCurrency, getExchangeInfo} from "../wxApi";
import {ImplicitStateComponent, StateHolder} from "../components"; import {ImplicitStateComponent, StateHolder} from "../components";
import {
getCurrency,
getExchangeInfo,
getReserveCreationInfo,
} from "../wxApi";
import * as React from "react"; import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
@ -46,8 +54,8 @@ function delay<T>(delayMs: number, value: T): Promise<T> {
} }
class EventTrigger { class EventTrigger {
triggerResolve: any; private triggerResolve: any;
triggerPromise: Promise<boolean>; private triggerPromise: Promise<boolean>;
constructor() { constructor() {
this.reset(); this.reset();
@ -86,11 +94,11 @@ class Collapsible extends React.Component<CollapsibleProps, CollapsibleState> {
} }
render() { render() {
const doOpen = (e: any) => { const doOpen = (e: any) => {
this.setState({collapsed: false}) this.setState({collapsed: false});
e.preventDefault() e.preventDefault();
}; };
const doClose = (e: any) => { const doClose = (e: any) => {
this.setState({collapsed: true}) this.setState({collapsed: true});
e.preventDefault(); e.preventDefault();
}; };
if (this.state.collapsed) { if (this.state.collapsed) {
@ -113,7 +121,7 @@ function renderAuditorDetails(rci: ReserveCreationInfo|null) {
</p> </p>
); );
} }
if (rci.exchangeInfo.auditors.length == 0) { if (rci.exchangeInfo.auditors.length === 0) {
return ( return (
<p> <p>
The exchange is not audited by any auditors. The exchange is not audited by any auditors.
@ -122,7 +130,7 @@ function renderAuditorDetails(rci: ReserveCreationInfo|null) {
} }
return ( return (
<div> <div>
{rci.exchangeInfo.auditors.map(a => ( {rci.exchangeInfo.auditors.map((a) => (
<h3>Auditor {a.url}</h3> <h3>Auditor {a.url}</h3>
))} ))}
</div> </div>
@ -138,14 +146,14 @@ function renderReserveCreationDetails(rci: ReserveCreationInfo|null) {
); );
} }
let denoms = rci.selectedDenoms; const denoms = rci.selectedDenoms;
let countByPub: {[s: string]: number} = {}; const countByPub: {[s: string]: number} = {};
let uniq: DenominationRecord[] = []; const uniq: DenominationRecord[] = [];
denoms.forEach((x: DenominationRecord) => { denoms.forEach((x: DenominationRecord) => {
let c = countByPub[x.denomPub] || 0; let c = countByPub[x.denomPub] || 0;
if (c == 0) { if (c === 0) {
uniq.push(x); uniq.push(x);
} }
c += 1; c += 1;
@ -177,19 +185,19 @@ function renderReserveCreationDetails(rci: ReserveCreationInfo|null) {
</tr> </tr>
</thead>, </thead>,
<tbody> <tbody>
{rci!.wireFees.feesForType[s].map(f => ( {rci!.wireFees.feesForType[s].map((f) => (
<tr> <tr>
<td>{moment.unix(f.endStamp).format("llll")}</td> <td>{moment.unix(f.endStamp).format("llll")}</td>
<td>{amountToPretty(f.wireFee)}</td> <td>{amountToPretty(f.wireFee)}</td>
<td>{amountToPretty(f.closingFee)}</td> <td>{amountToPretty(f.closingFee)}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>,
]; ];
} }
let withdrawFeeStr = amountToPretty(rci.withdrawFee); const withdrawFeeStr = amountToPretty(rci.withdrawFee);
let overheadStr = amountToPretty(rci.overhead); const overheadStr = amountToPretty(rci.overhead);
return ( return (
<div> <div>
@ -221,28 +229,10 @@ function renderReserveCreationDetails(rci: ReserveCreationInfo|null) {
} }
function getSuggestedExchange(currency: string): Promise<string> {
// TODO: make this request go to the wallet backend
// Right now, this is a stub.
const defaultExchange: {[s: string]: string} = {
"KUDOS": "https://exchange.demo.taler.net",
"PUDOS": "https://exchange.test.taler.net",
};
let exchange = defaultExchange[currency];
if (!exchange) {
exchange = ""
}
return Promise.resolve(exchange);
}
function WithdrawFee(props: {reserveCreationInfo: ReserveCreationInfo|null}): JSX.Element { function WithdrawFee(props: {reserveCreationInfo: ReserveCreationInfo|null}): JSX.Element {
if (props.reserveCreationInfo) { if (props.reserveCreationInfo) {
let {overhead, withdrawFee} = props.reserveCreationInfo; const {overhead, withdrawFee} = props.reserveCreationInfo;
let totalCost = Amounts.add(overhead, withdrawFee).amount; const totalCost = Amounts.add(overhead, withdrawFee).amount;
return <p>{i18n.str`Withdraw fees:`} {amountToPretty(totalCost)}</p>; return <p>{i18n.str`Withdraw fees:`} {amountToPretty(totalCost)}</p>;
} }
return <p />; return <p />;
@ -263,10 +253,10 @@ interface ManualSelectionProps {
} }
class ManualSelection extends ImplicitStateComponent<ManualSelectionProps> { class ManualSelection extends ImplicitStateComponent<ManualSelectionProps> {
url: StateHolder<string> = this.makeState(""); private url: StateHolder<string> = this.makeState("");
errorMessage: StateHolder<string|null> = this.makeState(null); private errorMessage: StateHolder<string|null> = this.makeState(null);
isOkay: StateHolder<boolean> = this.makeState(false); private isOkay: StateHolder<boolean> = this.makeState(false);
updateEvent = new EventTrigger(); private updateEvent = new EventTrigger();
constructor(p: ManualSelectionProps) { constructor(p: ManualSelectionProps) {
super(p); super(p);
this.url(p.initialUrl); this.url(p.initialUrl);
@ -300,23 +290,23 @@ class ManualSelection extends ImplicitStateComponent<ManualSelectionProps> {
if (!this.url()) { if (!this.url()) {
return; return;
} }
let parsedUrl = new URI(this.url()!); const parsedUrl = new URI(this.url()!);
if (parsedUrl.is("relative")) { if (parsedUrl.is("relative")) {
this.errorMessage(i18n.str`Error: URL may not be relative`); this.errorMessage(i18n.str`Error: URL may not be relative`);
this.isOkay(false); this.isOkay(false);
return; return;
} }
try { try {
let url = canonicalizeBaseUrl(this.url()!); const url = canonicalizeBaseUrl(this.url()!);
let r = await getExchangeInfo(url) const r = await getExchangeInfo(url);
console.log("getExchangeInfo returned") console.log("getExchangeInfo returned");
this.isOkay(true); this.isOkay(true);
} catch (e) { } catch (e) {
console.log("got error", e); console.log("got error", e);
if (e.hasOwnProperty("httpStatus")) { if (e.hasOwnProperty("httpStatus")) {
this.errorMessage(`Error: request failed with status ${e.httpStatus}`); this.errorMessage(`Error: request failed with status ${e.httpStatus}`);
} else if (e.hasOwnProperty("errorResponse")) { } else if (e.hasOwnProperty("errorResponse")) {
let resp = e.errorResponse; const resp = e.errorResponse;
this.errorMessage(`Error: ${resp.error} (${resp.hint})`); this.errorMessage(`Error: ${resp.error} (${resp.hint})`);
} else { } else {
this.errorMessage("invalid exchange URL"); this.errorMessage("invalid exchange URL");
@ -329,7 +319,7 @@ class ManualSelection extends ImplicitStateComponent<ManualSelectionProps> {
this.errorMessage(null); this.errorMessage(null);
this.isOkay(false); this.isOkay(false);
this.updateEvent.trigger(); this.updateEvent.trigger();
let waited = await this.updateEvent.wait(200); const waited = await this.updateEvent.wait(200);
if (waited) { if (waited) {
// Run the actual update if nobody else preempted us. // Run the actual update if nobody else preempted us.
this.update(); this.update();
@ -339,24 +329,24 @@ class ManualSelection extends ImplicitStateComponent<ManualSelectionProps> {
class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
statusString: StateHolder<string|null> = this.makeState(null); private statusString: StateHolder<string|null> = this.makeState(null);
reserveCreationInfo: StateHolder<ReserveCreationInfo|null> = this.makeState( private reserveCreationInfo: StateHolder<ReserveCreationInfo|null> = this.makeState(
null); null);
url: StateHolder<string|null> = this.makeState(null); private url: StateHolder<string|null> = this.makeState(null);
selectingExchange: StateHolder<boolean> = this.makeState(false); private selectingExchange: StateHolder<boolean> = this.makeState(false);
constructor(props: ExchangeSelectionProps) { constructor(props: ExchangeSelectionProps) {
super(props); super(props);
let prefilledExchangesUrls = []; const prefilledExchangesUrls = [];
if (props.currencyRecord) { if (props.currencyRecord) {
let exchanges = props.currencyRecord.exchanges.map((x) => x.baseUrl); const exchanges = props.currencyRecord.exchanges.map((x) => x.baseUrl);
prefilledExchangesUrls.push(...exchanges); prefilledExchangesUrls.push(...exchanges);
} }
if (props.suggestedExchangeUrl) { if (props.suggestedExchangeUrl) {
prefilledExchangesUrls.push(props.suggestedExchangeUrl); prefilledExchangesUrls.push(props.suggestedExchangeUrl);
} }
if (prefilledExchangesUrls.length != 0) { if (prefilledExchangesUrls.length !== 0) {
this.url(prefilledExchangesUrls[0]); this.url(prefilledExchangesUrls[0]);
this.forceReserveUpdate(); this.forceReserveUpdate();
} else { } else {
@ -365,9 +355,9 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
} }
renderFeeStatus() { renderFeeStatus() {
let rci = this.reserveCreationInfo(); const rci = this.reserveCreationInfo();
if (rci) { if (rci) {
let totalCost = Amounts.add(rci.overhead, rci.withdrawFee).amount; const totalCost = Amounts.add(rci.overhead, rci.withdrawFee).amount;
let trustMessage; let trustMessage;
if (rci.isTrusted) { if (rci.isTrusted) {
trustMessage = ( trustMessage = (
@ -404,7 +394,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
); );
} }
if (this.url() && !this.statusString()) { if (this.url() && !this.statusString()) {
let shortName = new URI(this.url()!).host(); const shortName = new URI(this.url()!).host();
return ( return (
<i18n.Translate wrap="p"> <i18n.Translate wrap="p">
Waiting for a response from Waiting for a response from
@ -432,7 +422,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
<div> <div>
{this.renderFeeStatus()} {this.renderFeeStatus()}
<button className="pure-button button-success" <button className="pure-button button-success"
disabled={this.reserveCreationInfo() == null} disabled={this.reserveCreationInfo() === null}
onClick={() => this.confirmReserve()}> onClick={() => this.confirmReserve()}>
{i18n.str`Accept fees and withdraw`} {i18n.str`Accept fees and withdraw`}
</button> </button>
@ -460,7 +450,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
} }
renderSelect() { renderSelect() {
let exchanges = (this.props.currencyRecord && this.props.currencyRecord.exchanges) || []; const exchanges = (this.props.currencyRecord && this.props.currencyRecord.exchanges) || [];
console.log(exchanges); console.log(exchanges);
return ( return (
<div> <div>
@ -478,7 +468,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
{exchanges.length > 0 && ( {exchanges.length > 0 && (
<div> <div>
<h2>Known Exchanges</h2> <h2>Known Exchanges</h2>
{exchanges.map(e => ( {exchanges.map((e) => (
<button className="pure-button button-success" onClick={() => this.select(e.baseUrl)}> <button className="pure-button button-success" onClick={() => this.select(e.baseUrl)}>
Select <strong>{e.baseUrl}</strong> Select <strong>{e.baseUrl}</strong>
</button> </button>
@ -519,8 +509,8 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
async forceReserveUpdate() { async forceReserveUpdate() {
this.reserveCreationInfo(null); this.reserveCreationInfo(null);
try { try {
let url = canonicalizeBaseUrl(this.url()!); const url = canonicalizeBaseUrl(this.url()!);
let r = await getReserveCreationInfo(url, const r = await getReserveCreationInfo(url,
this.props.amount); this.props.amount);
console.log("get exchange info resolved"); console.log("get exchange info resolved");
this.reserveCreationInfo(r); this.reserveCreationInfo(r);
@ -530,7 +520,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
if (e.hasOwnProperty("httpStatus")) { if (e.hasOwnProperty("httpStatus")) {
this.statusString(`Error: request failed with status ${e.httpStatus}`); this.statusString(`Error: request failed with status ${e.httpStatus}`);
} else if (e.hasOwnProperty("errorResponse")) { } else if (e.hasOwnProperty("errorResponse")) {
let resp = e.errorResponse; const resp = e.errorResponse;
this.statusString(`Error: ${resp.error} (${resp.hint})`); this.statusString(`Error: ${resp.error} (${resp.hint})`);
} }
} }
@ -546,13 +536,13 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
throw Error("empty response"); throw Error("empty response");
} }
// FIXME: filter out types that bank/exchange don't have in common // FIXME: filter out types that bank/exchange don't have in common
let wireDetails = rci.wireInfo; const wireDetails = rci.wireInfo;
let filteredWireDetails: any = {}; const filteredWireDetails: any = {};
for (let wireType in wireDetails) { for (const wireType in wireDetails) {
if (this.props.wt_types.findIndex((x) => x.toLowerCase() == wireType.toLowerCase()) < 0) { if (this.props.wt_types.findIndex((x) => x.toLowerCase() === wireType.toLowerCase()) < 0) {
continue; continue;
} }
let obj = Object.assign({}, wireDetails[wireType]); const obj = Object.assign({}, wireDetails[wireType]);
// The bank doesn't need to know about fees // The bank doesn't need to know about fees
delete obj.fees; delete obj.fees;
// Consequently the bank can't verify signatures anyway, so // Consequently the bank can't verify signatures anyway, so
@ -563,15 +553,15 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
} }
if (!rawResp.error) { if (!rawResp.error) {
const resp = CreateReserveResponse.checked(rawResp); const resp = CreateReserveResponse.checked(rawResp);
let q: {[name: string]: string|number} = { const q: {[name: string]: string|number} = {
wire_details: JSON.stringify(filteredWireDetails), amount_currency: amount.currency,
amount_fraction: amount.fraction,
amount_value: amount.value,
exchange: resp.exchange, exchange: resp.exchange,
reserve_pub: resp.reservePub, reserve_pub: resp.reservePub,
amount_value: amount.value, wire_details: JSON.stringify(filteredWireDetails),
amount_fraction: amount.fraction,
amount_currency: amount.currency,
}; };
let url = new URI(callback_url).addQuery(q); const url = new URI(callback_url).addQuery(q);
if (!url.is("absolute")) { if (!url.is("absolute")) {
throw Error("callback url is not absolute"); throw Error("callback url is not absolute");
} }
@ -582,7 +572,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
i18n.str`Oops, something went wrong. The wallet responded with error status (${rawResp.error}).`); i18n.str`Oops, something went wrong. The wallet responded with error status (${rawResp.error}).`);
} }
}; };
chrome.runtime.sendMessage({type: 'create-reserve', detail: d}, cb); chrome.runtime.sendMessage({type: "create-reserve", detail: d}, cb);
} }
renderStatus(): any { renderStatus(): any {
@ -595,7 +585,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
} }
} }
export async function main() { async function main() {
try { try {
const url = new URI(document.location.href); const url = new URI(document.location.href);
const query: any = URI.parseQuery(url.query()); const query: any = URI.parseQuery(url.query());
@ -614,15 +604,15 @@ export async function main() {
throw Error(i18n.str`Can't parse wire_types: ${e.message}`); throw Error(i18n.str`Can't parse wire_types: ${e.message}`);
} }
let suggestedExchangeUrl = query.suggested_exchange_url; const suggestedExchangeUrl = query.suggested_exchange_url;
let currencyRecord = await getCurrency(amount.currency); const currencyRecord = await getCurrency(amount.currency);
let args = { const args = {
wt_types,
suggestedExchangeUrl,
callback_url,
amount, amount,
callback_url,
currencyRecord, currencyRecord,
suggestedExchangeUrl,
wt_types,
}; };
ReactDOM.render(<ExchangeSelection {...args} />, document.getElementById( ReactDOM.render(<ExchangeSelection {...args} />, document.getElementById(

View File

@ -28,8 +28,6 @@ import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
import URI = require("urijs"); import URI = require("urijs");
"use strict";
interface ErrorProps { interface ErrorProps {
message: string; message: string;
} }
@ -44,7 +42,7 @@ class ErrorView extends React.Component<ErrorProps, void> {
} }
} }
export async function main() { async function main() {
try { try {
const url = new URI(document.location.href); const url = new URI(document.location.href);
const query: any = URI.parseQuery(url.query()); const query: any = URI.parseQuery(url.query());

View File

@ -20,7 +20,10 @@
* @author Florian Dold * @author Florian Dold
*/ */
import {LogEntry, getLogs} from "../../logging"; import {
LogEntry,
getLogs,
} from "../../logging";
import * as React from "react"; import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
@ -31,7 +34,7 @@ interface LogViewProps {
class LogView extends React.Component<LogViewProps, void> { class LogView extends React.Component<LogViewProps, void> {
render(): JSX.Element { render(): JSX.Element {
let e = this.props.log; const e = this.props.log;
return ( return (
<div className="tree-item"> <div className="tree-item">
<ul> <ul>
@ -60,19 +63,19 @@ class Logs extends React.Component<any, LogsState> {
} }
async update() { async update() {
let logs = await getLogs(); const logs = await getLogs();
this.setState({logs}); this.setState({logs});
} }
render(): JSX.Element { render(): JSX.Element {
let logs = this.state.logs; const logs = this.state.logs;
if (!logs) { if (!logs) {
return <span>...</span>; return <span>...</span>;
} }
return ( return (
<div className="tree-item"> <div className="tree-item">
Logs: Logs:
{logs.map(e => <LogView log={e} />)} {logs.map((e) => <LogView log={e} />)}
</div> </div>
); );
} }

View File

@ -21,25 +21,28 @@
*/ */
/**
* Imports.
*/
import { amountToPretty, getTalerStampDate } from "../../helpers"; import { amountToPretty, getTalerStampDate } from "../../helpers";
import { import {
ExchangeRecord,
ExchangeForCurrencyRecord,
DenominationRecord,
AuditorRecord, AuditorRecord,
CurrencyRecord,
ReserveRecord,
CoinRecord, CoinRecord,
PreCoinRecord, CurrencyRecord,
Denomination, Denomination,
DenominationRecord,
ExchangeForCurrencyRecord,
ExchangeRecord,
PreCoinRecord,
ReserveRecord,
WalletBalance, WalletBalance,
} from "../../types"; } from "../../types";
import { ImplicitStateComponent, StateHolder } from "../components"; import { ImplicitStateComponent, StateHolder } from "../components";
import { import {
getCurrencies, getCurrencies,
updateCurrency,
getPaybackReserves, getPaybackReserves,
updateCurrency,
withdrawPaybackReserve, withdrawPaybackReserve,
} from "../wxApi"; } from "../wxApi";
@ -47,10 +50,10 @@ import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
class Payback extends ImplicitStateComponent<any> { class Payback extends ImplicitStateComponent<any> {
reserves: StateHolder<ReserveRecord[]|null> = this.makeState(null); private reserves: StateHolder<ReserveRecord[]|null> = this.makeState(null);
constructor() { constructor() {
super(); super();
let port = chrome.runtime.connect(); const port = chrome.runtime.connect();
port.onMessage.addListener((msg: any) => { port.onMessage.addListener((msg: any) => {
if (msg.notify) { if (msg.notify) {
console.log("got notified"); console.log("got notified");
@ -61,7 +64,7 @@ class Payback extends ImplicitStateComponent<any> {
} }
async update() { async update() {
let reserves = await getPaybackReserves(); const reserves = await getPaybackReserves();
this.reserves(reserves); this.reserves(reserves);
} }
@ -70,16 +73,16 @@ class Payback extends ImplicitStateComponent<any> {
} }
render(): JSX.Element { render(): JSX.Element {
let reserves = this.reserves(); const reserves = this.reserves();
if (!reserves) { if (!reserves) {
return <span>loading ...</span>; return <span>loading ...</span>;
} }
if (reserves.length == 0) { if (reserves.length === 0) {
return <span>No reserves with payback available.</span>; return <span>No reserves with payback available.</span>;
} }
return ( return (
<div> <div>
{reserves.map(r => ( {reserves.map((r) => (
<div> <div>
<h2>Reserve for ${amountToPretty(r.current_amount!)}</h2> <h2>Reserve for ${amountToPretty(r.current_amount!)}</h2>
<ul> <ul>
@ -93,7 +96,7 @@ class Payback extends ImplicitStateComponent<any> {
} }
} }
export function main() { function main() {
ReactDOM.render(<Payback />, document.getElementById("container")!); ReactDOM.render(<Payback />, document.getElementById("container")!);
} }

View File

@ -24,10 +24,13 @@
*/ */
import { import {
AmountJson, AmountJson,
CheckPayResult,
ConfirmPayResult,
CoinRecord, CoinRecord,
CurrencyRecord, CurrencyRecord,
DenominationRecord, DenominationRecord,
ExchangeRecord, ExchangeRecord,
OfferRecord,
PreCoinRecord, PreCoinRecord,
ReserveCreationInfo, ReserveCreationInfo,
ReserveRecord, ReserveRecord,
@ -172,3 +175,26 @@ export async function refresh(coinPub: string): Promise<void> {
export async function payback(coinPub: string): Promise<void> { export async function payback(coinPub: string): Promise<void> {
return await callBackend("payback-coin", { coinPub }); return await callBackend("payback-coin", { coinPub });
} }
/**
* Get an offer stored in the wallet by its offer id.
* Note that the numeric offer id is not to be confused with
* the string order_id from the contract terms.
*/
export async function getOffer(offerId: number) {
return await callBackend("get-offer", { offerId });
}
/**
* Check if payment is possible or already done.
*/
export async function checkPay(offer: OfferRecord): Promise<CheckPayResult> {
return await callBackend("check-pay", { offer });
}
/**
* Pay for an offer.
*/
export async function confirmPay(offer: OfferRecord): Promise<ConfirmPayResult> {
return await callBackend("confirm-pay", { offer });
}

View File

@ -35,12 +35,12 @@ import {
AmountJson, AmountJson,
Contract, Contract,
Notifier, Notifier,
OfferRecord,
} from "../types"; } from "../types";
import { import {
Badge, Badge,
ConfirmReserveRequest, ConfirmReserveRequest,
CreateReserveRequest, CreateReserveRequest,
OfferRecord,
Stores, Stores,
Wallet, Wallet,
} from "../wallet"; } from "../wallet";