web extension WIP

This commit is contained in:
Florian Dold 2020-08-14 00:13:51 +05:30
parent 599c8380f2
commit cbe325cb0f
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
9 changed files with 98 additions and 388 deletions

View File

@ -26,6 +26,7 @@ import { createWithdrawPage } from "./pages/withdraw";
import { createWelcomePage } from "./pages/welcome"; import { createWelcomePage } from "./pages/welcome";
import { createPayPage } from "./pages/pay"; import { createPayPage } from "./pages/pay";
import { createRefundPage } from "./pages/refund"; import { createRefundPage } from "./pages/refund";
import { setupI18n } from "taler-wallet-core";
function main(): void { function main(): void {
try { try {
@ -65,6 +66,8 @@ function main(): void {
} }
} }
setupI18n("en-US");
if (document.readyState === "loading") { if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", main); document.addEventListener("DOMContentLoaded", main);
} else { } else {

View File

@ -397,19 +397,19 @@ function actionForTalerUri(talerUri: string): string | undefined {
const uriType = classifyTalerUri(talerUri); const uriType = classifyTalerUri(talerUri);
switch (uriType) { switch (uriType) {
case TalerUriType.TalerWithdraw: case TalerUriType.TalerWithdraw:
return makeExtensionUrlWithParams("withdraw.html", { return makeExtensionUrlWithParams("static/withdraw.html", {
talerWithdrawUri: talerUri, talerWithdrawUri: talerUri,
}); });
case TalerUriType.TalerPay: case TalerUriType.TalerPay:
return makeExtensionUrlWithParams("pay.html", { return makeExtensionUrlWithParams("static/pay.html", {
talerPayUri: talerUri, talerPayUri: talerUri,
}); });
case TalerUriType.TalerTip: case TalerUriType.TalerTip:
return makeExtensionUrlWithParams("tip.html", { return makeExtensionUrlWithParams("static/tip.html", {
talerTipUri: talerUri, talerTipUri: talerUri,
}); });
case TalerUriType.TalerRefund: case TalerUriType.TalerRefund:
return makeExtensionUrlWithParams("refund.html", { return makeExtensionUrlWithParams("static/refund.html", {
talerRefundUri: talerUri, talerRefundUri: talerUri,
}); });
case TalerUriType.TalerNotifyReserve: case TalerUriType.TalerNotifyReserve:

View File

@ -29,11 +29,13 @@ import React, { useState, useEffect } from "react";
import { import {
acceptWithdrawal, acceptWithdrawal,
onUpdateNotification, onUpdateNotification,
getWithdrawalDetailsForUri,
} from "../wxApi"; } from "../wxApi";
import { WithdrawUriInfoResponse } from "taler-wallet-core";
function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element { function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
const [details, setDetails] = useState< const [details, setDetails] = useState<
any | undefined WithdrawUriInfoResponse | undefined
>(); >();
const [selectedExchange, setSelectedExchange] = useState< const [selectedExchange, setSelectedExchange] = useState<
string | undefined string | undefined
@ -54,55 +56,12 @@ function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
useEffect(() => { useEffect(() => {
const fetchData = async (): Promise<void> => { const fetchData = async (): Promise<void> => {
// FIXME: re-implement with new API const res = await getWithdrawalDetailsForUri({talerWithdrawUri: props.talerWithdrawUri});
// console.log("getting from", talerWithdrawUri); setDetails(res);
// let d: WithdrawalDetailsResponse | undefined = undefined;
// try {
// d = await getWithdrawDetails(talerWithdrawUri, selectedExchange);
// } catch (e) {
// console.error(
// `error getting withdraw details for uri ${talerWithdrawUri}, exchange ${selectedExchange}`,
// e,
// );
// setErrMsg(e.message);
// return;
// }
// console.log("got withdrawDetails", d);
// if (!selectedExchange && d.bankWithdrawDetails.suggestedExchange) {
// console.log("setting selected exchange");
// setSelectedExchange(d.bankWithdrawDetails.suggestedExchange);
// }
// setDetails(d);
}; };
fetchData(); fetchData();
}, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter]); }, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter]);
if (errMsg) {
return (
<div>
<i18n.Translate wrap="p">
Could not get details for withdraw operation:
</i18n.Translate>
<p style={{ color: "red" }}>{errMsg}</p>
<p>
<span
role="button"
tabIndex={0}
style={{ textDecoration: "underline", cursor: "pointer" }}
onClick={() => {
setSelecting(true);
setErrMsg(undefined);
setSelectedExchange(undefined);
setDetails(undefined);
}}
>
{i18n.str`Chose different exchange provider`}
</span>
</p>
</div>
);
}
if (!details) { if (!details) {
return <span>Loading...</span>; return <span>Loading...</span>;
} }
@ -111,51 +70,6 @@ function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
return <span>Withdraw operation has been cancelled.</span>; return <span>Withdraw operation has been cancelled.</span>;
} }
if (selecting) {
const bankSuggestion =
details && details.bankWithdrawDetails.suggestedExchange;
return (
<div>
{i18n.str`Please select an exchange. You can review the details before after your selection.`}
{bankSuggestion && (
<div>
<h2>Bank Suggestion</h2>
<button
className="pure-button button-success"
onClick={() => {
setDetails(undefined);
setSelectedExchange(bankSuggestion);
setSelecting(false);
}}
>
<i18n.Translate wrap="span">
Select <strong>{bankSuggestion}</strong>
</i18n.Translate>
</button>
</div>
)}
<h2>Custom Selection</h2>
<p>
<input
type="text"
onChange={(e) => setCustomUrl(e.target.value)}
value={customUrl}
/>
</p>
<button
className="pure-button button-success"
onClick={() => {
setDetails(undefined);
setSelectedExchange(customUrl);
setSelecting(false);
}}
>
<i18n.Translate wrap="span">Select custom exchange</i18n.Translate>
</button>
</div>
);
}
const accept = async (): Promise<void> => { const accept = async (): Promise<void> => {
if (!selectedExchange) { if (!selectedExchange) {
throw Error("can't accept, no exchange selected"); throw Error("can't accept, no exchange selected");
@ -173,7 +87,7 @@ function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
<h1>Digital Cash Withdrawal</h1> <h1>Digital Cash Withdrawal</h1>
<i18n.Translate wrap="p"> <i18n.Translate wrap="p">
You are about to withdraw{" "} You are about to withdraw{" "}
<strong>{renderAmount(details.bankWithdrawDetails.amount)}</strong> from <strong>{renderAmount(details.amount)}</strong> from
your bank account into your wallet. your bank account into your wallet.
</i18n.Translate> </i18n.Translate>
{selectedExchange ? ( {selectedExchange ? (
@ -211,9 +125,9 @@ function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
</span> </span>
</p> </p>
{details.exchangeWithdrawDetails ? ( {/* {details.exchangeWithdrawDetails ? (
<WithdrawDetailView rci={details.exchangeWithdrawDetails} /> <WithdrawDetailView rci={details.exchangeWithdrawDetails} />
) : null} ) : null} */}
</div> </div>
</div> </div>
); );

View File

@ -21,14 +21,26 @@
/** /**
* Imports. * Imports.
*/ */
import { AmountJson, ConfirmPayResult, BalancesResponse, PurchaseDetails, TipStatus, BenchmarkResult, PreparePayResult, AcceptWithdrawalResponse, WalletDiagnostics } from "taler-wallet-core"; import {
AmountJson,
ConfirmPayResult,
BalancesResponse,
PurchaseDetails,
TipStatus,
BenchmarkResult,
PreparePayResult,
AcceptWithdrawalResponse,
WalletDiagnostics,
CoreApiResponse,
OperationFailedError,
GetWithdrawalDetailsForUriRequest,
WithdrawUriInfoResponse,
} from "taler-wallet-core";
export interface ExtendedPermissionsResponse { export interface ExtendedPermissionsResponse {
newValue: boolean; newValue: boolean;
} }
/** /**
* Response with information about available version upgrades. * Response with information about available version upgrades.
*/ */
@ -50,23 +62,9 @@ export interface UpgradeResponse {
oldDbVersion: string; oldDbVersion: string;
} }
/** async function callBackend(operation: string, payload: any): Promise<any> {
* Error thrown when the function from the backend (via RPC) threw an error.
*/
export class WalletApiError extends Error {
constructor(message: string, public detail: any) {
super(message);
// restore prototype chain
Object.setPrototypeOf(this, new.target.prototype);
}
}
async function callBackend(
type: string,
detail: any,
): Promise<any> {
return new Promise<any>((resolve, reject) => { return new Promise<any>((resolve, reject) => {
chrome.runtime.sendMessage({ type, detail }, (resp) => { chrome.runtime.sendMessage({ operation, payload, id: "(none)" }, (resp) => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.log("Error calling backend"); console.log("Error calling backend");
reject( reject(
@ -75,19 +73,17 @@ async function callBackend(
), ),
); );
} }
if (typeof resp === "object" && resp && resp.error) { console.log("got response", resp);
console.warn("response error:", resp); const r = resp as CoreApiResponse;
const e = new WalletApiError(resp.error.message, resp.error); if (r.type === "error") {
reject(e); reject(new OperationFailedError(r.error));
} else { return;
resolve(resp);
} }
resolve(r.result);
}); });
}); });
} }
/** /**
* Start refreshing a coin. * Start refreshing a coin.
*/ */
@ -123,7 +119,7 @@ export function resetDb(): Promise<void> {
* Get balances for all currencies/exchanges. * Get balances for all currencies/exchanges.
*/ */
export function getBalance(): Promise<BalancesResponse> { export function getBalance(): Promise<BalancesResponse> {
return callBackend("balances", {}); return callBackend("getBalances", {});
} }
/** /**
@ -227,6 +223,15 @@ export function getExtendedPermissions(): Promise<ExtendedPermissionsResponse> {
return callBackend("get-extended-permissions", {}); return callBackend("get-extended-permissions", {});
} }
/**
* Get diagnostics information
*/
export function getWithdrawalDetailsForUri(
req: GetWithdrawalDetailsForUriRequest,
): Promise<WithdrawUriInfoResponse> {
return callBackend("getWithdrawalDetailsForUri", req);
}
export function onUpdateNotification(f: () => void): () => void { export function onUpdateNotification(f: () => void): () => void {
const port = chrome.runtime.connect({ name: "notifications" }); const port = chrome.runtime.connect({ name: "notifications" });
const listener = (): void => { const listener = (): void => {

View File

@ -28,7 +28,21 @@ import * as wxApi from "./wxApi";
import MessageSender = chrome.runtime.MessageSender; import MessageSender = chrome.runtime.MessageSender;
import { extendedPermissions } from "./permissions"; import { extendedPermissions } from "./permissions";
import { Wallet, OpenedPromise, openPromise, deleteTalerDatabase, WALLET_DB_MINOR_VERSION, WalletDiagnostics, openTalerDatabase, Database, classifyTalerUri, TalerUriType } from "taler-wallet-core"; import {
Wallet,
OpenedPromise,
openPromise,
deleteTalerDatabase,
WALLET_DB_MINOR_VERSION,
WalletDiagnostics,
openTalerDatabase,
Database,
classifyTalerUri,
TalerUriType,
makeErrorDetails,
TalerErrorCode,
handleCoreApiRequest,
} from "taler-wallet-core";
import { BrowserHttpLib } from "./browserHttpLib"; import { BrowserHttpLib } from "./browserHttpLib";
import { BrowserCryptoWorkerFactory } from "./browserCryptoWorkerFactory"; import { BrowserCryptoWorkerFactory } from "./browserCryptoWorkerFactory";
@ -51,255 +65,29 @@ const walletInit: OpenedPromise<void> = openPromise<void>();
const notificationPorts: chrome.runtime.Port[] = []; const notificationPorts: chrome.runtime.Port[] = [];
async function handleMessage(
sender: MessageSender,
type: string,
detail: any,
): Promise<any> {
function needsWallet(): Wallet {
if (!currentWallet) {
throw NeedsWallet;
}
return currentWallet;
}
switch (type) {
case "balances": {
return needsWallet().getBalances();
}
case "dump-db": {
const db = needsWallet().db;
return db.exportDatabase();
}
case "import-db": {
const db = needsWallet().db;
return db.importDatabase(detail.dump);
}
case "ping": {
return Promise.resolve();
}
case "reset-db": {
deleteTalerDatabase(indexedDB);
setBadgeText({ text: "" });
console.log("reset done");
if (!currentWallet) {
reinitWallet();
}
return Promise.resolve({});
}
case "confirm-pay": {
if (typeof detail.proposalId !== "string") {
throw Error("proposalId must be string");
}
return needsWallet().confirmPay(detail.proposalId, detail.sessionId);
}
case "exchange-info": {
if (!detail.baseUrl) {
return Promise.resolve({ error: "bad url" });
}
return needsWallet().updateExchangeFromUrl(detail.baseUrl);
}
case "get-exchanges": {
return needsWallet().getExchangeRecords();
}
case "get-currencies": {
return needsWallet().getCurrencies();
}
case "update-currency": {
return needsWallet().updateCurrency(detail.currencyRecord);
}
case "get-reserves": {
if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangeBaseUrl missing"));
}
return needsWallet().getReserves(detail.exchangeBaseUrl);
}
case "get-coins": {
if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing"));
}
return needsWallet().getCoinsForExchange(detail.exchangeBaseUrl);
}
case "get-denoms": {
if (typeof detail.exchangeBaseUrl !== "string") {
return Promise.reject(Error("exchangBaseUrl missing"));
}
return needsWallet().getDenoms(detail.exchangeBaseUrl);
}
case "refresh-coin": {
if (typeof detail.coinPub !== "string") {
return Promise.reject(Error("coinPub missing"));
}
return needsWallet().refresh(detail.coinPub);
}
case "get-sender-wire-infos": {
return needsWallet().getSenderWireInfos();
}
case "return-coins": {
const d = {
amount: detail.amount,
exchange: detail.exchange,
senderWire: detail.senderWire,
};
return needsWallet().returnCoins(d);
}
case "check-upgrade": {
let dbResetRequired = false;
if (!currentWallet) {
dbResetRequired = true;
}
const resp: wxApi.UpgradeResponse = {
currentDbVersion: WALLET_DB_MINOR_VERSION.toString(),
dbResetRequired,
oldDbVersion: (outdatedDbVersion || "unknown").toString(),
};
return resp;
}
case "get-purchase-details": {
const proposalId = detail.proposalId;
if (!proposalId) {
throw Error("proposalId missing");
}
if (typeof proposalId !== "string") {
throw Error("proposalId must be a string");
}
return needsWallet().getPurchaseDetails(proposalId);
}
case "accept-refund":
return needsWallet().applyRefund(detail.refundUrl);
case "get-tip-status": {
return needsWallet().getTipStatus(detail.talerTipUri);
}
case "accept-tip": {
return needsWallet().acceptTip(detail.talerTipUri);
}
case "abort-failed-payment": {
if (!detail.contractTermsHash) {
throw Error("contracTermsHash not given");
}
return needsWallet().abortFailedPayment(detail.contractTermsHash);
}
case "benchmark-crypto": {
if (!detail.repetitions) {
throw Error("repetitions not given");
}
return needsWallet().benchmarkCrypto(detail.repetitions);
}
case "accept-withdrawal": {
return needsWallet().acceptWithdrawal(
detail.talerWithdrawUri,
detail.selectedExchange,
);
}
case "get-diagnostics": {
const manifestData = chrome.runtime.getManifest();
const errors: string[] = [];
let firefoxIdbProblem = false;
let dbOutdated = false;
try {
await walletInit.promise;
} catch (e) {
errors.push("Error during wallet initialization: " + e);
if (
currentDatabase === undefined &&
outdatedDbVersion === undefined &&
isFirefox()
) {
firefoxIdbProblem = true;
}
}
if (!currentWallet) {
errors.push("Could not create wallet backend.");
}
if (!currentDatabase) {
errors.push("Could not open database");
}
if (outdatedDbVersion !== undefined) {
errors.push(`Outdated DB version: ${outdatedDbVersion}`);
dbOutdated = true;
}
const diagnostics: WalletDiagnostics = {
walletManifestDisplayVersion:
manifestData.version_name || "(undefined)",
walletManifestVersion: manifestData.version,
errors,
firefoxIdbProblem,
dbOutdated,
};
return diagnostics;
}
case "prepare-pay":
return needsWallet().preparePayForUri(detail.talerPayUri);
case "set-extended-permissions": {
const newVal = detail.value;
console.log("new extended permissions value", newVal);
if (newVal) {
setupHeaderListener();
return { newValue: true };
} else {
await new Promise((resolve, reject) => {
getPermissionsApi().remove(extendedPermissions, (rem) => {
console.log("permissions removed:", rem);
resolve();
});
});
return { newVal: false };
}
}
case "get-extended-permissions": {
const res = await new Promise((resolve, reject) => {
getPermissionsApi().contains(extendedPermissions, (result: boolean) => {
resolve(result);
});
});
return { newValue: res };
}
default:
console.error(`Request type ${type} unknown`);
console.error(`Request detail was ${detail}`);
return {
error: {
message: `request type ${type} unknown`,
requestType: type,
},
};
}
}
async function dispatch( async function dispatch(
req: any, req: any,
sender: any, sender: any,
sendResponse: any, sendResponse: any,
): Promise<void> { ): Promise<void> {
try { const w = currentWallet;
const p = handleMessage(sender, req.type, req.detail); if (!w) {
const r = await p; sendResponse(
makeErrorDetails(
TalerErrorCode.WALLET_CORE_NOT_AVAILABLE,
"wallet core not available",
{},
),
);
return;
}
const r = await handleCoreApiRequest(w, req.operation, req.id, req.payload);
try { try {
sendResponse(r); sendResponse(r);
} catch (e) { } catch (e) {
// might fail if tab disconnected // might fail if tab disconnected
} }
} catch (e) {
console.log(`exception during wallet handler for '${req.type}'`);
console.log("request", req);
console.error(e);
let stack;
try {
stack = e.stack.toString();
} catch (e) {
// might fail
}
try {
sendResponse({
error: {
message: e.message,
stack,
},
});
} catch (e) {
console.log(e);
// might fail if tab disconnected
}
}
} }
function getTab(tabId: number): Promise<chrome.tabs.Tab> { function getTab(tabId: number): Promise<chrome.tabs.Tab> {
@ -436,7 +224,7 @@ function headerListener(
switch (uriType) { switch (uriType) {
case TalerUriType.TalerWithdraw: case TalerUriType.TalerWithdraw:
return makeSyncWalletRedirect( return makeSyncWalletRedirect(
"withdraw.html", "/static/withdraw.html",
details.tabId, details.tabId,
details.url, details.url,
{ {
@ -445,7 +233,7 @@ function headerListener(
); );
case TalerUriType.TalerPay: case TalerUriType.TalerPay:
return makeSyncWalletRedirect( return makeSyncWalletRedirect(
"pay.html", "/static/pay.html",
details.tabId, details.tabId,
details.url, details.url,
{ {
@ -454,7 +242,7 @@ function headerListener(
); );
case TalerUriType.TalerTip: case TalerUriType.TalerTip:
return makeSyncWalletRedirect( return makeSyncWalletRedirect(
"tip.html", "/static/tip.html",
details.tabId, details.tabId,
details.url, details.url,
{ {
@ -463,7 +251,7 @@ function headerListener(
); );
case TalerUriType.TalerRefund: case TalerUriType.TalerRefund:
return makeSyncWalletRedirect( return makeSyncWalletRedirect(
"refund.html", "/static/refund.html",
details.tabId, details.tabId,
details.url, details.url,
{ {

View File

@ -3,9 +3,9 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>Taler Wallet: Benchmarks</title> <title>Taler Wallet: Benchmarks</title>
<link rel="stylesheet" type="text/css" href="/style/wallet.css" /> <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
<link rel="icon" href="/img/icon.png" /> <link rel="icon" href="/static/img/icon.png" />
<script src="/pageEntryPoint.js"></script> <script src="/dist/pageEntryPoint.js"></script>
</head> </head>
<body> <body>
<section id="main"> <section id="main">

View File

@ -4,10 +4,10 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>Taler Wallet: Confirm Contract</title> <title>Taler Wallet: Confirm Contract</title>
<link rel="stylesheet" type="text/css" href="/style/pure.css" /> <link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
<link rel="stylesheet" type="text/css" href="/style/wallet.css" /> <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
<link rel="icon" href="/img/icon.png" /> <link rel="icon" href="/static/img/icon.png" />
<script src="/pageEntryPoint.js"></script> <script src="/dist/pageEntryPoint.js"></script>
<style> <style>
button.accept { button.accept {

View File

@ -2,11 +2,11 @@
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="/style/pure.css" /> <link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
<link rel="stylesheet" type="text/css" href="/style/wallet.css" /> <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
<link rel="stylesheet" type="text/css" href="/style/popup.css" /> <link rel="stylesheet" type="text/css" href="/static/style/popup.css" />
<link rel="icon" href="/img/icon.png" /> <link rel="icon" href="/static/img/icon.png" />
<script src="/pageEntryPoint.js"></script> <script src="/dist/pageEntryPoint.js"></script>
</head> </head>
<body> <body>

View File

@ -3,10 +3,10 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>Taler Wallet: Withdraw</title> <title>Taler Wallet: Withdraw</title>
<link rel="icon" href="/img/icon.png" /> <link rel="icon" href="/static/img/icon.png" />
<link rel="stylesheet" type="text/css" href="/style/pure.css" /> <link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
<link rel="stylesheet" type="text/css" href="/style/wallet.css" /> <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
<script src="/pageEntryPoint.js"></script> <script src="/dist/pageEntryPoint.js"></script>
</head> </head>
<body> <body>