diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/crypto/cryptoApi.ts | 2 | ||||
-rw-r--r-- | src/crypto/cryptoWorker.ts | 2 | ||||
-rw-r--r-- | src/i18n.tsx | 46 | ||||
-rw-r--r-- | src/types.ts | 52 | ||||
-rw-r--r-- | src/wallet.ts | 73 | ||||
-rw-r--r-- | src/webex/pages/confirm-contract.tsx | 117 | ||||
-rw-r--r-- | src/webex/pages/confirm-create-reserve.tsx | 160 | ||||
-rw-r--r-- | src/webex/pages/error.tsx | 4 | ||||
-rw-r--r-- | src/webex/pages/logs.tsx | 13 | ||||
-rw-r--r-- | src/webex/pages/payback.tsx | 33 | ||||
-rw-r--r-- | src/webex/wxApi.ts | 26 | ||||
-rw-r--r-- | src/webex/wxBackend.ts | 2 |
12 files changed, 266 insertions, 264 deletions
diff --git a/src/crypto/cryptoApi.ts b/src/crypto/cryptoApi.ts index 3c5eb84d9..b5d7d4fb9 100644 --- a/src/crypto/cryptoApi.ts +++ b/src/crypto/cryptoApi.ts @@ -27,6 +27,7 @@ import { AmountJson, CoinRecord, DenominationRecord, + OfferRecord, PayCoinInfo, PaybackRequest, PreCoinRecord, @@ -37,7 +38,6 @@ import { import { CoinWithDenom, - OfferRecord, } from "../wallet"; import * as timer from "../timer"; diff --git a/src/crypto/cryptoWorker.ts b/src/crypto/cryptoWorker.ts index 1a337446d..85a0425b3 100644 --- a/src/crypto/cryptoWorker.ts +++ b/src/crypto/cryptoWorker.ts @@ -29,6 +29,7 @@ import { CoinRecord, CoinStatus, DenominationRecord, + OfferRecord, PayCoinInfo, PaybackRequest, PreCoinRecord, @@ -39,7 +40,6 @@ import { } from "../types"; import { CoinWithDenom, - OfferRecord, } from "../wallet"; import { diff --git a/src/i18n.tsx b/src/i18n.tsx index e5b9c398a..8c3d54192 100644 --- a/src/i18n.tsx +++ b/src/i18n.tsx @@ -40,7 +40,7 @@ if (!strings[lang]) { 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. */ export function str(strings: TemplateStringsArray, ...values: any[]) { - let str = toI18nString(strings); - let tr = jed.translate(str).ifPlural(1, str).fetch(...values); + const str = toI18nString(strings); + const tr = jed.translate(str).ifPlural(1, str).fetch(...values); return tr; } @@ -75,7 +75,7 @@ interface TranslateSwitchProps { function stringifyChildren(children: any): string { let n = 1; - let ss = React.Children.map(children, (c) => { + const ss = React.Children.map(children, (c) => { if (typeof c === "string") { return c; } @@ -113,23 +113,23 @@ interface TranslateProps { */ export class Translate extends React.Component<TranslateProps,void> { render(): JSX.Element { - let s = stringifyChildren(this.props.children); - let 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 s = stringifyChildren(this.props.children); + const tr = jed.ngettext(s, s, 1).split(/%(\d+)\$s/).filter((e: any, i: number) => i % 2 == 0); + const childArray = React.Children.toArray(this.props.children!); for (let i = 0; i < childArray.length - 1; ++i) { if ((typeof childArray[i]) == "string" && (typeof childArray[i+1]) == "string") { childArray[i+1] = (childArray[i] as string).concat(childArray[i+1] as string); childArray.splice(i,1); } } - let result = []; + const result = []; while (childArray.length > 0) { - let x = childArray.shift(); + const x = childArray.shift(); if (x === undefined) { continue; } if (typeof x === "string") { - let t = tr.shift(); + const t = tr.shift(); result.push(t); } else { result.push(x); @@ -159,7 +159,7 @@ export class TranslateSwitch extends React.Component<TranslateSwitchProps,void>{ render(): JSX.Element { let singular: React.ReactElement<TranslationPluralProps> | undefined; let plural: React.ReactElement<TranslationPluralProps> | undefined; - let children = this.props.children; + const children = this.props.children; if (children) { React.Children.forEach(children, (child: any) => { if (child.type == TranslatePlural) { @@ -192,23 +192,23 @@ interface TranslationPluralProps { */ export class TranslatePlural extends React.Component<TranslationPluralProps,void> { render(): JSX.Element { - let s = stringifyChildren(this.props.children); - let 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 s = stringifyChildren(this.props.children); + const tr = jed.ngettext(s, s, 1).split(/%(\d+)\$s/).filter((e: any, i: number) => i % 2 == 0); + const childArray = React.Children.toArray(this.props.children!); for (let i = 0; i < childArray.length - 1; ++i) { if ((typeof childArray[i]) == "string" && (typeof childArray[i + 1]) == "string") { childArray[i+i] = childArray[i] as string + childArray[i + 1] as string; childArray.splice(i,1); } } - let result = []; + const result = []; while (childArray.length > 0) { - let x = childArray.shift(); + const x = childArray.shift(); if (x === undefined) { continue; } if (typeof x === "string") { - let t = tr.shift(); + const t = tr.shift(); result.push(t); } else { result.push(x); @@ -224,23 +224,23 @@ export class TranslatePlural extends React.Component<TranslationPluralProps,void */ export class TranslateSingular extends React.Component<TranslationPluralProps,void> { render(): JSX.Element { - let s = stringifyChildren(this.props.children); - let 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 s = stringifyChildren(this.props.children); + const tr = jed.ngettext(s, s, 1).split(/%(\d+)\$s/).filter((e: any, i: number) => i % 2 == 0); + const childArray = React.Children.toArray(this.props.children!); for (let i = 0; i < childArray.length - 1; ++i) { if ((typeof childArray[i]) == "string" && (typeof childArray[i + 1]) == "string") { childArray[i+i] = childArray[i] as string + childArray[i + 1] as string; childArray.splice(i,1); } } - let result = []; + const result = []; while (childArray.length > 0) { - let x = childArray.shift(); + const x = childArray.shift(); if (x === undefined) { continue; } if (typeof x === "string") { - let t = tr.shift(); + const t = tr.shift(); result.push(t); } else { result.push(x); diff --git a/src/types.ts b/src/types.ts index 0371aab77..8b5f4063b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1075,6 +1075,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 * wallet's database. */ @@ -1333,3 +1378,10 @@ export interface Notifier { export function mkAmount(value: number, fraction: number, currency: string): AmountJson { return {value, fraction, currency}; } + +/** + * Possible responses for checkPay. + */ +export type CheckPayResult = "paid" | "payment-possible" | "insufficient-balance"; + +export type ConfirmPayResult = "paid" | "insufficient-balance"; diff --git a/src/wallet.ts b/src/wallet.ts index 743042b97..5564162b9 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -50,9 +50,11 @@ import { Amounts, Auditor, AuditorRecord, + CheckPayResult, CoinPaySig, CoinRecord, CoinStatus, + ConfirmPayResult, Contract, CreateReserveResponse, CurrencyRecord, @@ -63,6 +65,7 @@ import { ExchangeRecord, ExchangeWireFeesRecord, Notifier, + OfferRecord, PayCoinInfo, PaybackConfirmation, PreCoinRecord, @@ -272,48 +275,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. */ export interface HistoryRecord { @@ -981,14 +942,14 @@ export class Wallet { * Add a contract to the wallet and sign coins, * but do not send them yet. */ - async confirmPay(offer: OfferRecord): Promise<any> { + async confirmPay(offer: OfferRecord): Promise<ConfirmPayResult> { console.log("executing confirmPay"); const transaction = await this.q().get(Stores.transactions, offer.H_contract); if (transaction) { // Already payed ... - return {}; + return "paid"; } const res = await this.getCoinsForPayment({ @@ -1007,29 +968,25 @@ export class Wallet { if (!res) { console.log("not confirming payment, insufficient coins"); - return { - error: "coins-insufficient", - }; + return "insufficient-balance"; } const {exchangeUrl, cds} = res; const ds = await this.cryptoApi.signDeposit(offer, cds); - await this.recordConfirmPay(offer, - ds, - exchangeUrl); - return {}; + await this.recordConfirmPay(offer, ds, exchangeUrl); + return "paid"; } /** - * Add a contract to the wallet and sign coins, - * but do not send them yet. + * Check if payment for an offer is possible, or if the offer has already + * been payed for. */ - async checkPay(offer: OfferRecord): Promise<any> { + async checkPay(offer: OfferRecord): Promise<CheckPayResult> { // First check if we already payed for it. const transaction = await this.q().get(Stores.transactions, offer.H_contract); if (transaction) { - return {isPayed: true}; + return "insufficient-balance"; } // If not already payed, check if we could pay for it. @@ -1046,11 +1003,9 @@ export class Wallet { if (!res) { console.log("not confirming payment, insufficient coins"); - return { - error: "coins-insufficient", - }; + return "insufficient-balance"; } - return {isPayed: false}; + return "payment-possible"; } diff --git a/src/webex/pages/confirm-contract.tsx b/src/webex/pages/confirm-contract.tsx index 011df27a1..cc3025847 100644 --- a/src/webex/pages/confirm-contract.tsx +++ b/src/webex/pages/confirm-contract.tsx @@ -24,11 +24,15 @@ * Imports. */ import * as i18n from "../../i18n"; -import { Contract, AmountJson, ExchangeRecord } from "../../types"; -import { OfferRecord } from "../../wallet"; +import { + AmountJson, + Contract, + ExchangeRecord, + OfferRecord, +} from "../../types"; import { renderContract } from "../renderHtml"; -import { getExchanges } from "../wxApi"; +import * as wxApi from "../wxApi"; import * as React from "react"; import * as ReactDOM from "react-dom"; @@ -125,85 +129,56 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt } async update() { - let offer = await this.getOffer(); + let offer = await wxApi.getOffer(this.props.offerId); this.setState({offer} as any); this.checkPayment(); - let exchanges = await getExchanges(); + let exchanges = await wxApi.getExchanges(); this.setState({exchanges} as any); } - getOffer(): Promise<OfferRecord> { - return new Promise<OfferRecord>((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.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 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) { - let acceptedExchangePubs = this.state.offer.contract.exchanges.map((e) => e.master_pub); - let ex = this.state.exchanges.find((e) => acceptedExchangePubs.indexOf(e.masterPublicKey) >= 0); - if (ex) { - this.setState({error: msgInsufficient}); - } else { - this.setState({error: msgNoMatch}); - } - } else { - this.setState({error: msgInsufficient}); - } - break; - default: - this.setState({error: `Error: ${resp.error}`}); - break; + async checkPayment() { + let offer = this.state.offer; + if (!offer) { + return; + } + const payStatus = await wxApi.checkPay(offer); + + if (payStatus === "insufficient-balance") { + 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.`; + if (this.state.exchanges && this.state.offer) { + let acceptedExchangePubs = this.state.offer.contract.exchanges.map((e) => e.master_pub); + let ex = this.state.exchanges.find((e) => acceptedExchangePubs.indexOf(e.masterPublicKey) >= 0); + if (ex) { + this.setState({error: msgInsufficient}); + } else { + this.setState({error: msgNoMatch}); } - this.setState({payDisabled: true}); } else { - this.setState({payDisabled: false, error: null}); + this.setState({error: msgInsufficient}); } - this.setState({} as any); - window.setTimeout(() => this.checkPayment(), 500); - }); + this.setState({payDisabled: true}); + } else { + this.setState({payDisabled: false, error: null}); + } + window.setTimeout(() => this.checkPayment(), 500); } - doPayment() { - 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)); - 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; - } + async doPayment() { + let offer = this.state.offer; + if (!offer) { + return; + } + const payStatus = await wxApi.confirmPay(offer); + switch (payStatus) { + case "insufficient-balance": + this.checkPayment(); return; - } - let c = d.offer!.contract; - console.log("contract", c); - document.location.href = c.fulfillment_url; - }); + case "paid": + console.log("contract", offer.contract); + document.location.href = offer.contract.fulfillment_url; + break; + } } diff --git a/src/webex/pages/confirm-create-reserve.tsx b/src/webex/pages/confirm-create-reserve.tsx index 6ece92e21..50a1045ef 100644 --- a/src/webex/pages/confirm-create-reserve.tsx +++ b/src/webex/pages/confirm-create-reserve.tsx @@ -23,15 +23,23 @@ */ import {amountToPretty, canonicalizeBaseUrl} from "../../helpers"; +import * as i18n from "../../i18n"; import { - AmountJson, CreateReserveResponse, - ReserveCreationInfo, Amounts, - Denomination, DenominationRecord, CurrencyRecord + AmountJson, + Amounts, + CreateReserveResponse, + CurrencyRecord, + Denomination, + DenominationRecord, + ReserveCreationInfo, } from "../../types"; -import * as i18n from "../../i18n"; -import {getReserveCreationInfo, getCurrency, getExchangeInfo} from "../wxApi"; import {ImplicitStateComponent, StateHolder} from "../components"; +import { + getCurrency, + getExchangeInfo, + getReserveCreationInfo, +} from "../wxApi"; import * as React from "react"; import * as ReactDOM from "react-dom"; @@ -46,8 +54,8 @@ function delay<T>(delayMs: number, value: T): Promise<T> { } class EventTrigger { - triggerResolve: any; - triggerPromise: Promise<boolean>; + private triggerResolve: any; + private triggerPromise: Promise<boolean>; constructor() { this.reset(); @@ -86,11 +94,11 @@ class Collapsible extends React.Component<CollapsibleProps, CollapsibleState> { } render() { const doOpen = (e: any) => { - this.setState({collapsed: false}) - e.preventDefault() + this.setState({collapsed: false}); + e.preventDefault(); }; const doClose = (e: any) => { - this.setState({collapsed: true}) + this.setState({collapsed: true}); e.preventDefault(); }; if (this.state.collapsed) { @@ -113,7 +121,7 @@ function renderAuditorDetails(rci: ReserveCreationInfo|null) { </p> ); } - if (rci.exchangeInfo.auditors.length == 0) { + if (rci.exchangeInfo.auditors.length === 0) { return ( <p> The exchange is not audited by any auditors. @@ -122,7 +130,7 @@ function renderAuditorDetails(rci: ReserveCreationInfo|null) { } return ( <div> - {rci.exchangeInfo.auditors.map(a => ( + {rci.exchangeInfo.auditors.map((a) => ( <h3>Auditor {a.url}</h3> ))} </div> @@ -138,14 +146,14 @@ function renderReserveCreationDetails(rci: ReserveCreationInfo|null) { ); } - let denoms = rci.selectedDenoms; + const denoms = rci.selectedDenoms; - let countByPub: {[s: string]: number} = {}; - let uniq: DenominationRecord[] = []; + const countByPub: {[s: string]: number} = {}; + const uniq: DenominationRecord[] = []; denoms.forEach((x: DenominationRecord) => { let c = countByPub[x.denomPub] || 0; - if (c == 0) { + if (c === 0) { uniq.push(x); } c += 1; @@ -177,19 +185,19 @@ function renderReserveCreationDetails(rci: ReserveCreationInfo|null) { </tr> </thead>, <tbody> - {rci!.wireFees.feesForType[s].map(f => ( + {rci!.wireFees.feesForType[s].map((f) => ( <tr> <td>{moment.unix(f.endStamp).format("llll")}</td> <td>{amountToPretty(f.wireFee)}</td> <td>{amountToPretty(f.closingFee)}</td> </tr> ))} - </tbody> + </tbody>, ]; } - let withdrawFeeStr = amountToPretty(rci.withdrawFee); - let overheadStr = amountToPretty(rci.overhead); + const withdrawFeeStr = amountToPretty(rci.withdrawFee); + const overheadStr = amountToPretty(rci.overhead); return ( <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 { if (props.reserveCreationInfo) { - let {overhead, withdrawFee} = props.reserveCreationInfo; - let totalCost = Amounts.add(overhead, withdrawFee).amount; + const {overhead, withdrawFee} = props.reserveCreationInfo; + const totalCost = Amounts.add(overhead, withdrawFee).amount; return <p>{i18n.str`Withdraw fees:`} {amountToPretty(totalCost)}</p>; } return <p />; @@ -263,10 +253,10 @@ interface ManualSelectionProps { } class ManualSelection extends ImplicitStateComponent<ManualSelectionProps> { - url: StateHolder<string> = this.makeState(""); - errorMessage: StateHolder<string|null> = this.makeState(null); - isOkay: StateHolder<boolean> = this.makeState(false); - updateEvent = new EventTrigger(); + private url: StateHolder<string> = this.makeState(""); + private errorMessage: StateHolder<string|null> = this.makeState(null); + private isOkay: StateHolder<boolean> = this.makeState(false); + private updateEvent = new EventTrigger(); constructor(p: ManualSelectionProps) { super(p); this.url(p.initialUrl); @@ -300,23 +290,23 @@ class ManualSelection extends ImplicitStateComponent<ManualSelectionProps> { if (!this.url()) { return; } - let parsedUrl = new URI(this.url()!); + const parsedUrl = new URI(this.url()!); if (parsedUrl.is("relative")) { this.errorMessage(i18n.str`Error: URL may not be relative`); this.isOkay(false); return; } try { - let url = canonicalizeBaseUrl(this.url()!); - let r = await getExchangeInfo(url) - console.log("getExchangeInfo returned") + const url = canonicalizeBaseUrl(this.url()!); + const r = await getExchangeInfo(url); + console.log("getExchangeInfo returned"); this.isOkay(true); } catch (e) { console.log("got error", e); if (e.hasOwnProperty("httpStatus")) { this.errorMessage(`Error: request failed with status ${e.httpStatus}`); } else if (e.hasOwnProperty("errorResponse")) { - let resp = e.errorResponse; + const resp = e.errorResponse; this.errorMessage(`Error: ${resp.error} (${resp.hint})`); } else { this.errorMessage("invalid exchange URL"); @@ -329,7 +319,7 @@ class ManualSelection extends ImplicitStateComponent<ManualSelectionProps> { this.errorMessage(null); this.isOkay(false); this.updateEvent.trigger(); - let waited = await this.updateEvent.wait(200); + const waited = await this.updateEvent.wait(200); if (waited) { // Run the actual update if nobody else preempted us. this.update(); @@ -339,24 +329,24 @@ class ManualSelection extends ImplicitStateComponent<ManualSelectionProps> { class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { - statusString: StateHolder<string|null> = this.makeState(null); - reserveCreationInfo: StateHolder<ReserveCreationInfo|null> = this.makeState( + private statusString: StateHolder<string|null> = this.makeState(null); + private reserveCreationInfo: StateHolder<ReserveCreationInfo|null> = this.makeState( 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) { super(props); - let prefilledExchangesUrls = []; + const prefilledExchangesUrls = []; if (props.currencyRecord) { - let exchanges = props.currencyRecord.exchanges.map((x) => x.baseUrl); + const exchanges = props.currencyRecord.exchanges.map((x) => x.baseUrl); prefilledExchangesUrls.push(...exchanges); } if (props.suggestedExchangeUrl) { prefilledExchangesUrls.push(props.suggestedExchangeUrl); } - if (prefilledExchangesUrls.length != 0) { + if (prefilledExchangesUrls.length !== 0) { this.url(prefilledExchangesUrls[0]); this.forceReserveUpdate(); } else { @@ -365,9 +355,9 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { } renderFeeStatus() { - let rci = this.reserveCreationInfo(); + const rci = this.reserveCreationInfo(); if (rci) { - let totalCost = Amounts.add(rci.overhead, rci.withdrawFee).amount; + const totalCost = Amounts.add(rci.overhead, rci.withdrawFee).amount; let trustMessage; if (rci.isTrusted) { trustMessage = ( @@ -404,7 +394,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { ); } if (this.url() && !this.statusString()) { - let shortName = new URI(this.url()!).host(); + const shortName = new URI(this.url()!).host(); return ( <i18n.Translate wrap="p"> Waiting for a response from @@ -432,7 +422,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { <div> {this.renderFeeStatus()} <button className="pure-button button-success" - disabled={this.reserveCreationInfo() == null} + disabled={this.reserveCreationInfo() === null} onClick={() => this.confirmReserve()}> {i18n.str`Accept fees and withdraw`} </button> @@ -460,7 +450,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { } renderSelect() { - let exchanges = (this.props.currencyRecord && this.props.currencyRecord.exchanges) || []; + const exchanges = (this.props.currencyRecord && this.props.currencyRecord.exchanges) || []; console.log(exchanges); return ( <div> @@ -478,7 +468,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { {exchanges.length > 0 && ( <div> <h2>Known Exchanges</h2> - {exchanges.map(e => ( + {exchanges.map((e) => ( <button className="pure-button button-success" onClick={() => this.select(e.baseUrl)}> Select <strong>{e.baseUrl}</strong> </button> @@ -519,8 +509,8 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { async forceReserveUpdate() { this.reserveCreationInfo(null); try { - let url = canonicalizeBaseUrl(this.url()!); - let r = await getReserveCreationInfo(url, + const url = canonicalizeBaseUrl(this.url()!); + const r = await getReserveCreationInfo(url, this.props.amount); console.log("get exchange info resolved"); this.reserveCreationInfo(r); @@ -530,7 +520,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { if (e.hasOwnProperty("httpStatus")) { this.statusString(`Error: request failed with status ${e.httpStatus}`); } else if (e.hasOwnProperty("errorResponse")) { - let resp = e.errorResponse; + const resp = e.errorResponse; this.statusString(`Error: ${resp.error} (${resp.hint})`); } } @@ -546,13 +536,13 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { throw Error("empty response"); } // FIXME: filter out types that bank/exchange don't have in common - let wireDetails = rci.wireInfo; - let filteredWireDetails: any = {}; - for (let wireType in wireDetails) { - if (this.props.wt_types.findIndex((x) => x.toLowerCase() == wireType.toLowerCase()) < 0) { + const wireDetails = rci.wireInfo; + const filteredWireDetails: any = {}; + for (const wireType in wireDetails) { + if (this.props.wt_types.findIndex((x) => x.toLowerCase() === wireType.toLowerCase()) < 0) { continue; } - let obj = Object.assign({}, wireDetails[wireType]); + const obj = Object.assign({}, wireDetails[wireType]); // The bank doesn't need to know about fees delete obj.fees; // Consequently the bank can't verify signatures anyway, so @@ -563,15 +553,15 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { } if (!rawResp.error) { const resp = CreateReserveResponse.checked(rawResp); - let q: {[name: string]: string|number} = { - wire_details: JSON.stringify(filteredWireDetails), + const q: {[name: string]: string|number} = { + amount_currency: amount.currency, + amount_fraction: amount.fraction, + amount_value: amount.value, exchange: resp.exchange, reserve_pub: resp.reservePub, - amount_value: amount.value, - amount_fraction: amount.fraction, - amount_currency: amount.currency, + wire_details: JSON.stringify(filteredWireDetails), }; - let url = new URI(callback_url).addQuery(q); + const url = new URI(callback_url).addQuery(q); if (!url.is("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}).`); } }; - chrome.runtime.sendMessage({type: 'create-reserve', detail: d}, cb); + chrome.runtime.sendMessage({type: "create-reserve", detail: d}, cb); } renderStatus(): any { @@ -595,7 +585,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> { } } -export async function main() { +async function main() { try { const url = new URI(document.location.href); 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}`); } - let suggestedExchangeUrl = query.suggested_exchange_url; - let currencyRecord = await getCurrency(amount.currency); + const suggestedExchangeUrl = query.suggested_exchange_url; + const currencyRecord = await getCurrency(amount.currency); - let args = { - wt_types, - suggestedExchangeUrl, - callback_url, + const args = { amount, + callback_url, currencyRecord, + suggestedExchangeUrl, + wt_types, }; ReactDOM.render(<ExchangeSelection {...args} />, document.getElementById( diff --git a/src/webex/pages/error.tsx b/src/webex/pages/error.tsx index f278bd224..829ea0c90 100644 --- a/src/webex/pages/error.tsx +++ b/src/webex/pages/error.tsx @@ -28,8 +28,6 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; import URI = require("urijs"); -"use strict"; - interface ErrorProps { message: string; } @@ -44,7 +42,7 @@ class ErrorView extends React.Component<ErrorProps, void> { } } -export async function main() { +async function main() { try { const url = new URI(document.location.href); const query: any = URI.parseQuery(url.query()); diff --git a/src/webex/pages/logs.tsx b/src/webex/pages/logs.tsx index 0c533bfa8..51f2cef33 100644 --- a/src/webex/pages/logs.tsx +++ b/src/webex/pages/logs.tsx @@ -20,7 +20,10 @@ * @author Florian Dold */ -import {LogEntry, getLogs} from "../../logging"; +import { + LogEntry, + getLogs, +} from "../../logging"; import * as React from "react"; import * as ReactDOM from "react-dom"; @@ -31,7 +34,7 @@ interface LogViewProps { class LogView extends React.Component<LogViewProps, void> { render(): JSX.Element { - let e = this.props.log; + const e = this.props.log; return ( <div className="tree-item"> <ul> @@ -60,19 +63,19 @@ class Logs extends React.Component<any, LogsState> { } async update() { - let logs = await getLogs(); + const logs = await getLogs(); this.setState({logs}); } render(): JSX.Element { - let logs = this.state.logs; + const logs = this.state.logs; if (!logs) { return <span>...</span>; } return ( <div className="tree-item"> Logs: - {logs.map(e => <LogView log={e} />)} + {logs.map((e) => <LogView log={e} />)} </div> ); } diff --git a/src/webex/pages/payback.tsx b/src/webex/pages/payback.tsx index 7bcc581d8..e10da7b05 100644 --- a/src/webex/pages/payback.tsx +++ b/src/webex/pages/payback.tsx @@ -21,25 +21,28 @@ */ +/** + * Imports. + */ import { amountToPretty, getTalerStampDate } from "../../helpers"; import { - ExchangeRecord, - ExchangeForCurrencyRecord, - DenominationRecord, AuditorRecord, - CurrencyRecord, - ReserveRecord, CoinRecord, - PreCoinRecord, + CurrencyRecord, Denomination, + DenominationRecord, + ExchangeForCurrencyRecord, + ExchangeRecord, + PreCoinRecord, + ReserveRecord, WalletBalance, } from "../../types"; import { ImplicitStateComponent, StateHolder } from "../components"; import { getCurrencies, - updateCurrency, getPaybackReserves, + updateCurrency, withdrawPaybackReserve, } from "../wxApi"; @@ -47,10 +50,10 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; class Payback extends ImplicitStateComponent<any> { - reserves: StateHolder<ReserveRecord[]|null> = this.makeState(null); + private reserves: StateHolder<ReserveRecord[]|null> = this.makeState(null); constructor() { super(); - let port = chrome.runtime.connect(); + const port = chrome.runtime.connect(); port.onMessage.addListener((msg: any) => { if (msg.notify) { console.log("got notified"); @@ -61,25 +64,25 @@ class Payback extends ImplicitStateComponent<any> { } async update() { - let reserves = await getPaybackReserves(); + const reserves = await getPaybackReserves(); this.reserves(reserves); } withdrawPayback(pub: string) { - withdrawPaybackReserve(pub); + withdrawPaybackReserve(pub); } render(): JSX.Element { - let reserves = this.reserves(); + const reserves = this.reserves(); if (!reserves) { return <span>loading ...</span>; } - if (reserves.length == 0) { + if (reserves.length === 0) { return <span>No reserves with payback available.</span>; } return ( <div> - {reserves.map(r => ( + {reserves.map((r) => ( <div> <h2>Reserve for ${amountToPretty(r.current_amount!)}</h2> <ul> @@ -93,7 +96,7 @@ class Payback extends ImplicitStateComponent<any> { } } -export function main() { +function main() { ReactDOM.render(<Payback />, document.getElementById("container")!); } diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts index e5a502406..c120f34e7 100644 --- a/src/webex/wxApi.ts +++ b/src/webex/wxApi.ts @@ -24,10 +24,13 @@ */ import { AmountJson, + CheckPayResult, + ConfirmPayResult, CoinRecord, CurrencyRecord, DenominationRecord, ExchangeRecord, + OfferRecord, PreCoinRecord, ReserveCreationInfo, ReserveRecord, @@ -172,3 +175,26 @@ export async function refresh(coinPub: string): Promise<void> { export async function payback(coinPub: string): Promise<void> { 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 }); +} diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts index 2579bc317..c7aa34a9a 100644 --- a/src/webex/wxBackend.ts +++ b/src/webex/wxBackend.ts @@ -35,12 +35,12 @@ import { AmountJson, Contract, Notifier, + OfferRecord, } from "../types"; import { Badge, ConfirmReserveRequest, CreateReserveRequest, - OfferRecord, Stores, Wallet, } from "../wallet"; |