towards the new withdrawal API (temporarily breaks WebExtension wallet)

This commit is contained in:
Florian Dold 2020-07-28 14:22:35 +05:30
parent 472307a607
commit 4365cd6401
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
10 changed files with 101 additions and 271 deletions

View File

@ -306,9 +306,8 @@ walletCli
break; break;
case TalerUriType.TalerWithdraw: case TalerUriType.TalerWithdraw:
{ {
const withdrawInfo = await wallet.getWithdrawDetailsForUri(uri); const withdrawInfo = await wallet.getWithdrawalDetailsForUri(uri);
const selectedExchange = const selectedExchange = withdrawInfo.defaultExchangeBaseUrl;
withdrawInfo.bankWithdrawDetails.suggestedExchange;
if (!selectedExchange) { if (!selectedExchange) {
console.error("no suggested exchange!"); console.error("no suggested exchange!");
process.exit(1); process.exit(1);

View File

@ -32,12 +32,13 @@ import {
import { import {
BankWithdrawDetails, BankWithdrawDetails,
ExchangeWithdrawDetails, ExchangeWithdrawDetails,
WithdrawalDetailsResponse,
OperationErrorDetails, OperationErrorDetails,
ExchangeListItem,
} from "../types/walletTypes"; } from "../types/walletTypes";
import { import {
codecForWithdrawOperationStatusResponse, codecForWithdrawOperationStatusResponse,
codecForWithdrawResponse, codecForWithdrawResponse,
WithdrawUriInfoResponse,
} from "../types/talerTypes"; } from "../types/talerTypes";
import { InternalWalletState } from "./state"; import { InternalWalletState } from "./state";
import { parseWithdrawUri } from "../util/taleruri"; import { parseWithdrawUri } from "../util/taleruri";
@ -154,7 +155,7 @@ export async function getBankWithdrawalInfo(
return { return {
amount: Amounts.parseOrThrow(status.amount), amount: Amounts.parseOrThrow(status.amount),
confirmTransferUrl: status.confirm_transfer_url, confirmTransferUrl: status.confirm_transfer_url,
extractedStatusUrl: uriResult.bankIntegrationApiBaseUrl, extractedStatusUrl: reqUrl.href,
selectionDone: status.selection_done, selectionDone: status.selection_done,
senderWire: status.sender_wire, senderWire: status.sender_wire,
suggestedExchange: status.suggested_exchange, suggestedExchange: status.suggested_exchange,
@ -706,22 +707,50 @@ export async function getExchangeWithdrawalInfo(
return ret; return ret;
} }
export async function getWithdrawDetailsForUri( export async function getWithdrawalDetailsForUri(
ws: InternalWalletState, ws: InternalWalletState,
talerWithdrawUri: string, talerWithdrawUri: string,
maybeSelectedExchange?: string, ): Promise<WithdrawUriInfoResponse> {
): Promise<WithdrawalDetailsResponse> {
const info = await getBankWithdrawalInfo(ws, talerWithdrawUri); const info = await getBankWithdrawalInfo(ws, talerWithdrawUri);
let rci: ExchangeWithdrawDetails | undefined = undefined; if (info.suggestedExchange) {
if (maybeSelectedExchange) { // FIXME: right now the exchange gets permanently added,
rci = await getExchangeWithdrawalInfo( // we might want to only temporarily add it.
ws, try {
maybeSelectedExchange, await updateExchangeFromUrl(ws, info.suggestedExchange);
info.amount, } catch (e) {
); // We still continued if it failed, as other exchanges might be available.
// We don't want to fail if the bank-suggested exchange is broken/offline.
logger.trace(`querying bank-suggested exchange (${info.suggestedExchange}) failed`)
}
}
const exchangesRes: (ExchangeListItem | undefined)[] = await ws.db
.iter(Stores.exchanges)
.map((x) => {
const details = x.details;
if (!details) {
return undefined;
}
if (!x.addComplete) {
return undefined;
}
if (!x.wireInfo) {
return undefined;
}
if (details.currency !== info.amount.currency) {
return undefined;
} }
return { return {
bankWithdrawDetails: info, exchangeBaseUrl: x.baseUrl,
exchangeWithdrawDetails: rci, currency: details.currency,
paytoUris: x.wireInfo.accounts.map((x) => x.payto_uri),
}; };
});
const exchanges = exchangesRes.filter((x) => !!x) as ExchangeListItem[];
return {
amount: Amounts.stringify(info.amount),
defaultExchangeBaseUrl: info.suggestedExchange,
possibleExchanges: exchanges,
}
} }

View File

@ -39,8 +39,6 @@ import {
Codec, Codec,
makeCodecForConstNumber, makeCodecForConstNumber,
makeCodecForUnion, makeCodecForUnion,
makeCodecForConstTrue,
makeCodecForConstFalse,
makeCodecForConstString, makeCodecForConstString,
} from "../util/codec"; } from "../util/codec";
import { import {
@ -49,6 +47,7 @@ import {
Duration, Duration,
codecForDuration, codecForDuration,
} from "../util/time"; } from "../util/time";
import { ExchangeListItem } from "./walletTypes";
/** /**
* Denomination as found in the /keys response from the exchange. * Denomination as found in the /keys response from the exchange.
@ -935,6 +934,12 @@ export interface MerchantOrderStatusUnpaid {
already_paid_order_id?: string; already_paid_order_id?: string;
} }
export interface WithdrawUriInfoResponse {
amount: AmountString;
defaultExchangeBaseUrl?: string;
possibleExchanges: ExchangeListItem[];
}
export type AmountString = string; export type AmountString = string;
export type Base32String = string; export type Base32String = string;
export type EddsaSignatureString = string; export type EddsaSignatureString = string;

View File

@ -146,11 +146,6 @@ export interface ExchangeWithdrawDetails {
walletVersion: string; walletVersion: string;
} }
export interface WithdrawalDetailsResponse {
bankWithdrawDetails: BankWithdrawDetails;
exchangeWithdrawDetails: ExchangeWithdrawDetails | undefined;
}
/** /**
* Mapping from currency/exchange to detailed balance * Mapping from currency/exchange to detailed balance
* information. * information.

View File

@ -29,8 +29,8 @@ import { Database } from "./util/query";
import { Amounts, AmountJson } from "./util/amounts"; import { Amounts, AmountJson } from "./util/amounts";
import { import {
getWithdrawDetailsForUri,
getExchangeWithdrawalInfo, getExchangeWithdrawalInfo,
getWithdrawalDetailsForUri,
} from "./operations/withdraw"; } from "./operations/withdraw";
import { import {
@ -53,7 +53,7 @@ import {
CoinSourceType, CoinSourceType,
RefundState, RefundState,
} from "./types/dbTypes"; } from "./types/dbTypes";
import { CoinDumpJson } from "./types/talerTypes"; import { CoinDumpJson, WithdrawUriInfoResponse } from "./types/talerTypes";
import { import {
BenchmarkResult, BenchmarkResult,
ConfirmPayResult, ConfirmPayResult,
@ -62,7 +62,6 @@ import {
TipStatus, TipStatus,
WalletBalance, WalletBalance,
PreparePayResult, PreparePayResult,
WithdrawalDetailsResponse,
AcceptWithdrawalResponse, AcceptWithdrawalResponse,
PurchaseDetails, PurchaseDetails,
RefreshReason, RefreshReason,
@ -479,15 +478,8 @@ export class Wallet {
return getExchangeTrust(this.ws, exchangeInfo); return getExchangeTrust(this.ws, exchangeInfo);
} }
async getWithdrawDetailsForUri( async getWithdrawalDetailsForUri(talerWithdrawUri: string): Promise<WithdrawUriInfoResponse> {
talerWithdrawUri: string, return getWithdrawalDetailsForUri(this.ws, talerWithdrawUri);
maybeSelectedExchange?: string,
): Promise<WithdrawalDetailsResponse> {
return getWithdrawDetailsForUri(
this.ws,
talerWithdrawUri,
maybeSelectedExchange,
);
} }
/** /**

View File

@ -108,16 +108,16 @@ const codecForApplyRefundRequest = (): Codec<ApplyRefundRequest> =>
.property("talerRefundUri", codecForString) .property("talerRefundUri", codecForString)
.build("ApplyRefundRequest"); .build("ApplyRefundRequest");
interface GetWithdrawUriInfoRequest { interface GetWithdrawalDetailsForUriRequest {
talerWithdrawUri: string; talerWithdrawUri: string;
} }
const codecForGetWithdrawUriInfoRequest = (): Codec< const codecForGetWithdrawalDetailsForUri = (): Codec<
GetWithdrawUriInfoRequest GetWithdrawalDetailsForUriRequest
> => > =>
makeCodecForObject<GetWithdrawUriInfoRequest>() makeCodecForObject<GetWithdrawalDetailsForUriRequest>()
.property("talerWithdrawUri", codecForString) .property("talerWithdrawUri", codecForString)
.build("GetWithdrawUriInfoRequest"); .build("GetWithdrawalDetailsForUriRequest");
interface AbortProposalRequest { interface AbortProposalRequest {
proposalId: string; proposalId: string;
@ -172,10 +172,9 @@ async function dispatchRequestInternal(
case "listExchanges": { case "listExchanges": {
return await wallet.getExchanges(); return await wallet.getExchanges();
} }
case "getWithdrawUriInfo": { case "getWithdrawalDetailsForUri": {
const req = codecForGetWithdrawUriInfoRequest().decode(payload); const req = codecForGetWithdrawalDetailsForUri().decode(payload);
// FIXME: implement "natively" return await wallet.getWithdrawalDetailsForUri(req.talerWithdrawUri);
throw Error("not implemented");
} }
case "acceptManualWithdrawal": { case "acceptManualWithdrawal": {
const req = codecForAcceptManualWithdrawalRequet().decode(payload); const req = codecForAcceptManualWithdrawalRequet().decode(payload);

View File

@ -1,159 +0,0 @@
/*
This file is part of TALER
(C) 2017 Inria and GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Type definitions for messages between content scripts/pages and backend.
*/
// Messages are already documented in wxApi.
/* tslint:disable:completed-docs */
import * as dbTypes from "../types/dbTypes";
import * as walletTypes from "../types/walletTypes";
import { UpgradeResponse } from "./wxApi";
/**
* Message type information.
*/
export interface MessageMap {
balances: {
request: {};
response: walletTypes.WalletBalance;
};
"dump-db": {
request: {};
response: any;
};
"import-db": {
request: {
dump: object;
};
response: void;
};
ping: {
request: {};
response: void;
};
"reset-db": {
request: {};
response: void;
};
"confirm-pay": {
request: { proposalId: string; sessionId?: string };
response: walletTypes.ConfirmPayResult;
};
"exchange-info": {
request: { baseUrl: string };
response: dbTypes.ExchangeRecord;
};
"get-coins": {
request: { exchangeBaseUrl: string };
response: any;
};
"refresh-coin": {
request: { coinPub: string };
response: any;
};
"get-currencies": {
request: {};
response: dbTypes.CurrencyRecord[];
};
"update-currency": {
request: { currencyRecord: dbTypes.CurrencyRecord };
response: void;
};
"get-exchanges": {
request: {};
response: dbTypes.ExchangeRecord[];
};
"get-reserves": {
request: { exchangeBaseUrl: string };
response: dbTypes.ReserveRecord[];
};
"get-denoms": {
request: { exchangeBaseUrl: string };
response: dbTypes.DenominationRecord[];
};
"check-upgrade": {
request: {};
response: UpgradeResponse;
};
"get-sender-wire-infos": {
request: {};
response: walletTypes.SenderWireInfos;
};
"return-coins": {
request: {};
response: void;
};
"get-purchase-details": {
request: { proposalId: string };
response: walletTypes.PurchaseDetails;
};
"accept-tip": {
request: { talerTipUri: string };
response: void;
};
"get-tip-status": {
request: { talerTipUri: string };
response: walletTypes.TipStatus;
};
"accept-refund": {
request: { refundUrl: string };
response: { contractTermsHash: string; proposalId: string };
};
"abort-failed-payment": {
request: { contractTermsHash: string };
response: void;
};
"benchmark-crypto": {
request: { repetitions: number };
response: walletTypes.BenchmarkResult;
};
"get-withdraw-details": {
request: {
talerWithdrawUri: string;
maybeSelectedExchange: string | undefined;
};
response: walletTypes.WithdrawalDetailsResponse;
};
"accept-withdrawal": {
request: { talerWithdrawUri: string; selectedExchange: string };
response: walletTypes.AcceptWithdrawalResponse;
};
"prepare-pay": {
request: { talerPayUri: string };
response: walletTypes.PreparePayResult;
};
"get-diagnostics": {
request: {};
response: walletTypes.WalletDiagnostics;
};
"set-extended-permissions": {
request: { value: boolean };
response: walletTypes.ExtendedPermissionsResponse;
};
"get-extended-permissions": {
request: {};
response: walletTypes.ExtendedPermissionsResponse;
};
}
/**
* String literal types for messages.
*/
export type MessageType = keyof MessageMap;

View File

@ -23,20 +23,17 @@
import * as i18n from "../i18n"; import * as i18n from "../i18n";
import { WithdrawalDetailsResponse } from "../../types/walletTypes";
import { WithdrawDetailView, renderAmount } from "../renderHtml"; import { WithdrawDetailView, renderAmount } from "../renderHtml";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { import {
getWithdrawDetails,
acceptWithdrawal, acceptWithdrawal,
onUpdateNotification, onUpdateNotification,
} from "../wxApi"; } from "../wxApi";
function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element { function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
const [details, setDetails] = useState< const [details, setDetails] = useState<
WithdrawalDetailsResponse | undefined any | undefined
>(); >();
const [selectedExchange, setSelectedExchange] = useState< const [selectedExchange, setSelectedExchange] = useState<
string | undefined string | undefined
@ -57,24 +54,25 @@ function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
useEffect(() => { useEffect(() => {
const fetchData = async (): Promise<void> => { const fetchData = async (): Promise<void> => {
console.log("getting from", talerWithdrawUri); // FIXME: re-implement with new API
let d: WithdrawalDetailsResponse | undefined = undefined; // console.log("getting from", talerWithdrawUri);
try { // let d: WithdrawalDetailsResponse | undefined = undefined;
d = await getWithdrawDetails(talerWithdrawUri, selectedExchange); // try {
} catch (e) { // d = await getWithdrawDetails(talerWithdrawUri, selectedExchange);
console.error( // } catch (e) {
`error getting withdraw details for uri ${talerWithdrawUri}, exchange ${selectedExchange}`, // console.error(
e, // `error getting withdraw details for uri ${talerWithdrawUri}, exchange ${selectedExchange}`,
); // e,
setErrMsg(e.message); // );
return; // setErrMsg(e.message);
} // return;
console.log("got withdrawDetails", d); // }
if (!selectedExchange && d.bankWithdrawDetails.suggestedExchange) { // console.log("got withdrawDetails", d);
console.log("setting selected exchange"); // if (!selectedExchange && d.bankWithdrawDetails.suggestedExchange) {
setSelectedExchange(d.bankWithdrawDetails.suggestedExchange); // console.log("setting selected exchange");
} // setSelectedExchange(d.bankWithdrawDetails.suggestedExchange);
setDetails(d); // }
// setDetails(d);
}; };
fetchData(); fetchData();
}, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter]); }, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter]);

View File

@ -37,14 +37,11 @@ import {
WalletBalance, WalletBalance,
PurchaseDetails, PurchaseDetails,
WalletDiagnostics, WalletDiagnostics,
WithdrawalDetailsResponse,
PreparePayResult, PreparePayResult,
AcceptWithdrawalResponse, AcceptWithdrawalResponse,
ExtendedPermissionsResponse, ExtendedPermissionsResponse,
} from "../types/walletTypes"; } from "../types/walletTypes";
import { MessageMap, MessageType } from "./messages";
/** /**
* Response with information about available version upgrades. * Response with information about available version upgrades.
*/ */
@ -77,11 +74,11 @@ export class WalletApiError extends Error {
} }
} }
async function callBackend<T extends MessageType>( async function callBackend(
type: T, type: string,
detail: MessageMap[T]["request"], detail: any,
): Promise<MessageMap[T]["response"]> { ): Promise<any> {
return new Promise<MessageMap[T]["response"]>((resolve, reject) => { return new Promise<any>((resolve, reject) => {
chrome.runtime.sendMessage({ type, detail }, (resp) => { chrome.runtime.sendMessage({ type, detail }, (resp) => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.log("Error calling backend"); console.log("Error calling backend");
@ -206,7 +203,7 @@ export function getSenderWireInfos(): Promise<SenderWireInfos> {
export function returnCoins(args: { export function returnCoins(args: {
amount: AmountJson; amount: AmountJson;
exchange: string; exchange: string;
senderWire: object; senderWire: string;
}): Promise<void> { }): Promise<void> {
return callBackend("return-coins", args); return callBackend("return-coins", args);
} }
@ -258,19 +255,6 @@ export function benchmarkCrypto(repetitions: number): Promise<BenchmarkResult> {
return callBackend("benchmark-crypto", { repetitions }); return callBackend("benchmark-crypto", { repetitions });
} }
/**
* Get details about a withdraw operation.
*/
export function getWithdrawDetails(
talerWithdrawUri: string,
maybeSelectedExchange: string | undefined,
): Promise<WithdrawalDetailsResponse> {
return callBackend("get-withdraw-details", {
talerWithdrawUri,
maybeSelectedExchange,
});
}
/** /**
* Get details about a pay operation. * Get details about a pay operation.
*/ */

View File

@ -35,7 +35,6 @@ import { OpenedPromise, openPromise } from "../util/promiseUtils";
import { classifyTalerUri, TalerUriType } from "../util/taleruri"; import { classifyTalerUri, TalerUriType } from "../util/taleruri";
import { Wallet } from "../wallet"; import { Wallet } from "../wallet";
import { isFirefox, getPermissionsApi } from "./compat"; import { isFirefox, getPermissionsApi } from "./compat";
import { MessageType } from "./messages";
import * as wxApi from "./wxApi"; import * as wxApi from "./wxApi";
import MessageSender = chrome.runtime.MessageSender; import MessageSender = chrome.runtime.MessageSender;
import { Database } from "../util/query"; import { Database } from "../util/query";
@ -62,19 +61,9 @@ const notificationPorts: chrome.runtime.Port[] = [];
async function handleMessage( async function handleMessage(
sender: MessageSender, sender: MessageSender,
type: MessageType, type: string,
detail: any, detail: any,
): Promise<any> { ): Promise<any> {
function assertNotFound(t: never): never {
console.error(`Request type ${t as string} unknown`);
console.error(`Request detail was ${detail}`);
return {
error: {
message: `request type ${t as string} unknown`,
requestType: type,
},
} as never;
}
function needsWallet(): Wallet { function needsWallet(): Wallet {
if (!currentWallet) { if (!currentWallet) {
throw NeedsWallet; throw NeedsWallet;
@ -204,12 +193,6 @@ async function handleMessage(
} }
return needsWallet().benchmarkCrypto(detail.repetitions); return needsWallet().benchmarkCrypto(detail.repetitions);
} }
case "get-withdraw-details": {
return needsWallet().getWithdrawDetailsForUri(
detail.talerWithdrawUri,
detail.maybeSelectedExchange,
);
}
case "accept-withdrawal": { case "accept-withdrawal": {
return needsWallet().acceptWithdrawal( return needsWallet().acceptWithdrawal(
detail.talerWithdrawUri, detail.talerWithdrawUri,
@ -280,9 +263,14 @@ async function handleMessage(
return { newValue: res }; return { newValue: res };
} }
default: default:
// Exhaustiveness check. console.error(`Request type ${type} unknown`);
// See https://www.typescriptlang.org/docs/handbook/advanced-types.html console.error(`Request detail was ${detail}`);
return assertNotFound(type); return {
error: {
message: `request type ${type} unknown`,
requestType: type,
},
};
} }
} }
@ -354,7 +342,7 @@ function makeSyncWalletRedirect(
tabId: number, tabId: number,
oldUrl: string, oldUrl: string,
params?: { [name: string]: string | undefined }, params?: { [name: string]: string | undefined },
): object { ): Record<string, unknown> {
const innerUrl = new URL(chrome.extension.getURL("/" + url)); const innerUrl = new URL(chrome.extension.getURL("/" + url));
if (params) { if (params) {
for (const key in params) { for (const key in params) {