wallet-core: make listExchanges return less data

Since the webextension UI depends on the full response, we have a
temporary listExchangesDetailled request.
See https://bugs.taler.net/n/7323 for details.
This commit is contained in:
Florian Dold 2022-09-05 21:09:28 +02:00
parent 0f57f48f84
commit 3f5a76751b
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
16 changed files with 141 additions and 40 deletions

View File

@ -571,6 +571,11 @@ export interface DepositInfo {
export interface ExchangesListRespose { export interface ExchangesListRespose {
exchanges: ExchangeListItem[]; exchanges: ExchangeListItem[];
} }
export interface ExchangeDetailledListRespose {
exchanges: ExchangeFullDetailsListItem[];
}
export interface WalletCoreVersion { export interface WalletCoreVersion {
hash: string | undefined; hash: string | undefined;
version: string; version: string;
@ -578,6 +583,7 @@ export interface WalletCoreVersion {
merchant: string; merchant: string;
bank: string; bank: string;
} }
export interface KnownBankAccounts { export interface KnownBankAccounts {
accounts: { [payto: string]: PaytoUri }; accounts: { [payto: string]: PaytoUri };
} }
@ -727,7 +733,7 @@ export interface DenominationInfo {
stampExpireDeposit: TalerProtocolTimestamp; stampExpireDeposit: TalerProtocolTimestamp;
} }
export interface ExchangeListItem { export interface ExchangeFullDetailsListItem {
exchangeBaseUrl: string; exchangeBaseUrl: string;
currency: string; currency: string;
paytoUris: string[]; paytoUris: string[];
@ -737,6 +743,13 @@ export interface ExchangeListItem {
denominations: DenominationInfo[]; denominations: DenominationInfo[];
} }
export interface ExchangeListItem {
exchangeBaseUrl: string;
currency: string;
paytoUris: string[];
tos: ExchangeTos;
}
const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> => const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> =>
buildCodecForObject<AuditorDenomSig>() buildCodecForObject<AuditorDenomSig>()
.property("denom_pub_h", codecForString()) .property("denom_pub_h", codecForString())
@ -758,8 +771,9 @@ const codecForExchangeTos = (): Codec<ExchangeTos> =>
.property("content", codecOptional(codecForString())) .property("content", codecOptional(codecForString()))
.build("ExchangeTos"); .build("ExchangeTos");
export const codecForExchangeListItem = (): Codec<ExchangeListItem> => export const codecForExchangeFullDetailsListItem =
buildCodecForObject<ExchangeListItem>() (): Codec<ExchangeFullDetailsListItem> =>
buildCodecForObject<ExchangeFullDetailsListItem>()
.property("currency", codecForString()) .property("currency", codecForString())
.property("exchangeBaseUrl", codecForString()) .property("exchangeBaseUrl", codecForString())
.property("paytoUris", codecForList(codecForString())) .property("paytoUris", codecForList(codecForString()))
@ -769,9 +783,17 @@ export const codecForExchangeListItem = (): Codec<ExchangeListItem> =>
.property("denominations", codecForList(codecForDenominationInfo())) .property("denominations", codecForList(codecForDenominationInfo()))
.build("ExchangeListItem"); .build("ExchangeListItem");
export const codecForExchangeListItem = (): Codec<ExchangeListItem> =>
buildCodecForObject<ExchangeListItem>()
.property("currency", codecForString())
.property("exchangeBaseUrl", codecForString())
.property("paytoUris", codecForList(codecForString()))
.property("tos", codecForExchangeTos())
.build("ExchangeListItem");
export const codecForExchangesListResponse = (): Codec<ExchangesListRespose> => export const codecForExchangesListResponse = (): Codec<ExchangesListRespose> =>
buildCodecForObject<ExchangesListRespose>() buildCodecForObject<ExchangesListRespose>()
.property("exchanges", codecForList(codecForExchangeListItem())) .property("exchanges", codecForList(codecForExchangeFullDetailsListItem()))
.build("ExchangesListRespose"); .build("ExchangesListRespose");
export interface AcceptManualWithdrawalResult { export interface AcceptManualWithdrawalResult {

View File

@ -1436,9 +1436,6 @@ export async function getWithdrawalDetailsForUri(
content: details.termsOfServiceText, content: details.termsOfServiceText,
}, },
paytoUris: details.wireInfo.accounts.map((x) => x.payto_uri), paytoUris: details.wireInfo.accounts.map((x) => x.payto_uri),
auditors: details.auditors,
wireInfo: details.wireInfo,
denominations: denominations,
}); });
} }
} }

View File

@ -72,7 +72,7 @@ import {
Duration, Duration,
durationFromSpec, durationFromSpec,
durationMin, durationMin,
ExchangeListItem, ExchangeFullDetailsListItem,
ExchangesListRespose, ExchangesListRespose,
GetExchangeTosResult, GetExchangeTosResult,
j2s, j2s,
@ -87,6 +87,7 @@ import {
URL, URL,
WalletNotification, WalletNotification,
WalletCoreVersion, WalletCoreVersion,
ExchangeListItem,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js"; import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
import { import {
@ -207,7 +208,11 @@ import {
} from "./util/promiseUtils.js"; } from "./util/promiseUtils.js";
import { DbAccess, GetReadWriteAccess } from "./util/query.js"; import { DbAccess, GetReadWriteAccess } from "./util/query.js";
import { TimerAPI, TimerGroup } from "./util/timer.js"; import { TimerAPI, TimerGroup } from "./util/timer.js";
import { WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, WALLET_EXCHANGE_PROTOCOL_VERSION, WALLET_MERCHANT_PROTOCOL_VERSION } from "./versions.js"; import {
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
WALLET_EXCHANGE_PROTOCOL_VERSION,
WALLET_MERCHANT_PROTOCOL_VERSION,
} from "./versions.js";
import { WalletCoreApiClient } from "./wallet-api-types.js"; import { WalletCoreApiClient } from "./wallet-api-types.js";
const builtinAuditors: AuditorTrustRecord[] = [ const builtinAuditors: AuditorTrustRecord[] = [
@ -592,6 +597,53 @@ async function getExchanges(
continue; continue;
} }
exchanges.push({
exchangeBaseUrl: r.baseUrl,
currency,
tos: {
acceptedVersion: exchangeDetails.termsOfServiceAcceptedEtag,
currentVersion: exchangeDetails.termsOfServiceLastEtag,
contentType: exchangeDetails.termsOfServiceContentType,
content: exchangeDetails.termsOfServiceText,
},
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
});
}
});
return { exchanges };
}
async function getExchangesDetailled(
ws: InternalWalletState,
): Promise<ExchangesListRespose> {
const exchanges: ExchangeFullDetailsListItem[] = [];
await ws.db
.mktx((x) => ({
exchanges: x.exchanges,
exchangeDetails: x.exchangeDetails,
denominations: x.denominations,
}))
.runReadOnly(async (tx) => {
const exchangeRecords = await tx.exchanges.iter().toArray();
for (const r of exchangeRecords) {
const dp = r.detailsPointer;
if (!dp) {
continue;
}
const { currency } = dp;
const exchangeDetails = await getExchangeDetails(tx, r.baseUrl);
if (!exchangeDetails) {
continue;
}
const denominations = await tx.denominations.indexes.byExchangeBaseUrl
.iter(r.baseUrl)
.toArray();
if (!denominations) {
continue;
}
exchanges.push({ exchanges.push({
exchangeBaseUrl: r.baseUrl, exchangeBaseUrl: r.baseUrl,
currency, currency,
@ -782,6 +834,9 @@ async function dispatchRequestInternal(
case "listExchanges": { case "listExchanges": {
return await getExchanges(ws); return await getExchanges(ws);
} }
case "listExchangesDetailled": {
return await getExchangesDetailled(ws);
}
case "listKnownBankAccounts": { case "listKnownBankAccounts": {
const req = codecForListKnownBankAccounts().decode(payload); const req = codecForListKnownBankAccounts().decode(payload);
return await listKnownBankAccounts(ws, req.currency); return await listKnownBankAccounts(ws, req.currency);
@ -790,6 +845,7 @@ async function dispatchRequestInternal(
const req = codecForGetWithdrawalDetailsForUri().decode(payload); const req = codecForGetWithdrawalDetailsForUri().decode(payload);
return await getWithdrawalDetailsForUri(ws, req.talerWithdrawUri); return await getWithdrawalDetailsForUri(ws, req.talerWithdrawUri);
} }
case "getExchangeWithdrawalInfo": { case "getExchangeWithdrawalInfo": {
const req = codecForGetExchangeWithdrawalInfo().decode(payload); const req = codecForGetExchangeWithdrawalInfo().decode(payload);
return await getExchangeWithdrawalInfo( return await getExchangeWithdrawalInfo(
@ -1078,7 +1134,7 @@ async function dispatchRequestInternal(
exchange: WALLET_EXCHANGE_PROTOCOL_VERSION, exchange: WALLET_EXCHANGE_PROTOCOL_VERSION,
merchant: WALLET_MERCHANT_PROTOCOL_VERSION, merchant: WALLET_MERCHANT_PROTOCOL_VERSION,
bank: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, bank: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
} };
return version; return version;
} }
} }

View File

@ -30,7 +30,7 @@ export function useComponentState(
const [subject, setSubject] = useState(""); const [subject, setSubject] = useState("");
const [talerUri, setTalerUri] = useState("") const [talerUri, setTalerUri] = useState("")
const hook = useAsyncAsHook(api.listExchanges); const hook = useAsyncAsHook(api.listExchangesDetailled);
const [exchangeIdx, setExchangeIdx] = useState("0") const [exchangeIdx, setExchangeIdx] = useState("0")
const [operationError, setOperationError] = useState<TalerErrorDetail | undefined>(undefined) const [operationError, setOperationError] = useState<TalerErrorDetail | undefined>(undefined)

View File

@ -30,7 +30,7 @@ export function useComponentStateFromParams(
const [ageRestricted, setAgeRestricted] = useState(0); const [ageRestricted, setAgeRestricted] = useState(0);
const exchangeHook = useAsyncAsHook(api.listExchanges); const exchangeHook = useAsyncAsHook(api.listExchangesDetailled);
const exchangeHookDep = const exchangeHookDep =
!exchangeHook || exchangeHook.hasError || !exchangeHook.response !exchangeHook || exchangeHook.hasError || !exchangeHook.response

View File

@ -21,6 +21,7 @@
import { import {
Amounts, Amounts,
ExchangeFullDetailsListItem,
ExchangeListItem, ExchangeListItem,
GetExchangeTosResult, GetExchangeTosResult,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
@ -29,7 +30,7 @@ import { expect } from "chai";
import { mountHook } from "../../test-utils.js"; import { mountHook } from "../../test-utils.js";
import { useComponentStateFromURI } from "./state.js"; import { useComponentStateFromURI } from "./state.js";
const exchanges: ExchangeListItem[] = [ const exchanges: ExchangeFullDetailsListItem[] = [
{ {
currency: "ARS", currency: "ARS",
exchangeBaseUrl: "http://exchange.demo.taler.net", exchangeBaseUrl: "http://exchange.demo.taler.net",

View File

@ -171,7 +171,7 @@ export function SelectCurrency({
}): VNode { }): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const hook = useAsyncAsHook(wxApi.listExchanges); const hook = useAsyncAsHook(wxApi.listExchangesDetailled);
if (!hook) { if (!hook) {
return <Loading />; return <Loading />;

View File

@ -45,7 +45,7 @@ export function DeveloperPage(): VNode {
const response = useAsyncAsHook(async () => { const response = useAsyncAsHook(async () => {
const op = await wxApi.getPendingOperations(); const op = await wxApi.getPendingOperations();
const c = await wxApi.dumpCoins(); const c = await wxApi.dumpCoins();
const ex = await wxApi.listExchanges(); const ex = await wxApi.listExchangesDetailled();
return { return {
operations: op.pendingOperations, operations: op.pendingOperations,
coins: c.coins, coins: c.coins,

View File

@ -36,7 +36,7 @@ export function ExchangeAddPage({ currency, onBack }: Props): VNode {
{ url: string; config: TalerConfigResponse } | undefined { url: string; config: TalerConfigResponse } | undefined
>(undefined); >(undefined);
const knownExchangesResponse = useAsyncAsHook(wxApi.listExchanges); const knownExchangesResponse = useAsyncAsHook(wxApi.listExchangesDetailled);
const knownExchanges = !knownExchangesResponse const knownExchanges = !knownExchangesResponse
? [] ? []
: knownExchangesResponse.hasError : knownExchangesResponse.hasError

View File

@ -14,14 +14,17 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { ExchangeListItem } from "@gnu-taler/taler-util"; import {
ExchangeFullDetailsListItem,
ExchangeListItem,
} from "@gnu-taler/taler-util";
/** /**
* *
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
export const bitcoinExchanges: ExchangeListItem[] = [ export const bitcoinExchanges: ExchangeFullDetailsListItem[] = [
{ {
exchangeBaseUrl: "https://bitcoin1.ice.bfh.ch/", exchangeBaseUrl: "https://bitcoin1.ice.bfh.ch/",
currency: "BITCOINBTC", currency: "BITCOINBTC",
@ -11778,7 +11781,7 @@ export const bitcoinExchanges: ExchangeListItem[] = [
}, },
] as any; ] as any;
export const kudosExchanges: ExchangeListItem[] = [ export const kudosExchanges: ExchangeFullDetailsListItem[] = [
{ {
exchangeBaseUrl: "https://exchange1.demo.taler.net/", exchangeBaseUrl: "https://exchange1.demo.taler.net/",
currency: "KUDOS", currency: "KUDOS",

View File

@ -14,7 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { AbsoluteTime, AmountJson, ExchangeListItem } from "@gnu-taler/taler-util"; import { AbsoluteTime, AmountJson, ExchangeFullDetailsListItem } from "@gnu-taler/taler-util";
import { Loading } from "../../components/Loading.js"; import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js"; import { HookError } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js"; import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js";
@ -52,7 +52,7 @@ export namespace State {
export interface BaseInfo { export interface BaseInfo {
exchanges: SelectFieldHandler; exchanges: SelectFieldHandler;
selected: ExchangeListItem; selected: ExchangeFullDetailsListItem;
nextFeeUpdate: AbsoluteTime; nextFeeUpdate: AbsoluteTime;
error: undefined; error: undefined;
} }

View File

@ -25,7 +25,7 @@ export function useComponentState(
{ onCancel, onSelection, currency }: Props, { onCancel, onSelection, currency }: Props,
api: typeof wxApi, api: typeof wxApi,
): State { ): State {
const hook = useAsyncAsHook(api.listExchanges); const hook = useAsyncAsHook(api.listExchangesDetailled);
const initialValue = 0 const initialValue = 0
const [value, setValue] = useState(String(initialValue)); const [value, setValue] = useState(String(initialValue));

View File

@ -19,7 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm) * @author Sebastian Javier Marchano (sebasjm)
*/ */
import { ExchangeListItem } from "@gnu-taler/taler-util"; import { ExchangeFullDetailsListItem, ExchangeListItem } from "@gnu-taler/taler-util";
import { createExample } from "../../test-utils.js"; import { createExample } from "../../test-utils.js";
import { bitcoinExchanges, kudosExchanges } from "./example.js"; import { bitcoinExchanges, kudosExchanges } from "./example.js";
import { FeeDescription, FeeDescriptionPair, OperationMap } from "./index.js"; import { FeeDescription, FeeDescriptionPair, OperationMap } from "./index.js";
@ -34,7 +34,7 @@ export default {
}; };
function timelineForExchange( function timelineForExchange(
ex: ExchangeListItem, ex: ExchangeFullDetailsListItem,
): OperationMap<FeeDescription[]> { ): OperationMap<FeeDescription[]> {
return { return {
deposit: createDenominationTimeline( deposit: createDenominationTimeline(
@ -61,8 +61,8 @@ function timelineForExchange(
} }
function timelinePairForExchange( function timelinePairForExchange(
ex1: ExchangeListItem, ex1: ExchangeFullDetailsListItem,
ex2: ExchangeListItem, ex2: ExchangeFullDetailsListItem,
): OperationMap<FeeDescriptionPair[]> { ): OperationMap<FeeDescriptionPair[]> {
const om1 = timelineForExchange(ex1); const om1 = timelineForExchange(ex1);
const om2 = timelineForExchange(ex2); const om2 = timelineForExchange(ex2);

View File

@ -50,7 +50,7 @@ export function ManualWithdrawPage({ amount, onCancel }: Props): VNode {
>(undefined); >(undefined);
const [error, setError] = useState<string | undefined>(undefined); const [error, setError] = useState<string | undefined>(undefined);
const state = useAsyncAsHook(wxApi.listExchanges); const state = useAsyncAsHook(wxApi.listExchangesDetailled);
useEffect(() => { useEffect(() => {
return wxApi.onUpdateNotification([NotificationType.ExchangeAdded], () => { return wxApi.onUpdateNotification([NotificationType.ExchangeAdded], () => {
state?.retry(); state?.retry();

View File

@ -49,7 +49,7 @@ export function SettingsPage(): VNode {
const webex = platform.getWalletWebExVersion(); const webex = platform.getWalletWebExVersion();
const exchangesHook = useAsyncAsHook(async () => { const exchangesHook = useAsyncAsHook(async () => {
const list = await wxApi.listExchanges(); const list = await wxApi.listExchangesDetailled();
const version = await wxApi.getVersion(); const version = await wxApi.getVersion();
return { exchanges: list.exchanges, version }; return { exchanges: list.exchanges, version };
}); });

View File

@ -67,6 +67,7 @@ import {
WalletDiagnostics, WalletDiagnostics,
WalletCoreVersion, WalletCoreVersion,
WithdrawUriInfoResponse, WithdrawUriInfoResponse,
ExchangeDetailledListRespose,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { import {
AddBackupProviderRequest, AddBackupProviderRequest,
@ -78,7 +79,11 @@ import {
} from "@gnu-taler/taler-wallet-core"; } from "@gnu-taler/taler-wallet-core";
import type { DepositGroupFees } from "@gnu-taler/taler-wallet-core/src/operations/deposits"; import type { DepositGroupFees } from "@gnu-taler/taler-wallet-core/src/operations/deposits";
import type { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw"; import type { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw";
import { platform, MessageFromBackend, WalletWebExVersion } from "./platform/api.js"; import {
platform,
MessageFromBackend,
WalletWebExVersion,
} from "./platform/api.js";
/** /**
* *
@ -202,8 +207,9 @@ export function getBalance(): Promise<BalancesResponse> {
return callBackend("getBalances", {}); return callBackend("getBalances", {});
} }
export function getContractTermsDetails(
export function getContractTermsDetails(proposalId: string): Promise<WalletContractData> { proposalId: string,
): Promise<WalletContractData> {
return callBackend("getContractTermsDetails", { proposalId }); return callBackend("getContractTermsDetails", { proposalId });
} }
@ -250,9 +256,15 @@ export function listKnownCurrencies(): Promise<ListOfKnownCurrencies> {
export function listExchanges(): Promise<ExchangesListRespose> { export function listExchanges(): Promise<ExchangesListRespose> {
return callBackend("listExchanges", {}); return callBackend("listExchanges", {});
} }
export function listExchangesDetailled(): Promise<ExchangeDetailledListRespose> {
return callBackend("listExchangesDetailled", {});
}
export function getVersion(): Promise<WalletCoreVersion> { export function getVersion(): Promise<WalletCoreVersion> {
return callBackend("getVersion", {}); return callBackend("getVersion", {});
} }
export function listKnownBankAccounts( export function listKnownBankAccounts(
currency?: string, currency?: string,
): Promise<KnownBankAccounts> { ): Promise<KnownBankAccounts> {
@ -487,23 +499,33 @@ export function onUpdateNotification(
return platform.listenToWalletBackground(onNewMessage); return platform.listenToWalletBackground(onNewMessage);
} }
export function initiatePeerPushPayment(req: InitiatePeerPushPaymentRequest): Promise<InitiatePeerPushPaymentResponse> { export function initiatePeerPushPayment(
req: InitiatePeerPushPaymentRequest,
): Promise<InitiatePeerPushPaymentResponse> {
return callBackend("initiatePeerPushPayment", req); return callBackend("initiatePeerPushPayment", req);
} }
export function checkPeerPushPayment(req: CheckPeerPushPaymentRequest): Promise<CheckPeerPushPaymentResponse> { export function checkPeerPushPayment(
req: CheckPeerPushPaymentRequest,
): Promise<CheckPeerPushPaymentResponse> {
return callBackend("checkPeerPushPayment", req); return callBackend("checkPeerPushPayment", req);
} }
export function acceptPeerPushPayment(req: AcceptPeerPushPaymentRequest): Promise<void> { export function acceptPeerPushPayment(
req: AcceptPeerPushPaymentRequest,
): Promise<void> {
return callBackend("acceptPeerPushPayment", req); return callBackend("acceptPeerPushPayment", req);
} }
export function initiatePeerPullPayment(req: InitiatePeerPullPaymentRequest): Promise<InitiatePeerPullPaymentResponse> { export function initiatePeerPullPayment(
req: InitiatePeerPullPaymentRequest,
): Promise<InitiatePeerPullPaymentResponse> {
return callBackend("initiatePeerPullPayment", req); return callBackend("initiatePeerPullPayment", req);
} }
export function checkPeerPullPayment(req: CheckPeerPullPaymentRequest): Promise<CheckPeerPullPaymentResponse> { export function checkPeerPullPayment(
req: CheckPeerPullPaymentRequest,
): Promise<CheckPeerPullPaymentResponse> {
return callBackend("checkPeerPullPayment", req); return callBackend("checkPeerPullPayment", req);
} }
export function acceptPeerPullPayment(req: AcceptPeerPullPaymentRequest): Promise<void> { export function acceptPeerPullPayment(
req: AcceptPeerPullPaymentRequest,
): Promise<void> {
return callBackend("acceptPeerPullPayment", req); return callBackend("acceptPeerPullPayment", req);
} }