wallet-core: simplify exchanges list response

This commit is contained in:
Florian Dold 2022-10-15 12:59:26 +02:00
parent e075134ffc
commit bd88dcebbc
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
11 changed files with 78 additions and 75 deletions

View File

@ -900,11 +900,18 @@ export interface ExchangeFullDetails {
globalFees: FeeDescription[];
}
export enum ExchangeTosStatus {
New = "new",
Accepted = "accepted",
Changed = "changed",
NotFound = "not-found",
}
export interface ExchangeListItem {
exchangeBaseUrl: string;
currency: string;
paytoUris: string[];
tos: ExchangeTosStatusDetails;
tosStatus: ExchangeTosStatus;
}
const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> =>
@ -976,7 +983,7 @@ export const codecForExchangeListItem = (): Codec<ExchangeListItem> =>
.property("currency", codecForString())
.property("exchangeBaseUrl", codecForString())
.property("paytoUris", codecForList(codecForString()))
.property("tos", codecForExchangeTos())
.property("tosStatus", codecForAny())
.build("ExchangeListItem");
export const codecForExchangesListResponse = (): Codec<ExchangesListResponse> =>
@ -1146,6 +1153,8 @@ export interface GetExchangeTosResult {
* Accepted content type
*/
contentType: string;
tosStatus: ExchangeTosStatus;
}
export interface TestPayArgs {

View File

@ -22,6 +22,7 @@ import {
Amounts,
CoinRefreshRequest,
CoinStatus,
ExchangeTosStatus,
j2s,
Logger,
RefreshReason,
@ -31,7 +32,7 @@ import {
TransactionIdStr,
TransactionType,
} from "@gnu-taler/taler-util";
import { WalletStoresV1, CoinRecord } from "../db.js";
import { WalletStoresV1, CoinRecord, ExchangeDetailsRecord } from "../db.js";
import { makeErrorDetail, TalerError } from "../errors.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
@ -307,3 +308,15 @@ export function makeTombstoneId(
): TombstoneIdStr {
return `tmb:${type}:${args.map((x) => encodeURIComponent(x)).join(":")}`;
}
export function getExchangeTosStatus(
exchangeDetails: ExchangeDetailsRecord,
): ExchangeTosStatus {
if (!exchangeDetails.tosAccepted) {
return ExchangeTosStatus.New;
}
if (exchangeDetails.tosAccepted?.etag == exchangeDetails.tosCurrentEtag) {
return ExchangeTosStatus.Accepted;
}
return ExchangeTosStatus.Changed;
}

View File

@ -83,6 +83,7 @@ import {
} from "../errors.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import {
getExchangeTosStatus,
makeCoinAvailable,
runOperationWithErrorReporting,
} from "../operations/common.js";
@ -1359,26 +1360,20 @@ export async function getWithdrawalDetailsForUri(
.runReadOnly(async (tx) => {
const exchangeRecords = await tx.exchanges.iter().toArray();
for (const r of exchangeRecords) {
const details = await ws.exchangeOps.getExchangeDetails(tx, r.baseUrl);
const exchangeDetails = await ws.exchangeOps.getExchangeDetails(tx, r.baseUrl);
const denominations = await tx.denominations.indexes.byExchangeBaseUrl
.iter(r.baseUrl)
.toArray();
if (details && denominations) {
if (exchangeDetails && denominations) {
const tosRecord = await tx.exchangeTos.get([
details.exchangeBaseUrl,
details.tosCurrentEtag,
exchangeDetails.exchangeBaseUrl,
exchangeDetails.tosCurrentEtag,
]);
exchanges.push({
exchangeBaseUrl: details.exchangeBaseUrl,
currency: details.currency,
// FIXME: We probably don't want to include the full ToS here!
tos: {
acceptedVersion: details.tosAccepted?.etag,
currentVersion: details.tosCurrentEtag,
contentType: tosRecord?.termsOfServiceContentType ?? "",
content: tosRecord?.termsOfServiceText ?? "",
},
paytoUris: details.wireInfo.accounts.map((x) => x.payto_uri),
exchangeBaseUrl: exchangeDetails.exchangeBaseUrl,
currency: exchangeDetails.currency,
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
tosStatus: getExchangeTosStatus(exchangeDetails),
});
}
}

View File

@ -146,7 +146,7 @@ import {
} from "./operations/backup/index.js";
import { setWalletDeviceId } from "./operations/backup/state.js";
import { getBalances } from "./operations/balance.js";
import { runOperationWithErrorReporting } from "./operations/common.js";
import { getExchangeTosStatus, runOperationWithErrorReporting } from "./operations/common.js";
import {
createDepositGroup,
getFeeForDeposit,
@ -503,6 +503,7 @@ async function getExchangeTos(
currentEtag,
content,
contentType,
tosStatus: getExchangeTosStatus(exchangeDetails),
};
}
@ -519,6 +520,7 @@ async function getExchangeTos(
currentEtag,
content,
contentType,
tosStatus: getExchangeTosStatus(exchangeDetails),
};
}
@ -529,6 +531,7 @@ async function getExchangeTos(
currentEtag: tosDownload.tosEtag,
content: tosDownload.tosText,
contentType: tosDownload.tosContentType,
tosStatus: getExchangeTosStatus(exchangeDetails),
};
}
@ -665,7 +668,7 @@ async function getExchanges(
exchanges.push({
exchangeBaseUrl: r.baseUrl,
currency,
tos,
tosStatus: getExchangeTosStatus(exchangeDetails),
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
});
}

View File

@ -14,7 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { GetExchangeTosResult } from "@gnu-taler/taler-util";
import { ExchangeTosStatus, GetExchangeTosResult } from "@gnu-taler/taler-util";
export function buildTermsOfServiceState(
tos: GetExchangeTosResult,
@ -24,26 +24,7 @@ export function buildTermsOfServiceState(
tos.content,
);
const status: TermsStatus = buildTermsOfServiceStatus(
tos.content,
tos.acceptedEtag,
tos.currentEtag,
);
return { content, status, version: tos.currentEtag };
}
export function buildTermsOfServiceStatus(
content: string | undefined,
acceptedVersion: string | undefined,
currentVersion: string | undefined,
): TermsStatus {
return !content
? "notfound"
: !acceptedVersion
? "new"
: acceptedVersion !== currentVersion
? "changed"
: "accepted";
return { content, status: tos.tosStatus, version: tos.currentEtag };
}
function parseTermsOfServiceContent(
@ -91,12 +72,10 @@ function parseTermsOfServiceContent(
export type TermsState = {
content: TermsDocument | undefined;
status: TermsStatus;
status: ExchangeTosStatus;
version: string;
};
type TermsStatus = "new" | "accepted" | "changed" | "notfound";
export type TermsDocument =
| TermsDocumentXml
| TermsDocumentHtml

View File

@ -29,6 +29,7 @@ import {
import { ExchangeXmlTos } from "../../components/ExchangeToS.js";
import { ToggleHandler } from "../../mui/handlers.js";
import { Button } from "../../mui/Button.js";
import { ExchangeTosStatus } from "@gnu-taler/taler-util";
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
const { i18n } = useTranslationContext();
@ -100,7 +101,7 @@ export function ShowButtonsNonAcceptedTosView({
if (!ableToReviewTermsOfService) {
return (
<Fragment>
{terms.status === "notfound" && (
{terms.status === ExchangeTosStatus.NotFound && (
<section style={{ justifyContent: "space-around", display: "flex" }}>
<WarningText>
<i18n.Translate>
@ -115,7 +116,7 @@ export function ShowButtonsNonAcceptedTosView({
return (
<Fragment>
{terms.status === "notfound" && (
{terms.status === ExchangeTosStatus.NotFound && (
<section style={{ justifyContent: "space-around", display: "flex" }}>
<WarningText>
<i18n.Translate>
@ -163,7 +164,7 @@ export function ShowTosContentView({
return (
<Fragment>
{terms.status !== "notfound" && !terms.content && (
{terms.status !== ExchangeTosStatus.NotFound && !terms.content && (
<section style={{ justifyContent: "space-around", display: "flex" }}>
<WarningBox>
<i18n.Translate>
@ -204,7 +205,7 @@ export function ShowTosContentView({
</LinkSuccess>
</section>
)}
{termsAccepted && terms.status !== "notfound" && (
{termsAccepted && terms.status !== ExchangeTosStatus.NotFound && (
<section style={{ justifyContent: "space-around", display: "flex" }}>
<CheckboxOutlined
name="terms"

View File

@ -15,7 +15,12 @@
*/
/* eslint-disable react-hooks/rules-of-hooks */
import { AmountJson, Amounts, ExchangeListItem } from "@gnu-taler/taler-util";
import {
AmountJson,
Amounts,
ExchangeListItem,
ExchangeTosStatus,
} from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core";
import { useState } from "preact/hooks";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@ -173,10 +178,8 @@ function exchangeSelectionState(
const [ageRestricted, setAgeRestricted] = useState(0);
const currentExchange = selectedExchange.selected;
const tosNeedToBeAccepted =
!currentExchange.tos.acceptedVersion ||
currentExchange.tos.currentVersion !==
currentExchange.tos.acceptedVersion;
currentExchange.tosStatus == ExchangeTosStatus.New ||
currentExchange.tosStatus == ExchangeTosStatus.Changed;
/**
* With the exchange and amount, ask the wallet the information
* about the withdrawal

View File

@ -22,6 +22,7 @@
import {
Amounts,
ExchangeFullDetails,
ExchangeTosStatus,
GetExchangeTosResult,
} from "@gnu-taler/taler-util";
import { expect } from "chai";
@ -169,6 +170,7 @@ describe("Withdraw CTA states", () => {
content: "just accept",
acceptedEtag: "v1",
currentEtag: "v1",
tosStatus: ExchangeTosStatus.Accepted,
}),
} as any,
),
@ -254,6 +256,7 @@ describe("Withdraw CTA states", () => {
content: "just accept",
acceptedEtag: "v1",
currentEtag: "v2",
tosStatus: ExchangeTosStatus.Changed,
}),
setExchangeTosAccepted: async () => ({}),
} as any,

View File

@ -37,6 +37,7 @@ import editIcon from "../../svg/edit_24px.svg";
import { ExchangeDetails, WithdrawDetails } from "../../wallet/Transaction.js";
import { TermsOfService } from "../../components/TermsOfService/index.js";
import { State } from "./index.js";
import { ExchangeTosStatus } from "@gnu-taler/taler-util";
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
const { i18n } = useTranslationContext();
@ -65,8 +66,7 @@ export function LoadingInfoView({ error }: State.LoadingInfoError): VNode {
export function SuccessView(state: State.Success): VNode {
const { i18n } = useTranslationContext();
const currentTosVersionIsAccepted =
state.currentExchange.tos.acceptedVersion ===
state.currentExchange.tos.currentVersion;
state.currentExchange.tosStatus === ExchangeTosStatus.Accepted;
return (
<WalletAction>
<LogoHeader />

View File

@ -17,6 +17,7 @@
import {
Amounts,
CoinDumpJson,
CoinStatus,
ExchangeListItem,
NotificationType,
} from "@gnu-taler/taler-util";
@ -86,7 +87,7 @@ type CoinsInfo = CoinDumpJson["coins"];
type CalculatedCoinfInfo = {
ageKeysCount: number | undefined;
denom_value: number;
remain_value: number;
//remain_value: number;
status: string;
from_refresh: boolean;
id: string;
@ -143,10 +144,10 @@ export function View({
prev[cur.exchange_base_url].push({
ageKeysCount: cur.ageCommitmentProof?.proof.privateKeys.length,
denom_value: parseFloat(Amounts.stringifyValue(denom)),
remain_value: parseFloat(
Amounts.stringifyValue(Amounts.parseOrThrow(cur.remaining_value)),
),
status: cur.coin_suspended ? "suspended" : "ok",
// remain_value: parseFloat(
// Amounts.stringifyValue(Amounts.parseOrThrow(cur.remaining_value)),
// ),
status: cur.coin_status,
from_refresh: cur.refresh_parent_coin_pub !== undefined,
id: cur.coin_pub,
});
@ -254,8 +255,8 @@ export function View({
const coins = allcoins.reduce(
(prev, cur) => {
if (cur.remain_value > 0) prev.usable.push(cur);
if (cur.remain_value === 0) prev.spent.push(cur);
if (cur.status === CoinStatus.Fresh) prev.usable.push(cur);
if (cur.status === CoinStatus.Dormant) prev.spent.push(cur);
return prev;
},
{
@ -356,7 +357,6 @@ function ShowAllCoins({
<tr key={idx}>
<td>{c.id.substring(0, 5)}</td>
<td>{c.denom_value}</td>
<td>{c.remain_value}</td>
<td>{c.status}</td>
<td>{c.from_refresh ? "true" : "false"}</td>
<td>{String(c.ageKeysCount)}</td>
@ -396,7 +396,6 @@ function ShowAllCoins({
<tr key={idx}>
<td>{c.id.substring(0, 5)}</td>
<td>{c.denom_value}</td>
<td>{c.remain_value}</td>
<td>{c.status}</td>
<td>{c.from_refresh ? "true" : "false"}</td>
</tr>

View File

@ -14,7 +14,11 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { ExchangeListItem, WalletCoreVersion } from "@gnu-taler/taler-util";
import {
ExchangeListItem,
ExchangeTosStatus,
WalletCoreVersion,
} from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact";
import { Checkbox } from "../components/Checkbox.js";
import { ErrorTalerOperation } from "../components/ErrorTalerOperation.js";
@ -36,7 +40,6 @@ import { useBackupDeviceName } from "../hooks/useBackupDeviceName.js";
import { useAutoOpenPermissions } from "../hooks/useAutoOpenPermissions.js";
import { ToggleHandler } from "../mui/handlers.js";
import { Pages } from "../NavigationBar.js";
import { buildTermsOfServiceStatus } from "../components/TermsOfService/utils.js";
import * as wxApi from "../wxApi.js";
import { platform } from "../platform/api.js";
import { useClipboardPermissions } from "../hooks/useClipboardPermissions.js";
@ -181,26 +184,21 @@ export function SettingsView({
<tbody>
{knownExchanges.map((e, idx) => {
function Status(): VNode {
const status = buildTermsOfServiceStatus(
e.tos.content,
e.tos.acceptedVersion,
e.tos.currentVersion,
);
switch (status) {
case "accepted":
switch (e.tosStatus) {
case ExchangeTosStatus.Accepted:
return (
<SuccessText>
<i18n.Translate>ok</i18n.Translate>
</SuccessText>
);
case "changed":
case ExchangeTosStatus.Changed:
return (
<WarningText>
<i18n.Translate>changed</i18n.Translate>
</WarningText>
);
case "new":
case "notfound":
case ExchangeTosStatus.New:
case ExchangeTosStatus.NotFound:
return (
<DestructiveText>
<i18n.Translate>not accepted</i18n.Translate>