wallet-core: pull out ToS into separate object store
This commit is contained in:
parent
f1cba79c65
commit
a57fcb144d
@ -1165,8 +1165,6 @@ export interface BackupExchange {
|
|||||||
|
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
protocol_version_range: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time when the pointer to the exchange details
|
* Time when the pointer to the exchange details
|
||||||
* was last updated.
|
* was last updated.
|
||||||
|
@ -620,7 +620,7 @@ export interface KnownBankAccounts {
|
|||||||
accounts: KnownBankAccountsInfo[];
|
accounts: KnownBankAccountsInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExchangeTos {
|
export interface ExchangeTosStatusDetails {
|
||||||
acceptedVersion?: string;
|
acceptedVersion?: string;
|
||||||
currentVersion?: string;
|
currentVersion?: string;
|
||||||
contentType?: string;
|
contentType?: string;
|
||||||
@ -805,7 +805,7 @@ export interface ExchangeFullDetails {
|
|||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
currency: string;
|
currency: string;
|
||||||
paytoUris: string[];
|
paytoUris: string[];
|
||||||
tos: ExchangeTos;
|
tos: ExchangeTosStatusDetails;
|
||||||
auditors: ExchangeAuditor[];
|
auditors: ExchangeAuditor[];
|
||||||
wireInfo: WireInfo;
|
wireInfo: WireInfo;
|
||||||
denomFees: DenomOperationMap<FeeDescription[]>;
|
denomFees: DenomOperationMap<FeeDescription[]>;
|
||||||
@ -817,7 +817,7 @@ export interface ExchangeListItem {
|
|||||||
exchangeBaseUrl: string;
|
exchangeBaseUrl: string;
|
||||||
currency: string;
|
currency: string;
|
||||||
paytoUris: string[];
|
paytoUris: string[];
|
||||||
tos: ExchangeTos;
|
tos: ExchangeTosStatusDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> =>
|
const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> =>
|
||||||
@ -833,8 +833,8 @@ const codecForExchangeAuditor = (): Codec<ExchangeAuditor> =>
|
|||||||
.property("denomination_keys", codecForList(codecForAuditorDenomSig()))
|
.property("denomination_keys", codecForList(codecForAuditorDenomSig()))
|
||||||
.build("codecForExchangeAuditor");
|
.build("codecForExchangeAuditor");
|
||||||
|
|
||||||
const codecForExchangeTos = (): Codec<ExchangeTos> =>
|
const codecForExchangeTos = (): Codec<ExchangeTosStatusDetails> =>
|
||||||
buildCodecForObject<ExchangeTos>()
|
buildCodecForObject<ExchangeTosStatusDetails>()
|
||||||
.property("acceptedVersion", codecOptional(codecForString()))
|
.property("acceptedVersion", codecOptional(codecForString()))
|
||||||
.property("currentVersion", codecOptional(codecForString()))
|
.property("currentVersion", codecOptional(codecForString()))
|
||||||
.property("contentType", codecOptional(codecForString()))
|
.property("contentType", codecOptional(codecForString()))
|
||||||
|
@ -80,7 +80,7 @@ import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
|
|||||||
* for all previous versions must be written, which should be
|
* for all previous versions must be written, which should be
|
||||||
* avoided.
|
* avoided.
|
||||||
*/
|
*/
|
||||||
export const TALER_DB_NAME = "taler-wallet-main-v6";
|
export const TALER_DB_NAME = "taler-wallet-main-v7";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the metadata database. This database is used
|
* Name of the metadata database. This database is used
|
||||||
@ -99,7 +99,7 @@ export const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
|
|||||||
* backwards-compatible way or object stores and indices
|
* backwards-compatible way or object stores and indices
|
||||||
* are added.
|
* are added.
|
||||||
*/
|
*/
|
||||||
export const WALLET_DB_MINOR_VERSION = 2;
|
export const WALLET_DB_MINOR_VERSION = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ranges for operation status fields.
|
* Ranges for operation status fields.
|
||||||
@ -450,40 +450,41 @@ export interface ExchangeDetailsRecord {
|
|||||||
*/
|
*/
|
||||||
signingKeys: ExchangeSignKeyJson[];
|
signingKeys: ExchangeSignKeyJson[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Etag of the current ToS of the exchange.
|
||||||
|
*/
|
||||||
|
tosCurrentEtag: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about ToS acceptance from the user.
|
||||||
|
*/
|
||||||
|
tosAccepted:
|
||||||
|
| {
|
||||||
|
etag: string;
|
||||||
|
timestamp: TalerProtocolTimestamp;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
wireInfo: WireInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExchangeTosRecord {
|
||||||
|
exchangeBaseUrl: string;
|
||||||
|
|
||||||
|
etag: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Terms of service text or undefined if not downloaded yet.
|
* Terms of service text or undefined if not downloaded yet.
|
||||||
*
|
*
|
||||||
* This is just used as a cache of the last downloaded ToS.
|
* This is just used as a cache of the last downloaded ToS.
|
||||||
*
|
*
|
||||||
* FIXME: Put in separate object store!
|
|
||||||
*/
|
*/
|
||||||
termsOfServiceText: string | undefined;
|
termsOfServiceText: string | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* content-type of the last downloaded termsOfServiceText.
|
* Content-type of the last downloaded termsOfServiceText.
|
||||||
*
|
|
||||||
* * FIXME: Put in separate object store!
|
|
||||||
*/
|
*/
|
||||||
termsOfServiceContentType: string | undefined;
|
termsOfServiceContentType: string | undefined;
|
||||||
|
|
||||||
/**
|
|
||||||
* ETag for last terms of service download.
|
|
||||||
*/
|
|
||||||
termsOfServiceLastEtag: string | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ETag for last terms of service accepted.
|
|
||||||
*/
|
|
||||||
termsOfServiceAcceptedEtag: string | undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Timestamp when the ToS was accepted.
|
|
||||||
*
|
|
||||||
* Used during backup merging.
|
|
||||||
*/
|
|
||||||
termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp | undefined;
|
|
||||||
|
|
||||||
wireInfo: WireInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExchangeDetailsPointer {
|
export interface ExchangeDetailsPointer {
|
||||||
@ -491,11 +492,6 @@ export interface ExchangeDetailsPointer {
|
|||||||
|
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Last observed protocol version range offered by the exchange.
|
|
||||||
*/
|
|
||||||
protocolVersionRange: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timestamp when the (masterPublicKey, currency) pointer
|
* Timestamp when the (masterPublicKey, currency) pointer
|
||||||
* has been updated.
|
* has been updated.
|
||||||
@ -1899,6 +1895,14 @@ export const WalletStoresV1 = {
|
|||||||
byReservePub: describeIndex("byReservePub", "reservePub", {}),
|
byReservePub: describeIndex("byReservePub", "reservePub", {}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
exchangeTos: describeStore(
|
||||||
|
"exchangeTos",
|
||||||
|
describeContents<ExchangeTosRecord>({
|
||||||
|
keyPath: ["exchangeBaseUrl", "etag"],
|
||||||
|
autoIncrement: true,
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
),
|
||||||
config: describeStore(
|
config: describeStore(
|
||||||
"config",
|
"config",
|
||||||
describeContents<ConfigRecord>({ keyPath: "key" }),
|
describeContents<ConfigRecord>({ keyPath: "key" }),
|
||||||
@ -2116,7 +2120,6 @@ export const WalletStoresV1 = {
|
|||||||
"bankAccounts",
|
"bankAccounts",
|
||||||
describeContents<BankAccountsRecord>({
|
describeContents<BankAccountsRecord>({
|
||||||
keyPath: "uri",
|
keyPath: "uri",
|
||||||
versionAdded: 2,
|
|
||||||
}),
|
}),
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
|
@ -298,7 +298,6 @@ export async function exportBackup(
|
|||||||
currency: dp.currency,
|
currency: dp.currency,
|
||||||
master_public_key: dp.masterPublicKey,
|
master_public_key: dp.masterPublicKey,
|
||||||
update_clock: dp.updateClock,
|
update_clock: dp.updateClock,
|
||||||
protocol_version_range: dp.protocolVersionRange,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -358,8 +357,8 @@ export async function exportBackup(
|
|||||||
purseTimeout: x.purseTimeout,
|
purseTimeout: x.purseTimeout,
|
||||||
startDate: x.startDate,
|
startDate: x.startDate,
|
||||||
})),
|
})),
|
||||||
tos_accepted_etag: ex.termsOfServiceAcceptedEtag,
|
tos_accepted_etag: ex.tosAccepted?.etag,
|
||||||
tos_accepted_timestamp: ex.termsOfServiceAcceptedTimestamp,
|
tos_accepted_timestamp: ex.tosAccepted?.timestamp,
|
||||||
denominations:
|
denominations:
|
||||||
backupDenominationsByExchange[ex.exchangeBaseUrl] ?? [],
|
backupDenominationsByExchange[ex.exchangeBaseUrl] ?? [],
|
||||||
});
|
});
|
||||||
|
@ -351,7 +351,6 @@ export async function importBackup(
|
|||||||
currency: backupExchange.currency,
|
currency: backupExchange.currency,
|
||||||
masterPublicKey: backupExchange.master_public_key,
|
masterPublicKey: backupExchange.master_public_key,
|
||||||
updateClock: backupExchange.update_clock,
|
updateClock: backupExchange.update_clock,
|
||||||
protocolVersionRange: backupExchange.protocol_version_range,
|
|
||||||
},
|
},
|
||||||
permanent: true,
|
permanent: true,
|
||||||
lastUpdate: undefined,
|
lastUpdate: undefined,
|
||||||
@ -388,14 +387,18 @@ export async function importBackup(
|
|||||||
wadFee: Amounts.parseOrThrow(fee.wad_fee),
|
wadFee: Amounts.parseOrThrow(fee.wad_fee),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let tosAccepted = undefined;
|
||||||
|
if (
|
||||||
|
backupExchangeDetails.tos_accepted_etag &&
|
||||||
|
backupExchangeDetails.tos_accepted_timestamp
|
||||||
|
) {
|
||||||
|
tosAccepted = {
|
||||||
|
etag: backupExchangeDetails.tos_accepted_etag,
|
||||||
|
timestamp: backupExchangeDetails.tos_accepted_timestamp,
|
||||||
|
};
|
||||||
|
}
|
||||||
await tx.exchangeDetails.put({
|
await tx.exchangeDetails.put({
|
||||||
exchangeBaseUrl: backupExchangeDetails.base_url,
|
exchangeBaseUrl: backupExchangeDetails.base_url,
|
||||||
termsOfServiceAcceptedEtag: backupExchangeDetails.tos_accepted_etag,
|
|
||||||
termsOfServiceText: undefined,
|
|
||||||
termsOfServiceLastEtag: undefined,
|
|
||||||
termsOfServiceContentType: undefined,
|
|
||||||
termsOfServiceAcceptedTimestamp:
|
|
||||||
backupExchangeDetails.tos_accepted_timestamp,
|
|
||||||
wireInfo,
|
wireInfo,
|
||||||
currency: backupExchangeDetails.currency,
|
currency: backupExchangeDetails.currency,
|
||||||
auditors: backupExchangeDetails.auditors.map((x) => ({
|
auditors: backupExchangeDetails.auditors.map((x) => ({
|
||||||
@ -406,6 +409,8 @@ export async function importBackup(
|
|||||||
masterPublicKey: backupExchangeDetails.master_public_key,
|
masterPublicKey: backupExchangeDetails.master_public_key,
|
||||||
protocolVersionRange: backupExchangeDetails.protocol_version,
|
protocolVersionRange: backupExchangeDetails.protocol_version,
|
||||||
reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
|
reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
|
||||||
|
tosCurrentEtag: backupExchangeDetails.tos_accepted_etag || "",
|
||||||
|
tosAccepted,
|
||||||
globalFees: backupExchangeDetails.global_fees.map((x) => ({
|
globalFees: backupExchangeDetails.global_fees.map((x) => ({
|
||||||
accountFee: Amounts.parseOrThrow(x.accountFee),
|
accountFee: Amounts.parseOrThrow(x.accountFee),
|
||||||
historyFee: Amounts.parseOrThrow(x.historyFee),
|
historyFee: Amounts.parseOrThrow(x.historyFee),
|
||||||
@ -419,7 +424,6 @@ export async function importBackup(
|
|||||||
purseTimeout: x.purseTimeout,
|
purseTimeout: x.purseTimeout,
|
||||||
startDate: x.startDate,
|
startDate: x.startDate,
|
||||||
})),
|
})),
|
||||||
|
|
||||||
signingKeys: backupExchangeDetails.signing_keys.map((x) => ({
|
signingKeys: backupExchangeDetails.signing_keys.map((x) => ({
|
||||||
key: x.key,
|
key: x.key,
|
||||||
master_sig: x.master_sig,
|
master_sig: x.master_sig,
|
||||||
|
@ -174,24 +174,40 @@ export async function getExchangeDetails(
|
|||||||
getExchangeDetails.makeContext = (db: DbAccess<typeof WalletStoresV1>) =>
|
getExchangeDetails.makeContext = (db: DbAccess<typeof WalletStoresV1>) =>
|
||||||
db.mktx((x) => [x.exchanges, x.exchangeDetails]);
|
db.mktx((x) => [x.exchanges, x.exchangeDetails]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the database based on the download of the terms of service.
|
||||||
|
*/
|
||||||
export async function updateExchangeTermsOfService(
|
export async function updateExchangeTermsOfService(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
exchangeBaseUrl: string,
|
exchangeBaseUrl: string,
|
||||||
tos: ExchangeTosDownloadResult,
|
tos: ExchangeTosDownloadResult,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await ws.db
|
await ws.db
|
||||||
.mktx((x) => [x.exchanges, x.exchangeDetails])
|
.mktx((x) => [x.exchanges, x.exchangeTos, x.exchangeDetails])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const d = await getExchangeDetails(tx, exchangeBaseUrl);
|
const d = await getExchangeDetails(tx, exchangeBaseUrl);
|
||||||
|
let tosRecord = await tx.exchangeTos.get([exchangeBaseUrl, tos.tosEtag]);
|
||||||
|
if (!tosRecord) {
|
||||||
|
tosRecord = {
|
||||||
|
etag: tos.tosEtag,
|
||||||
|
exchangeBaseUrl,
|
||||||
|
termsOfServiceContentType: tos.tosContentType,
|
||||||
|
termsOfServiceText: tos.tosText,
|
||||||
|
};
|
||||||
|
await tx.exchangeTos.put(tosRecord);
|
||||||
|
}
|
||||||
if (d) {
|
if (d) {
|
||||||
d.termsOfServiceText = tos.tosText;
|
d.tosCurrentEtag = tos.tosEtag;
|
||||||
d.termsOfServiceContentType = tos.tosContentType;
|
|
||||||
d.termsOfServiceLastEtag = tos.tosEtag;
|
|
||||||
await tx.exchangeDetails.put(d);
|
await tx.exchangeDetails.put(d);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark a ToS version as accepted by the user.
|
||||||
|
*
|
||||||
|
* @param etag version of the ToS to accept, or current ToS version of not given
|
||||||
|
*/
|
||||||
export async function acceptExchangeTermsOfService(
|
export async function acceptExchangeTermsOfService(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
exchangeBaseUrl: string,
|
exchangeBaseUrl: string,
|
||||||
@ -202,7 +218,10 @@ export async function acceptExchangeTermsOfService(
|
|||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const d = await getExchangeDetails(tx, exchangeBaseUrl);
|
const d = await getExchangeDetails(tx, exchangeBaseUrl);
|
||||||
if (d) {
|
if (d) {
|
||||||
d.termsOfServiceAcceptedEtag = etag;
|
d.tosAccepted = {
|
||||||
|
etag: etag || d.tosCurrentEtag,
|
||||||
|
timestamp: TalerProtocolTimestamp.now(),
|
||||||
|
};
|
||||||
await tx.exchangeDetails.put(d);
|
await tx.exchangeDetails.put(d);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -611,7 +630,8 @@ export async function updateExchangeFromUrlHandler(
|
|||||||
["text/plain"],
|
["text/plain"],
|
||||||
);
|
);
|
||||||
const tosHasBeenAccepted =
|
const tosHasBeenAccepted =
|
||||||
exchangeDetails?.termsOfServiceAcceptedEtag === tosDownload.tosEtag;
|
exchangeDetails?.tosAccepted &&
|
||||||
|
exchangeDetails.tosAccepted.etag === tosDownload.tosEtag;
|
||||||
|
|
||||||
let recoupGroupId: string | undefined;
|
let recoupGroupId: string | undefined;
|
||||||
|
|
||||||
@ -647,13 +667,13 @@ export async function updateExchangeFromUrlHandler(
|
|||||||
globalFees,
|
globalFees,
|
||||||
exchangeBaseUrl: r.baseUrl,
|
exchangeBaseUrl: r.baseUrl,
|
||||||
wireInfo,
|
wireInfo,
|
||||||
termsOfServiceText: tosDownload.tosText,
|
tosCurrentEtag: tosDownload.tosContentType,
|
||||||
termsOfServiceAcceptedEtag: tosHasBeenAccepted
|
tosAccepted: tosHasBeenAccepted
|
||||||
? tosDownload.tosEtag
|
? {
|
||||||
|
etag: tosDownload.tosEtag,
|
||||||
|
timestamp: TalerProtocolTimestamp.now(),
|
||||||
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
termsOfServiceContentType: tosDownload.tosContentType,
|
|
||||||
termsOfServiceLastEtag: tosDownload.tosEtag,
|
|
||||||
termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp.now(),
|
|
||||||
};
|
};
|
||||||
// FIXME: only update if pointer got updated
|
// FIXME: only update if pointer got updated
|
||||||
r.lastUpdate = TalerProtocolTimestamp.now();
|
r.lastUpdate = TalerProtocolTimestamp.now();
|
||||||
@ -665,7 +685,6 @@ export async function updateExchangeFromUrlHandler(
|
|||||||
masterPublicKey: details.masterPublicKey,
|
masterPublicKey: details.masterPublicKey,
|
||||||
// FIXME: only change if pointer really changed
|
// FIXME: only change if pointer really changed
|
||||||
updateClock: TalerProtocolTimestamp.now(),
|
updateClock: TalerProtocolTimestamp.now(),
|
||||||
protocolVersionRange: keysInfo.protocolVersion,
|
|
||||||
};
|
};
|
||||||
await tx.exchanges.put(r);
|
await tx.exchanges.put(r);
|
||||||
await tx.exchangeDetails.put(details);
|
await tx.exchangeDetails.put(details);
|
||||||
|
@ -69,6 +69,7 @@ import {
|
|||||||
CoinStatus,
|
CoinStatus,
|
||||||
DenominationRecord,
|
DenominationRecord,
|
||||||
DenominationVerificationStatus,
|
DenominationVerificationStatus,
|
||||||
|
ExchangeTosRecord,
|
||||||
PlanchetRecord,
|
PlanchetRecord,
|
||||||
PlanchetStatus,
|
PlanchetStatus,
|
||||||
WalletStoresV1,
|
WalletStoresV1,
|
||||||
@ -1278,12 +1279,8 @@ export async function getExchangeWithdrawalInfo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let tosAccepted = false;
|
let tosAccepted = false;
|
||||||
|
if (exchangeDetails.tosAccepted?.timestamp) {
|
||||||
if (exchangeDetails.termsOfServiceLastEtag) {
|
if (exchangeDetails.tosAccepted.etag === exchangeDetails.tosCurrentEtag) {
|
||||||
if (
|
|
||||||
exchangeDetails.termsOfServiceAcceptedEtag ===
|
|
||||||
exchangeDetails.termsOfServiceLastEtag
|
|
||||||
) {
|
|
||||||
tosAccepted = true;
|
tosAccepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1357,7 +1354,12 @@ export async function getWithdrawalDetailsForUri(
|
|||||||
const exchanges: ExchangeListItem[] = [];
|
const exchanges: ExchangeListItem[] = [];
|
||||||
|
|
||||||
await ws.db
|
await ws.db
|
||||||
.mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
|
.mktx((x) => [
|
||||||
|
x.exchanges,
|
||||||
|
x.exchangeDetails,
|
||||||
|
x.exchangeTos,
|
||||||
|
x.denominations,
|
||||||
|
])
|
||||||
.runReadOnly(async (tx) => {
|
.runReadOnly(async (tx) => {
|
||||||
const exchangeRecords = await tx.exchanges.iter().toArray();
|
const exchangeRecords = await tx.exchanges.iter().toArray();
|
||||||
for (const r of exchangeRecords) {
|
for (const r of exchangeRecords) {
|
||||||
@ -1366,14 +1368,19 @@ export async function getWithdrawalDetailsForUri(
|
|||||||
.iter(r.baseUrl)
|
.iter(r.baseUrl)
|
||||||
.toArray();
|
.toArray();
|
||||||
if (details && denominations) {
|
if (details && denominations) {
|
||||||
|
const tosRecord = await tx.exchangeTos.get([
|
||||||
|
details.exchangeBaseUrl,
|
||||||
|
details.tosCurrentEtag,
|
||||||
|
]);
|
||||||
exchanges.push({
|
exchanges.push({
|
||||||
exchangeBaseUrl: details.exchangeBaseUrl,
|
exchangeBaseUrl: details.exchangeBaseUrl,
|
||||||
currency: details.currency,
|
currency: details.currency,
|
||||||
|
// FIXME: We probably don't want to include the full ToS here!
|
||||||
tos: {
|
tos: {
|
||||||
acceptedVersion: details.termsOfServiceAcceptedEtag,
|
acceptedVersion: details.tosAccepted?.etag,
|
||||||
currentVersion: details.termsOfServiceLastEtag,
|
currentVersion: details.tosCurrentEtag,
|
||||||
contentType: details.termsOfServiceContentType,
|
contentType: tosRecord?.termsOfServiceContentType ?? "",
|
||||||
content: details.termsOfServiceText,
|
content: tosRecord?.termsOfServiceText ?? "",
|
||||||
},
|
},
|
||||||
paytoUris: details.wireInfo.accounts.map((x) => x.payto_uri),
|
paytoUris: details.wireInfo.accounts.map((x) => x.payto_uri),
|
||||||
});
|
});
|
||||||
|
@ -94,6 +94,7 @@ import {
|
|||||||
WalletCoreVersion,
|
WalletCoreVersion,
|
||||||
WalletNotification,
|
WalletNotification,
|
||||||
codecForSetDevModeRequest,
|
codecForSetDevModeRequest,
|
||||||
|
ExchangeTosStatusDetails,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
|
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
|
||||||
import {
|
import {
|
||||||
@ -107,6 +108,8 @@ import {
|
|||||||
CoinStatus,
|
CoinStatus,
|
||||||
ConfigRecordKey,
|
ConfigRecordKey,
|
||||||
DenominationRecord,
|
DenominationRecord,
|
||||||
|
ExchangeDetailsRecord,
|
||||||
|
ExchangeTosRecord,
|
||||||
exportDb,
|
exportDb,
|
||||||
importDb,
|
importDb,
|
||||||
WalletStoresV1,
|
WalletStoresV1,
|
||||||
@ -228,7 +231,11 @@ import {
|
|||||||
OpenedPromise,
|
OpenedPromise,
|
||||||
openPromise,
|
openPromise,
|
||||||
} from "./util/promiseUtils.js";
|
} from "./util/promiseUtils.js";
|
||||||
import { DbAccess, GetReadWriteAccess } from "./util/query.js";
|
import {
|
||||||
|
DbAccess,
|
||||||
|
GetReadOnlyAccess,
|
||||||
|
GetReadWriteAccess,
|
||||||
|
} from "./util/query.js";
|
||||||
import { OperationAttemptResult } from "./util/retries.js";
|
import { OperationAttemptResult } from "./util/retries.js";
|
||||||
import { TimerAPI, TimerGroup } from "./util/timer.js";
|
import { TimerAPI, TimerGroup } from "./util/timer.js";
|
||||||
import {
|
import {
|
||||||
@ -461,6 +468,10 @@ async function fillDefaults(ws: InternalWalletState): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the exchange ToS in the requested format.
|
||||||
|
* Try to download in the accepted format not cached.
|
||||||
|
*/
|
||||||
async function getExchangeTos(
|
async function getExchangeTos(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
exchangeBaseUrl: string,
|
exchangeBaseUrl: string,
|
||||||
@ -468,9 +479,14 @@ async function getExchangeTos(
|
|||||||
): Promise<GetExchangeTosResult> {
|
): Promise<GetExchangeTosResult> {
|
||||||
// FIXME: download ToS in acceptable format if passed!
|
// FIXME: download ToS in acceptable format if passed!
|
||||||
const { exchangeDetails } = await updateExchangeFromUrl(ws, exchangeBaseUrl);
|
const { exchangeDetails } = await updateExchangeFromUrl(ws, exchangeBaseUrl);
|
||||||
const content = exchangeDetails.termsOfServiceText;
|
const tosDetails = await ws.db
|
||||||
const currentEtag = exchangeDetails.termsOfServiceLastEtag;
|
.mktx((x) => [x.exchangeTos])
|
||||||
const contentType = exchangeDetails.termsOfServiceContentType;
|
.runReadOnly(async (tx) => {
|
||||||
|
return await getExchangeTosStatusDetails(tx, exchangeDetails);
|
||||||
|
});
|
||||||
|
const content = tosDetails.content;
|
||||||
|
const currentEtag = tosDetails.currentVersion;
|
||||||
|
const contentType = tosDetails.contentType;
|
||||||
if (
|
if (
|
||||||
content === undefined ||
|
content === undefined ||
|
||||||
currentEtag === undefined ||
|
currentEtag === undefined ||
|
||||||
@ -483,7 +499,7 @@ async function getExchangeTos(
|
|||||||
acceptedFormat.findIndex((f) => f === contentType) !== -1
|
acceptedFormat.findIndex((f) => f === contentType) !== -1
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
|
acceptedEtag: exchangeDetails.tosAccepted?.etag,
|
||||||
currentEtag,
|
currentEtag,
|
||||||
content,
|
content,
|
||||||
contentType,
|
contentType,
|
||||||
@ -499,16 +515,17 @@ async function getExchangeTos(
|
|||||||
|
|
||||||
if (tosDownload.tosContentType === contentType) {
|
if (tosDownload.tosContentType === contentType) {
|
||||||
return {
|
return {
|
||||||
acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
|
acceptedEtag: exchangeDetails.tosAccepted?.etag,
|
||||||
currentEtag,
|
currentEtag,
|
||||||
content,
|
content,
|
||||||
contentType,
|
contentType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateExchangeTermsOfService(ws, exchangeBaseUrl, tosDownload);
|
await updateExchangeTermsOfService(ws, exchangeBaseUrl, tosDownload);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
|
acceptedEtag: exchangeDetails.tosAccepted?.etag,
|
||||||
currentEtag: tosDownload.tosEtag,
|
currentEtag: tosDownload.tosEtag,
|
||||||
content: tosDownload.tosText,
|
content: tosDownload.tosText,
|
||||||
contentType: tosDownload.tosContentType,
|
contentType: tosDownload.tosContentType,
|
||||||
@ -585,12 +602,43 @@ async function forgetKnownBankAccounts(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getExchangeTosStatusDetails(
|
||||||
|
tx: GetReadOnlyAccess<{ exchangeTos: typeof WalletStoresV1.exchangeTos }>,
|
||||||
|
exchangeDetails: ExchangeDetailsRecord,
|
||||||
|
): Promise<ExchangeTosStatusDetails> {
|
||||||
|
let exchangeTos = await tx.exchangeTos.get([
|
||||||
|
exchangeDetails.exchangeBaseUrl,
|
||||||
|
exchangeDetails.tosCurrentEtag,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!exchangeTos) {
|
||||||
|
exchangeTos = {
|
||||||
|
etag: "not-available",
|
||||||
|
termsOfServiceContentType: "text/plain",
|
||||||
|
termsOfServiceText: "terms of service unavailable",
|
||||||
|
exchangeBaseUrl: exchangeDetails.exchangeBaseUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
acceptedVersion: exchangeDetails.tosAccepted?.etag,
|
||||||
|
content: exchangeTos.termsOfServiceContentType,
|
||||||
|
contentType: exchangeTos.termsOfServiceContentType,
|
||||||
|
currentVersion: exchangeTos.etag,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function getExchanges(
|
async function getExchanges(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
): Promise<ExchangesListResponse> {
|
): Promise<ExchangesListResponse> {
|
||||||
const exchanges: ExchangeListItem[] = [];
|
const exchanges: ExchangeListItem[] = [];
|
||||||
await ws.db
|
await ws.db
|
||||||
.mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
|
.mktx((x) => [
|
||||||
|
x.exchanges,
|
||||||
|
x.exchangeDetails,
|
||||||
|
x.exchangeTos,
|
||||||
|
x.denominations,
|
||||||
|
])
|
||||||
.runReadOnly(async (tx) => {
|
.runReadOnly(async (tx) => {
|
||||||
const exchangeRecords = await tx.exchanges.iter().toArray();
|
const exchangeRecords = await tx.exchanges.iter().toArray();
|
||||||
for (const r of exchangeRecords) {
|
for (const r of exchangeRecords) {
|
||||||
@ -612,15 +660,12 @@ async function getExchanges(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tos = await getExchangeTosStatusDetails(tx, exchangeDetails);
|
||||||
|
|
||||||
exchanges.push({
|
exchanges.push({
|
||||||
exchangeBaseUrl: r.baseUrl,
|
exchangeBaseUrl: r.baseUrl,
|
||||||
currency,
|
currency,
|
||||||
tos: {
|
tos,
|
||||||
acceptedVersion: exchangeDetails.termsOfServiceAcceptedEtag,
|
|
||||||
currentVersion: exchangeDetails.termsOfServiceLastEtag,
|
|
||||||
contentType: exchangeDetails.termsOfServiceContentType,
|
|
||||||
content: exchangeDetails.termsOfServiceText,
|
|
||||||
},
|
|
||||||
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
|
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -634,7 +679,12 @@ async function getExchangeDetailedInfo(
|
|||||||
): Promise<ExchangeFullDetails> {
|
): Promise<ExchangeFullDetails> {
|
||||||
//TODO: should we use the forceUpdate parameter?
|
//TODO: should we use the forceUpdate parameter?
|
||||||
const exchange = await ws.db
|
const exchange = await ws.db
|
||||||
.mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
|
.mktx((x) => [
|
||||||
|
x.exchanges,
|
||||||
|
x.exchangeTos,
|
||||||
|
x.exchangeDetails,
|
||||||
|
x.denominations,
|
||||||
|
])
|
||||||
.runReadOnly(async (tx) => {
|
.runReadOnly(async (tx) => {
|
||||||
const ex = await tx.exchanges.get(exchangeBaseurl);
|
const ex = await tx.exchanges.get(exchangeBaseurl);
|
||||||
const dp = ex?.detailsPointer;
|
const dp = ex?.detailsPointer;
|
||||||
@ -656,6 +706,8 @@ async function getExchangeDetailedInfo(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tos = await getExchangeTosStatusDetails(tx, exchangeDetails);
|
||||||
|
|
||||||
const denominations: DenominationInfo[] = denominationRecords.map((x) =>
|
const denominations: DenominationInfo[] = denominationRecords.map((x) =>
|
||||||
DenominationRecord.toDenomInfo(x),
|
DenominationRecord.toDenomInfo(x),
|
||||||
);
|
);
|
||||||
@ -664,12 +716,7 @@ async function getExchangeDetailedInfo(
|
|||||||
info: {
|
info: {
|
||||||
exchangeBaseUrl: ex.baseUrl,
|
exchangeBaseUrl: ex.baseUrl,
|
||||||
currency,
|
currency,
|
||||||
tos: {
|
tos,
|
||||||
acceptedVersion: exchangeDetails.termsOfServiceAcceptedEtag,
|
|
||||||
currentVersion: exchangeDetails.termsOfServiceLastEtag,
|
|
||||||
contentType: exchangeDetails.termsOfServiceContentType,
|
|
||||||
content: exchangeDetails.termsOfServiceText,
|
|
||||||
},
|
|
||||||
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
|
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
|
||||||
auditors: exchangeDetails.auditors,
|
auditors: exchangeDetails.auditors,
|
||||||
wireInfo: exchangeDetails.wireInfo,
|
wireInfo: exchangeDetails.wireInfo,
|
||||||
|
Loading…
Reference in New Issue
Block a user