aboutsummaryrefslogtreecommitdiff
path: root/src/webex
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2017-05-31 16:04:14 +0200
committerFlorian Dold <florian.dold@gmail.com>2017-05-31 16:04:23 +0200
commit613a14c14f969bf21ff7569f93cde3a7a35ce96a (patch)
tree5767b5d1664e503aa46bfd37074c14f44402b806 /src/webex
parent7e5ddf3a456b0b39f688dc79035841d4bf86aa00 (diff)
downloadwallet-core-613a14c14f969bf21ff7569f93cde3a7a35ce96a.tar.xz
fix messaging, small issues and safer types
Diffstat (limited to 'src/webex')
-rw-r--r--src/webex/messages.ts28
-rw-r--r--src/webex/notify.ts10
-rw-r--r--src/webex/pages/confirm-contract.html2
-rw-r--r--src/webex/pages/confirm-contract.tsx41
-rw-r--r--src/webex/wxApi.ts44
-rw-r--r--src/webex/wxBackend.ts202
6 files changed, 135 insertions, 192 deletions
diff --git a/src/webex/messages.ts b/src/webex/messages.ts
index 21acfc1d5..27ff9a5b6 100644
--- a/src/webex/messages.ts
+++ b/src/webex/messages.ts
@@ -69,11 +69,11 @@ export interface MessageMap {
response: string;
};
"confirm-pay": {
- request: { offer: types.OfferRecord; };
+ request: { proposalId: number; };
response: types.ConfirmPayResult;
};
"check-pay": {
- request: { offer: types.OfferRecord; };
+ request: { proposalId: number; };
response: types.CheckPayResult;
};
"query-payment": {
@@ -96,21 +96,29 @@ export interface MessageMap {
request: { historyEntry: types.HistoryRecord };
response: void;
};
- "safe-offer": {
- request: { offer: types.OfferRecord };
+ "save-proposal": {
+ request: { proposal: types.ProposalRecord };
response: void;
};
"reserve-creation-info": {
- request: { baseUrl: string };
+ request: { baseUrl: string, amount: types.AmountJson };
response: types.ReserveCreationInfo;
}
"get-history": {
request: { };
response: types.HistoryRecord[];
};
- "get-offer": {
- request: { offerId: number };
- response: types.OfferRecord | undefined;
+ "get-proposal": {
+ request: { proposalId: number };
+ response: types.ProposalRecord | undefined;
+ };
+ "get-coins": {
+ request: { exchangeBaseUrl: string };
+ response: any;
+ };
+ "refresh-coin": {
+ request: { coinPub: string };
+ response: any;
};
"get-currencies": {
request: { };
@@ -120,6 +128,10 @@ export interface MessageMap {
request: { currencyRecord: types.CurrencyRecord };
response: void;
};
+ "get-exchanges": {
+ request: { };
+ response: types.ExchangeRecord[];
+ };
"get-reserves": {
request: { exchangeBaseUrl: string };
response: types.ReserveRecord[];
diff --git a/src/webex/notify.ts b/src/webex/notify.ts
index 81bc6808d..2f38658bd 100644
--- a/src/webex/notify.ts
+++ b/src/webex/notify.ts
@@ -280,7 +280,8 @@ async function processProposal(proposal: any) {
const contractHash = await wxApi.hashContract(proposal.data);
if (contractHash !== proposal.hash) {
- console.error("merchant-supplied contract hash is wrong");
+ console.error(`merchant-supplied contract hash is wrong (us: ${contractHash}, merchant: ${proposal.hash})`);
+ console.dir(proposal.data);
return;
}
@@ -301,12 +302,11 @@ async function processProposal(proposal: any) {
type: "offer-contract",
};
await wxApi.putHistory(historyEntry);
- const offerId = await wxApi.saveOffer(proposal);
+ let proposalId = await wxApi.saveProposal(proposal);
- const uri = new URI(chrome.extension.getURL(
- "/src/webex/pages/confirm-contract.html"));
+ const uri = new URI(chrome.extension.getURL("/src/webex/pages/confirm-contract.html"));
const params = {
- offerId: offerId.toString(),
+ proposalId: proposalId.toString(),
};
const target = uri.query(params).href();
document.location.replace(target);
diff --git a/src/webex/pages/confirm-contract.html b/src/webex/pages/confirm-contract.html
index 6713b2e2c..e5ba68404 100644
--- a/src/webex/pages/confirm-contract.html
+++ b/src/webex/pages/confirm-contract.html
@@ -5,7 +5,7 @@
<meta charset="UTF-8">
<title>Taler Wallet: Confirm Reserve Creation</title>
- <link rel="stylesheet" type="text/css" href="/src/style/wallet.css">
+ <link rel="stylesheet" type="text/css" href="../style/wallet.css">
<link rel="icon" href="/img/icon.png">
diff --git a/src/webex/pages/confirm-contract.tsx b/src/webex/pages/confirm-contract.tsx
index 9b4c93334..c5513f7c6 100644
--- a/src/webex/pages/confirm-contract.tsx
+++ b/src/webex/pages/confirm-contract.tsx
@@ -27,7 +27,7 @@ import * as i18n from "../../i18n";
import {
Contract,
ExchangeRecord,
- OfferRecord,
+ ProposalRecord,
} from "../../types";
import { renderContract } from "../renderHtml";
@@ -98,11 +98,11 @@ class Details extends React.Component<DetailProps, DetailState> {
}
interface ContractPromptProps {
- offerId: number;
+ proposalId: number;
}
interface ContractPromptState {
- offer: OfferRecord|null;
+ proposal: ProposalRecord|null;
error: string|null;
payDisabled: boolean;
exchanges: null|ExchangeRecord[];
@@ -114,7 +114,7 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
this.state = {
error: null,
exchanges: null,
- offer: null,
+ proposal: null,
payDisabled: true,
};
}
@@ -128,26 +128,21 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
}
async update() {
- const offer = await wxApi.getOffer(this.props.offerId);
- this.setState({offer} as any);
+ const proposal = await wxApi.getProposal(this.props.proposalId);
+ this.setState({proposal} as any);
this.checkPayment();
const exchanges = await wxApi.getExchanges();
this.setState({exchanges} as any);
}
async checkPayment() {
- const offer = this.state.offer;
- if (!offer) {
- return;
- }
- const payStatus = await wxApi.checkPay(offer);
-
+ const payStatus = await wxApi.checkPay(this.props.proposalId);
if (payStatus === "insufficient-balance") {
const msgInsufficient = i18n.str`You have insufficient funds of the requested currency in your wallet.`;
// tslint:disable-next-line:max-line-length
const 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) {
- const acceptedExchangePubs = this.state.offer.contract.exchanges.map((e) => e.master_pub);
+ if (this.state.exchanges && this.state.proposal) {
+ const acceptedExchangePubs = this.state.proposal.contractTerms.exchanges.map((e) => e.master_pub);
const ex = this.state.exchanges.find((e) => acceptedExchangePubs.indexOf(e.masterPublicKey) >= 0);
if (ex) {
this.setState({error: msgInsufficient});
@@ -165,28 +160,28 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
}
async doPayment() {
- const offer = this.state.offer;
- if (!offer) {
+ const proposal = this.state.proposal;
+ if (!proposal) {
return;
}
- const payStatus = await wxApi.confirmPay(offer);
+ const payStatus = await wxApi.confirmPay(this.props.proposalId);
switch (payStatus) {
case "insufficient-balance":
this.checkPayment();
return;
case "paid":
- console.log("contract", offer.contract);
- document.location.href = offer.contract.fulfillment_url;
+ console.log("contract", proposal.contractTerms);
+ document.location.href = proposal.contractTerms.fulfillment_url;
break;
}
}
render() {
- if (!this.state.offer) {
+ if (!this.state.proposal) {
return <span>...</span>;
}
- const c = this.state.offer.contract;
+ const c = this.state.proposal.contractTerms;
return (
<div>
<div>
@@ -210,8 +205,8 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
document.addEventListener("DOMContentLoaded", () => {
const url = new URI(document.location.href);
const query: any = URI.parseQuery(url.query());
- const offerId = JSON.parse(query.offerId);
+ const proposalId = JSON.parse(query.proposalId);
- ReactDOM.render(<ContractPrompt offerId={offerId}/>, document.getElementById(
+ ReactDOM.render(<ContractPrompt proposalId={proposalId}/>, document.getElementById(
"contract")!);
});
diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts
index ff601b6f7..4babb2a79 100644
--- a/src/webex/wxApi.ts
+++ b/src/webex/wxApi.ts
@@ -30,15 +30,16 @@ import {
CurrencyRecord,
DenominationRecord,
ExchangeRecord,
- OfferRecord,
PreCoinRecord,
ReserveCreationInfo,
ReserveRecord,
} from "../types";
+import { MessageType, MessageMap } from "./messages";
-async function callBackend(type: string, detail?: any): Promise<any> {
+
+async function callBackend<T extends MessageType>(type: T, detail: MessageMap[T]["request"]): Promise<any> {
return new Promise<any>((resolve, reject) => {
chrome.runtime.sendMessage({ type, detail }, (resp) => {
if (resp && resp.error) {
@@ -65,7 +66,7 @@ export function getReserveCreationInfo(baseUrl: string,
* Get all exchanges the wallet knows about.
*/
export function getExchanges(): Promise<ExchangeRecord[]> {
- return callBackend("get-exchanges");
+ return callBackend("get-exchanges", { });
}
@@ -73,7 +74,7 @@ export function getExchanges(): Promise<ExchangeRecord[]> {
* Get all currencies the exchange knows about.
*/
export function getCurrencies(): Promise<CurrencyRecord[]> {
- return callBackend("get-currencies");
+ return callBackend("get-currencies", { });
}
@@ -114,7 +115,7 @@ export function getReserves(exchangeBaseUrl: string): Promise<ReserveRecord[]> {
* Get all reserves for which a payback is available.
*/
export function getPaybackReserves(): Promise<ReserveRecord[]> {
- return callBackend("get-payback-reserves");
+ return callBackend("get-payback-reserves", { });
}
@@ -166,41 +167,40 @@ export function payback(coinPub: string): Promise<void> {
}
/**
- * 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.
+ * Get a proposal stored in the wallet by its proposal id.
*/
-export function getOffer(offerId: number) {
- return callBackend("get-offer", { offerId });
+export function getProposal(proposalId: number) {
+ return callBackend("get-proposal", { proposalId });
}
/**
* Check if payment is possible or already done.
*/
-export function checkPay(offer: OfferRecord): Promise<CheckPayResult> {
- return callBackend("check-pay", { offer });
+export function checkPay(proposalId: number): Promise<CheckPayResult> {
+ return callBackend("check-pay", { proposalId });
}
/**
- * Pay for an offer.
+ * Pay for a proposal.
*/
-export function confirmPay(offer: OfferRecord): Promise<ConfirmPayResult> {
- return callBackend("confirm-pay", { offer });
+export function confirmPay(proposalId: number): Promise<ConfirmPayResult> {
+ return callBackend("confirm-pay", { proposalId });
}
/**
* Hash a contract. Throws if its not a valid contract.
*/
export function hashContract(contract: object): Promise<string> {
- return callBackend("confirm-pay", { contract });
+ return callBackend("hash-contract", { contract });
}
+
/**
- * Save an offer in the wallet. Returns the offer id that
- * the offer is stored under.
+ * Save a proposal in the wallet. Returns the proposal id that
+ * the proposal is stored under.
*/
-export function saveOffer(offer: object): Promise<number> {
- return callBackend("save-offer", { offer });
+export function saveProposal(proposal: any): Promise<number> {
+ return callBackend("save-proposal", proposal);
}
/**
@@ -243,7 +243,7 @@ export function paymentFailed(contractTermsHash: string): Promise<void> {
* cookie was set.
*/
export function getTabCookie(contractTermsHash: string, merchantSig: string): Promise<any> {
- return callBackend("get-tab-cookie");
+ return callBackend("get-tab-cookie", { });
}
/**
@@ -251,5 +251,5 @@ export function getTabCookie(contractTermsHash: string, merchantSig: string): Pr
* database and return the public key.
*/
export function generateNonce(): Promise<string> {
- return callBackend("generate-nonce");
+ return callBackend("generate-nonce", { });
}
diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts
index 816ce0251..356b2af6e 100644
--- a/src/webex/wxBackend.ts
+++ b/src/webex/wxBackend.ts
@@ -24,7 +24,6 @@
/**
* Imports.
*/
-import { Checkable } from "../checkable";
import { BrowserHttpLib } from "../http";
import * as logging from "../logging";
import {
@@ -34,7 +33,7 @@ import {
import {
AmountJson,
Notifier,
- OfferRecord,
+ ProposalRecord,
} from "../types";
import {
ConfirmReserveRequest,
@@ -44,6 +43,7 @@ import {
} from "../wallet";
import { ChromeBadge } from "./chromeBadge";
+import { MessageType } from "./messages";
import URI = require("urijs");
import Port = chrome.runtime.Port;
@@ -60,21 +60,23 @@ const DB_NAME = "taler";
*/
const DB_VERSION = 17;
-type Handler = (detail: any, sender: MessageSender) => Promise<any>;
-
-function makeHandlers(db: IDBDatabase,
- wallet: Wallet): { [msg: string]: Handler } {
- return {
- ["balances"]: (detail, sender) => {
+function handleMessage(db: IDBDatabase,
+ wallet: Wallet,
+ sender: MessageSender,
+ type: MessageType, detail: any): any {
+ function assertNotFound(t: never): never {
+ console.error(`Request type ${t as string} unknown`);
+ console.error(`Request detail was ${detail}`);
+ return { error: "request unknown", requestType: type } as never;
+ }
+ switch (type) {
+ case "balances":
return wallet.getBalances();
- },
- ["dump-db"]: (detail, sender) => {
+ case "dump-db":
return exportDb(db);
- },
- ["import-db"]: (detail, sender) => {
+ case "import-db":
return importDb(db, detail.dump);
- },
- ["get-tab-cookie"]: (detail, sender) => {
+ case "get-tab-cookie":
if (!sender || !sender.tab || !sender.tab.id) {
return Promise.resolve();
}
@@ -82,11 +84,9 @@ function makeHandlers(db: IDBDatabase,
const info: any = paymentRequestCookies[id] as any;
delete paymentRequestCookies[id];
return Promise.resolve(info);
- },
- ["ping"]: (detail, sender) => {
+ case "ping":
return Promise.resolve();
- },
- ["reset"]: (detail, sender) => {
+ case "reset":
if (db) {
const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
// tslint:disable-next-line:prefer-for-of
@@ -95,69 +95,36 @@ function makeHandlers(db: IDBDatabase,
}
}
deleteDb();
-
chrome.browserAction.setBadgeText({ text: "" });
console.log("reset done");
- // Response is synchronous
return Promise.resolve({});
- },
- ["create-reserve"]: (detail, sender) => {
+ case "create-reserve": {
const d = {
amount: detail.amount,
exchange: detail.exchange,
};
const req = CreateReserveRequest.checked(d);
return wallet.createReserve(req);
- },
- ["confirm-reserve"]: (detail, sender) => {
- // TODO: make it a checkable
+ }
+ case "confirm-reserve":
const d = {
reservePub: detail.reservePub,
};
const req = ConfirmReserveRequest.checked(d);
return wallet.confirmReserve(req);
- },
- ["generate-nonce"]: (detail, sender) => {
+ case "generate-nonce":
return wallet.generateNonce();
- },
- ["confirm-pay"]: (detail, sender) => {
- let offer: OfferRecord;
- try {
- offer = OfferRecord.checked(detail.offer);
- } catch (e) {
- if (e instanceof Checkable.SchemaError) {
- console.error("schema error:", e.message);
- return Promise.resolve({
- detail,
- error: "invalid contract",
- hint: e.message,
- });
- } else {
- throw e;
- }
+ case "confirm-pay":
+ if (typeof detail.proposalId !== "number") {
+ throw Error("proposalId must be number");
}
-
- return wallet.confirmPay(offer);
- },
- ["check-pay"]: (detail, sender) => {
- let offer: OfferRecord;
- try {
- offer = OfferRecord.checked(detail.offer);
- } catch (e) {
- if (e instanceof Checkable.SchemaError) {
- console.error("schema error:", e.message);
- return Promise.resolve({
- detail,
- error: "invalid contract",
- hint: e.message,
- });
- } else {
- throw e;
- }
+ return wallet.confirmPay(detail.proposalId);
+ case "check-pay":
+ if (typeof detail.proposalId !== "number") {
+ throw Error("proposalId must be number");
}
- return wallet.checkPay(offer);
- },
- ["query-payment"]: (detail: any, sender: MessageSender) => {
+ return wallet.checkPay(detail.proposalId);
+ case "query-payment":
if (sender.tab && sender.tab.id) {
rateLimitCache[sender.tab.id]++;
if (rateLimitCache[sender.tab.id] > 10) {
@@ -171,120 +138,98 @@ function makeHandlers(db: IDBDatabase,
}
}
return wallet.queryPayment(detail.url);
- },
- ["exchange-info"]: (detail) => {
+ case "exchange-info":
if (!detail.baseUrl) {
return Promise.resolve({ error: "bad url" });
}
return wallet.updateExchangeFromUrl(detail.baseUrl);
- },
- ["currency-info"]: (detail) => {
+ case "currency-info":
if (!detail.name) {
return Promise.resolve({ error: "name missing" });
}
return wallet.getCurrencyRecord(detail.name);
- },
- ["hash-contract"]: (detail) => {
+ case "hash-contract":
if (!detail.contract) {
return Promise.resolve({ error: "contract missing" });
}
return wallet.hashContract(detail.contract).then((hash) => {
- return { hash };
+ return hash;
});
- },
- ["put-history-entry"]: (detail: any) => {
+ case "put-history-entry":
if (!detail.historyEntry) {
return Promise.resolve({ error: "historyEntry missing" });
}
return wallet.putHistory(detail.historyEntry);
- },
- ["save-offer"]: (detail: any) => {
- const offer = detail.offer;
- if (!offer) {
- return Promise.resolve({ error: "offer missing" });
- }
- console.log("handling safe-offer", detail);
- // FIXME: fully migrate to new terminology
- const checkedOffer = OfferRecord.checked(offer);
- return wallet.saveOffer(checkedOffer);
- },
- ["reserve-creation-info"]: (detail, sender) => {
+ case "save-proposal":
+ console.log("handling save-proposal", detail);
+ const checkedRecord = ProposalRecord.checked({
+ contractTerms: detail.data,
+ contractTermsHash: detail.hash,
+ merchantSig: detail.sig,
+ });
+ return wallet.saveProposal(checkedRecord);
+ case "reserve-creation-info":
if (!detail.baseUrl || typeof detail.baseUrl !== "string") {
return Promise.resolve({ error: "bad url" });
}
const amount = AmountJson.checked(detail.amount);
return wallet.getReserveCreationInfo(detail.baseUrl, amount);
- },
- ["get-history"]: (detail, sender) => {
+ case "get-history":
// TODO: limit history length
return wallet.getHistory();
- },
- ["get-offer"]: (detail, sender) => {
- return wallet.getOffer(detail.offerId);
- },
- ["get-exchanges"]: (detail, sender) => {
+ case "get-proposal":
+ return wallet.getProposal(detail.proposalId);
+ case "get-exchanges":
return wallet.getExchanges();
- },
- ["get-currencies"]: (detail, sender) => {
+ case "get-currencies":
return wallet.getCurrencies();
- },
- ["update-currency"]: (detail, sender) => {
+ case "update-currency":
return wallet.updateCurrency(detail.currencyRecord);
- },
- ["get-reserves"]: (detail, sender) => {
+ case "get-reserves":
if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangeBaseUrl missing"));
}
return wallet.getReserves(detail.exchangeBaseUrl);
- },
- ["get-payback-reserves"]: (detail, sender) => {
+ case "get-payback-reserves":
return wallet.getPaybackReserves();
- },
- ["withdraw-payback-reserve"]: (detail, sender) => {
+ case "withdraw-payback-reserve":
if (typeof detail.reservePub !== "string") {
return Promise.reject(Error("reservePub missing"));
}
return wallet.withdrawPaybackReserve(detail.reservePub);
- },
- ["get-coins"]: (detail, sender) => {
+ case "get-coins":
if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing"));
}
return wallet.getCoins(detail.exchangeBaseUrl);
- },
- ["get-precoins"]: (detail, sender) => {
+ case "get-precoins":
if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing"));
}
return wallet.getPreCoins(detail.exchangeBaseUrl);
- },
- ["get-denoms"]: (detail, sender) => {
+ case "get-denoms":
if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing"));
}
return wallet.getDenoms(detail.exchangeBaseUrl);
- },
- ["refresh-coin"]: (detail, sender) => {
+ case "refresh-coin":
if (typeof detail.coinPub !== "string") {
return Promise.reject(Error("coinPub missing"));
}
return wallet.refresh(detail.coinPub);
- },
- ["payback-coin"]: (detail, sender) => {
+ case "payback-coin":
if (typeof detail.coinPub !== "string") {
return Promise.reject(Error("coinPub missing"));
}
return wallet.payback(detail.coinPub);
- },
- ["payment-failed"]: (detail, sender) => {
+ case "payment-failed":
// For now we just update exchanges (maybe the exchange did something
// wrong and the keys were messed up).
// FIXME: in the future we should look at what actually went wrong.
console.error("payment reported as failed");
wallet.updateExchanges();
return Promise.resolve();
- },
- ["payment-succeeded"]: (detail, sender) => {
+ case "payment-succeeded":
const contractTermsHash = detail.contractTermsHash;
const merchantSig = detail.merchantSig;
if (!contractTermsHash) {
@@ -294,24 +239,16 @@ function makeHandlers(db: IDBDatabase,
return Promise.reject(Error("merchantSig missing"));
}
return wallet.paymentSucceeded(contractTermsHash, merchantSig);
- },
- };
-}
-
-
-async function dispatch(handlers: any, req: any, sender: any, sendResponse: any): Promise<void> {
- if (!(req.type in handlers)) {
- console.error(`Request type ${req.type} unknown`);
- console.error(`Request was ${req}`);
- try {
- sendResponse({ error: "request unknown", requestType: req.type });
- } catch (e) {
- // might fail if tab disconnected
- }
+ default:
+ // Exhaustiveness check.
+ // See https://www.typescriptlang.org/docs/handbook/advanced-types.html
+ return assertNotFound(type);
}
+}
+async function dispatch(db: IDBDatabase, wallet: Wallet, req: any, sender: any, sendResponse: any): Promise<void> {
try {
- const p = handlers[req.type](req.detail, sender);
+ const p = handleMessage(db, wallet, sender, req.type, req.detail);
const r = await p;
try {
sendResponse(r);
@@ -587,9 +524,8 @@ export async function wxMain() {
// Handlers for messages coming directly from the content
// script on the page
- const handlers = makeHandlers(db, wallet!);
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
- dispatch(handlers, req, sender, sendResponse);
+ dispatch(db, wallet, req, sender, sendResponse);
return true;
});