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 { createPayPage } from "./pages/pay";
import { createRefundPage } from "./pages/refund";
import { setupI18n } from "taler-wallet-core";
function main(): void {
try {
@ -65,6 +66,8 @@ function main(): void {
}
}
setupI18n("en-US");
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", main);
} else {

View File

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

View File

@ -29,11 +29,13 @@ import React, { useState, useEffect } from "react";
import {
acceptWithdrawal,
onUpdateNotification,
getWithdrawalDetailsForUri,
} from "../wxApi";
import { WithdrawUriInfoResponse } from "taler-wallet-core";
function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
const [details, setDetails] = useState<
any | undefined
WithdrawUriInfoResponse | undefined
>();
const [selectedExchange, setSelectedExchange] = useState<
string | undefined
@ -54,55 +56,12 @@ function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
useEffect(() => {
const fetchData = async (): Promise<void> => {
// FIXME: re-implement with new API
// console.log("getting from", talerWithdrawUri);
// 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);
const res = await getWithdrawalDetailsForUri({talerWithdrawUri: props.talerWithdrawUri});
setDetails(res);
};
fetchData();
}, [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) {
return <span>Loading...</span>;
}
@ -111,51 +70,6 @@ function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
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> => {
if (!selectedExchange) {
throw Error("can't accept, no exchange selected");
@ -173,7 +87,7 @@ function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
<h1>Digital Cash Withdrawal</h1>
<i18n.Translate wrap="p">
You are about to withdraw{" "}
<strong>{renderAmount(details.bankWithdrawDetails.amount)}</strong> from
<strong>{renderAmount(details.amount)}</strong> from
your bank account into your wallet.
</i18n.Translate>
{selectedExchange ? (
@ -211,9 +125,9 @@ function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
</span>
</p>
{details.exchangeWithdrawDetails ? (
{/* {details.exchangeWithdrawDetails ? (
<WithdrawDetailView rci={details.exchangeWithdrawDetails} />
) : null}
) : null} */}
</div>
</div>
);

View File

@ -21,14 +21,26 @@
/**
* 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 {
newValue: boolean;
}
/**
* Response with information about available version upgrades.
*/
@ -50,23 +62,9 @@ export interface UpgradeResponse {
oldDbVersion: string;
}
/**
* 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> {
async function callBackend(operation: string, payload: any): Promise<any> {
return new Promise<any>((resolve, reject) => {
chrome.runtime.sendMessage({ type, detail }, (resp) => {
chrome.runtime.sendMessage({ operation, payload, id: "(none)" }, (resp) => {
if (chrome.runtime.lastError) {
console.log("Error calling backend");
reject(
@ -75,19 +73,17 @@ async function callBackend(
),
);
}
if (typeof resp === "object" && resp && resp.error) {
console.warn("response error:", resp);
const e = new WalletApiError(resp.error.message, resp.error);
reject(e);
} else {
resolve(resp);
console.log("got response", resp);
const r = resp as CoreApiResponse;
if (r.type === "error") {
reject(new OperationFailedError(r.error));
return;
}
resolve(r.result);
});
});
}
/**
* Start refreshing a coin.
*/
@ -123,7 +119,7 @@ export function resetDb(): Promise<void> {
* Get balances for all currencies/exchanges.
*/
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", {});
}
/**
* Get diagnostics information
*/
export function getWithdrawalDetailsForUri(
req: GetWithdrawalDetailsForUriRequest,
): Promise<WithdrawUriInfoResponse> {
return callBackend("getWithdrawalDetailsForUri", req);
}
export function onUpdateNotification(f: () => void): () => void {
const port = chrome.runtime.connect({ name: "notifications" });
const listener = (): void => {

View File

@ -28,7 +28,21 @@ import * as wxApi from "./wxApi";
import MessageSender = chrome.runtime.MessageSender;
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 { BrowserCryptoWorkerFactory } from "./browserCryptoWorkerFactory";
@ -51,255 +65,29 @@ const walletInit: OpenedPromise<void> = openPromise<void>();
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(
req: any,
sender: any,
sendResponse: any,
): Promise<void> {
try {
const p = handleMessage(sender, req.type, req.detail);
const r = await p;
const w = currentWallet;
if (!w) {
sendResponse(
makeErrorDetails(
TalerErrorCode.WALLET_CORE_NOT_AVAILABLE,
"wallet core not available",
{},
),
);
return;
}
const r = await handleCoreApiRequest(w, req.operation, req.id, req.payload);
try {
sendResponse(r);
} catch (e) {
// 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> {
@ -436,7 +224,7 @@ function headerListener(
switch (uriType) {
case TalerUriType.TalerWithdraw:
return makeSyncWalletRedirect(
"withdraw.html",
"/static/withdraw.html",
details.tabId,
details.url,
{
@ -445,7 +233,7 @@ function headerListener(
);
case TalerUriType.TalerPay:
return makeSyncWalletRedirect(
"pay.html",
"/static/pay.html",
details.tabId,
details.url,
{
@ -454,7 +242,7 @@ function headerListener(
);
case TalerUriType.TalerTip:
return makeSyncWalletRedirect(
"tip.html",
"/static/tip.html",
details.tabId,
details.url,
{
@ -463,7 +251,7 @@ function headerListener(
);
case TalerUriType.TalerRefund:
return makeSyncWalletRedirect(
"refund.html",
"/static/refund.html",
details.tabId,
details.url,
{

View File

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

View File

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

View File

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

View File

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