taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 475ac2e513e2b7a1b576592749038ee4ec61d3e8
parent 32944ad9a392da1d86a570f277232026c0bb8fd4
Author: Florian Dold <florian@dold.me>
Date:   Mon, 15 Sep 2025 13:37:55 +0200

wallet-core: implement allowCompletion for addExchange request, implement completeExchangeBaseUrl request

Diffstat:
Mpackages/taler-util/src/types-taler-wallet.ts | 49++++++++++++++++++++++++++++++++++++++++++++-----
Mpackages/taler-wallet-core/src/wallet-api-types.ts | 14++++++++++++++
Mpackages/taler-wallet-core/src/wallet.ts | 42++++++++++++++++++++++++++++++++++++++++++
3 files changed, 100 insertions(+), 5 deletions(-)

diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts @@ -2013,22 +2013,31 @@ export type GetExchangeEntryByUrlResponse = ExchangeListItem; export interface AddExchangeRequest { /** - * @deprecated Use {@link uri} instead - */ - exchangeBaseUrl?: string; - - /** * Either an http(s) exchange base URL or * a taler://add-exchange/ URI. */ uri?: string; + /** + * Only ephemerally add the exchange. + */ ephemeral?: boolean; /** + * Allow passing incomplete URLs. The wallet will try to complete + * the URL and throw an error if completion is not possible. + */ + allowCompletion?: boolean; + + /** * @deprecated use a separate API call to start a forced exchange update instead */ forceUpdate?: boolean; + + /** + * @deprecated Use {@link uri} instead + */ + exchangeBaseUrl?: string; } export interface AddExchangeResponse { @@ -4161,3 +4170,33 @@ export const codecForImportDbFromFileRequest = buildCodecForObject<ImportDbFromFileRequest>() .property("path", codecForString()) .build("ImportDbFromFileRequest"); + +export interface CompleteBaseUrlRequest { + url: string; +} + +export const codecForCompleteBaseUrlRequest = + (): Codec<CompleteBaseUrlRequest> => + buildCodecForObject<CompleteBaseUrlRequest>() + .property("url", codecForString()) + .build("CompleteBaseUrlRequest"); + +export type CompleteBaseUrlResult = + | { + /** + * ok: completion is a proper exchange + */ + status: "ok"; + /** Completed exchange base URL, if completion was possible */ + completion: string; + } + | { + /** + * bad-syntax: url is so badly malformed, it can't be completed + * bad-network: syntax okay, but exchange can't be reached + * bad-exchange: syntax and network okay, but not talking to an exchange + */ + status: "bad-syntax" | "bad-network" | "bad-exchange"; + /** Error details in case status is not "ok" */ + error: TalerErrorDetail; + }; diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -54,6 +54,8 @@ import { CheckPeerPushDebitRequest, CheckPeerPushDebitResponse, CoinDumpJson, + CompleteBaseUrlRequest, + CompleteBaseUrlResult, ConfirmPayRequest, ConfirmPayResult, ConfirmPeerPullDebitRequest, @@ -265,6 +267,7 @@ export enum WalletApiOperation { UpdateExchangeEntry = "updateExchangeEntry", PrepareWithdrawExchange = "prepareWithdrawExchange", GetExchangeResources = "getExchangeResources", + CompleteExchangeBaseUrl = "completeExchangeBaseUrl", DeleteExchange = "deleteExchange", ListGlobalCurrencyExchanges = "listGlobalCurrencyExchanges", ListGlobalCurrencyAuditors = "listGlobalCurrencyAuditors", @@ -695,6 +698,16 @@ export type RemoveGlobalCurrencyAuditorOp = { // group: Exchange Management /** + * Force a refresh on coins where it would not + * be necessary. + */ +export type CompleteExchangeBaseUrlOp = { + op: WalletApiOperation.CompleteExchangeBaseUrl; + request: CompleteBaseUrlRequest; + response: CompleteBaseUrlResult; +}; + +/** * List exchanges known to the wallet. */ export type ListExchangesOp = { @@ -1513,6 +1526,7 @@ export type WalletOperations = { [WalletApiOperation.TestingWaitExchangeWalletKyc]: TestingWaitExchangeWalletKycOp; [WalletApiOperation.TestingPlanMigrateExchangeBaseUrl]: TestingPlanMigrateExchangeBaseUrlOp; [WalletApiOperation.HintApplicationResumed]: HintApplicationResumedOp; + [WalletApiOperation.CompleteExchangeBaseUrl]: CompleteExchangeBaseUrlOp; }; export type WalletCoreRequestType< diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts @@ -52,6 +52,8 @@ import { Codec, CoinDumpJson, CoinStatus, + CompleteBaseUrlRequest, + CompleteBaseUrlResult, ConfirmPayRequest, ConfirmPayResult, CoreApiResponse, @@ -155,6 +157,7 @@ import { codecForCheckPayTemplateRequest, codecForCheckPeerPullPaymentRequest, codecForCheckPeerPushDebitRequest, + codecForCompleteBaseUrlRequest, codecForConfirmPayRequest, codecForConfirmPeerPushPaymentRequest, codecForConfirmWithdrawalRequestRequest, @@ -869,6 +872,30 @@ async function handleRecoverStoredBackup( return {}; } +const urlCharRegex = /^[a-zA-Z0-9\-_.~!*'();:@&=+$,/?%#[\]]+$/; + +export async function handleCompleteExchangeBaseUrl( + wex: WalletExecutionContext, + req: CompleteBaseUrlRequest, +): Promise<CompleteBaseUrlResult> { + const trimmedUrl = req.url.trim(); + + if (!urlCharRegex.test(trimmedUrl)) { + return { + status: "bad-syntax", + error: { + code: TalerErrorCode.WALLET_CORE_API_BAD_REQUEST, + }, + }; + } + + // FIXME: Do completion via network. + return { + completion: canonicalizeBaseUrl(trimmedUrl), + status: "ok", + }; +} + async function handleSetWalletRunConfig( wex: WalletExecutionContext, req: InitRequest, @@ -997,6 +1024,17 @@ async function handleAddExchange( "AddExchangeRequest must either specify uri or exchangeBaseUrl", ); } + + if (req.allowCompletion) { + const completeRes = await handleCompleteExchangeBaseUrl(wex, { + url: exchangeBaseUrl, + }); + if (completeRes.status != "ok") { + throw TalerError.fromUncheckedDetail(completeRes.error); + } + exchangeBaseUrl = completeRes.completion; + } + await fetchFreshExchange(wex, exchangeBaseUrl, {}); // Exchange has been explicitly added upon user request. // Thus, we mark it as "used". @@ -1837,6 +1875,10 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = { codec: codecForEmptyObject(), handler: listStoredBackups, }, + [WalletApiOperation.CompleteExchangeBaseUrl]: { + codec: codecForCompleteBaseUrlRequest(), + handler: handleCompleteExchangeBaseUrl, + }, [WalletApiOperation.SetWalletRunConfig]: { codec: codecForInitRequest(), handler: handleSetWalletRunConfig,