commit 8e4c4a5731df290b2dcca856bac0b9a43c81bdde parent bdb8d5f57c553b37eef32681e20f04b56c2dd82e Author: Florian Dold <florian@dold.me> Date: Thu, 18 Jun 2026 15:53:20 +0200 clean up taler URI parsing Due to historical baggage, we had up to three (!!) different implementations of parsing various taler:// URIs. This command cleans up the payto parsing and removes superfluous helper functions. Diffstat:
32 files changed, 548 insertions(+), 1341 deletions(-)
diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts @@ -18,6 +18,7 @@ * Imports. */ import { + AbsoluteTime, AmountJson, AmountLike, Amounts, @@ -33,52 +34,99 @@ import { getRandomBytes, hash, HttpStatusCode, + j2s, Logger, - parsePayUri, + Result, stringToBytes, TalerErrorCode, TalerProtocolTimestamp, TalerSignaturePurpose, - AbsoluteTime, + TalerUriAction, + TalerUris, URL, - j2s, } from "@gnu-taler/taler-util"; import { - HttpResponse, createPlatformHttpLib, + HttpResponse, } from "@gnu-taler/taler-util/http"; +import { unzlibSync, zlibSync } from "fflate"; import { anastasisData } from "./anastasis-data.js"; import { + ChallengeFeedback, + ChallengeFeedbackStatus, +} from "./challenge-feedback-types.js"; +import { + accountKeypairDerive, + asOpaque, + coreSecretEncrypt, + coreSecretRecover, + decryptKeyShare, + decryptPolicyMetadata, + decryptRecoveryDocument, + encryptKeyshare, + encryptPolicyMetadata, + encryptRecoveryDocument, + encryptTruth, + KeyShare, + OpaqueData, + pinAnswerHash, + PolicyKey, + policyKeyDerive, + PolicySalt, + secureAnswerHash, + TruthSalt, + typedArrayConcat, + UserIdentifier, + userIdentifierDerive, +} from "./crypto.js"; +import { ProviderInfo, suggestPolicies } from "./policy-suggestion.js"; +import { codecForChallengeInstructionMessage, EscrowConfigurationResponse, RecoveryMetaResponse, TruthUploadRequest, } from "./provider-types.js"; import { + ChallengeType, + EscrowMethod, + RecoveryDocument, +} from "./recovery-document-types.js"; +import { ActionArgsAddAuthentication, + ActionArgsAddPolicy, + ActionArgsAddProvider, + ActionArgsChangeVersion, ActionArgsDeleteAuthentication, ActionArgsDeletePolicy, + ActionArgsDeleteProvider, ActionArgsEnterSecret, ActionArgsEnterSecretName, ActionArgsEnterUserAttributes, - ActionArgsAddPolicy, + ActionArgsSelectChallenge, ActionArgsSelectContinent, ActionArgsSelectCountry, - ActionArgsSelectChallenge, ActionArgsSolveChallengeRequest, ActionArgsUpdateExpiration, + ActionArgsUpdatePolicy, + AggregatedPolicyMetaInfo, AuthenticationProviderStatus, + AuthenticationProviderStatusMap, AuthenticationProviderStatusOk, AuthMethod, BackupStates, - codecForActionArgsEnterUserAttributes, + ChallengeInfo, codecForActionArgsAddPolicy, - codecForActionArgsSelectChallenge, + codecForActionArgsChangeVersion, codecForActionArgSelectContinent, codecForActionArgSelectCountry, + codecForActionArgsEnterUserAttributes, + codecForActionArgsSelectChallenge, codecForActionArgsUpdateExpiration, ContinentInfo, CountryInfo, + DiscoveryCursor, + DiscoveryResult, + PolicyMetaInfo, RecoveryInformation, RecoveryInternalData, RecoveryStates, @@ -87,58 +135,12 @@ import { ReducerStateError, ReducerStateRecovery, SuccessDetails, - codecForActionArgsChangeVersion, - ActionArgsChangeVersion, TruthMetaData, - ActionArgsUpdatePolicy, - ActionArgsAddProvider, - ActionArgsDeleteProvider, - DiscoveryCursor, - DiscoveryResult, - PolicyMetaInfo, - ChallengeInfo, - AggregatedPolicyMetaInfo, - AuthenticationProviderStatusMap, } from "./reducer-types.js"; -import { - accountKeypairDerive, - asOpaque, - coreSecretEncrypt, - encryptKeyshare, - encryptRecoveryDocument, - encryptTruth, - OpaqueData, - PolicyKey, - policyKeyDerive, - PolicySalt, - TruthSalt, - secureAnswerHash, - UserIdentifier, - userIdentifierDerive, - typedArrayConcat, - decryptRecoveryDocument, - decryptKeyShare, - KeyShare, - coreSecretRecover, - pinAnswerHash, - decryptPolicyMetadata, - encryptPolicyMetadata, -} from "./crypto.js"; -import { unzlibSync, zlibSync } from "fflate"; -import { - ChallengeType, - EscrowMethod, - RecoveryDocument, -} from "./recovery-document-types.js"; -import { ProviderInfo, suggestPolicies } from "./policy-suggestion.js"; -import { - ChallengeFeedback, - ChallengeFeedbackStatus, -} from "./challenge-feedback-types.js"; +export * from "./challenge-feedback-types.js"; export * from "./reducer-types.js"; export * as validators from "./validators.js"; -export * from "./challenge-feedback-types.js"; const logger = new Logger("anastasis-core:index.ts"); @@ -590,15 +592,18 @@ async function uploadSecret( }; } truthPayUris.push(talerPayUri); - const parsedUri = parsePayUri(talerPayUri); - if (!parsedUri) { + const parsedUriRes = TalerUris.parse(talerPayUri); + if ( + Result.isError(parsedUriRes) || + parsedUriRes.value.type !== TalerUriAction.Pay + ) { return { reducer_type: "error", code: TalerErrorCode.ANASTASIS_REDUCER_BACKEND_FAILURE, hint: `payment requested, but no taler://pay URI given`, }; } - truthPaySecrets[meth.provider] = parsedUri.orderId; + truthPaySecrets[meth.provider] = parsedUriRes.value.orderId; continue; } return { @@ -646,7 +651,11 @@ async function uploadSecret( )?.payto; let paySecret: string | undefined; if (talerPayUri) { - paySecret = parsePayUri(talerPayUri)!.orderId; + const p = TalerUris.parse(talerPayUri); + if (Result.isError(p) || p.value.type !== TalerUriAction.Pay) { + throw Error("invalid pay URI"); + } + paySecret = p.value.orderId; } const reqUrl = new URL(`policy/${acctKeypair.pub}`, prov.provider_url); if (paySecret) { @@ -696,7 +705,9 @@ async function uploadSecret( }; } policyPayUris.push(talerPayUri); - const parsedUri = parsePayUri(talerPayUri); + const parsedUri = Result.orUndefined( + TalerUris.parseRestricted(talerPayUri, TalerUriAction.Pay), + ); if (!parsedUri) { return { reducer_type: "error", diff --git a/packages/libeufin-bank-webui/src/pages/OperationState/state.ts b/packages/libeufin-bank-webui/src/pages/OperationState/state.ts @@ -22,10 +22,15 @@ import { TalerCoreBankErrorsByMethod, TalerCorebankApi, TalerError, - TalerUris, + TalerUriAction, + WithdrawUriResult, assertUnreachable, } from "@gnu-taler/taler-util"; -import { useBankCoreApiContext, useTranslationContext, utils } from "@gnu-taler/web-util/browser"; +import { + useBankCoreApiContext, + useTranslationContext, + utils, +} from "@gnu-taler/web-util/browser"; import { useEffect, useState } from "preact/hooks"; import { useSettingsContext } from "../../context/settings.js"; import { useWithdrawalDetails } from "../../hooks/account.js"; @@ -96,22 +101,15 @@ export function useComponentState({ }; } - const parsedUri = TalerUris.createTalerWithdraw( - bank.getIntegrationAPI().href as HostPortPath, - withdrawalOperationId, - ); - const uri = TalerUris.stringify(parsedUri); - if (!parsedUri) { - return { - status: "invalid-withdrawal", - error: undefined, - uri, - }; - } + const parsedUri: WithdrawUriResult = { + type: TalerUriAction.Withdraw, + bankIntegrationApiBaseUrl: bank.getIntegrationAPI().href as HostPortPath, + withdrawalOperationId: withdrawalOperationId, + }; return (): utils.RecursiveState<State> => { const result = useWithdrawalDetails(withdrawalOperationId); - const {i18n} = useTranslationContext() + const { i18n } = useTranslationContext(); const shouldCreateNewOperation = result && @@ -135,7 +133,7 @@ export function useComponentState({ return { status: "loading-error", error: result, - title: i18n.str`Failed to load withdrawal details.` + title: i18n.str`Failed to load withdrawal details.`, }; } diff --git a/packages/libeufin-bank-webui/src/pages/OperationState/views.tsx b/packages/libeufin-bank-webui/src/pages/OperationState/views.tsx @@ -19,6 +19,7 @@ import { HttpStatusCode, PaytoType, TalerErrorCode, + TalerUriAction, TalerUris, assertUnreachable, } from "@gnu-taler/taler-util"; @@ -557,11 +558,11 @@ export function ReadyView({ lib: { bank }, } = useBankCoreApiContext(); - const parsedUri = TalerUris.createTalerWithdraw( - uri.bankIntegrationApiBaseUrl, - uri.withdrawalOperationId, - ); - const talerWithdrawUri = TalerUris.stringify(parsedUri); + const talerWithdrawUri = TalerUris.stringify({ + type: TalerUriAction.Withdraw, + bankIntegrationApiBaseUrl: uri.bankIntegrationApiBaseUrl, + withdrawalOperationId: uri.withdrawalOperationId, + }); useEffect(() => { walletInegrationApi.publishTalerAction(uri); }, []); diff --git a/packages/libeufin-bank-webui/src/pages/WithdrawalOperationPage.tsx b/packages/libeufin-bank-webui/src/pages/WithdrawalOperationPage.tsx @@ -14,14 +14,21 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { Attention, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { + HostPortPath, + TalerUri, + TalerUriAction, + TalerUris, +} from "@gnu-taler/taler-util"; +import { + Attention, + RouteDefinition, + useBankCoreApiContext, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; -import { useBankCoreApiContext } from "@gnu-taler/web-util/browser"; import { useBankState } from "../hooks/bank-state.js"; -import { RouteDefinition } from "@gnu-taler/web-util/browser"; import { WithdrawalQRCode } from "./WithdrawalQRCode.js"; -import { HostPortPath } from "@gnu-taler/taler-util"; -import { TalerUris } from "@gnu-taler/taler-util"; const TALER_SCREEN_ID = 115; @@ -39,10 +46,11 @@ export function WithdrawalOperationPage({ const { lib: { bank: api }, } = useBankCoreApiContext(); - const parsedUri = TalerUris.createTalerWithdraw( - api.getIntegrationAPI().href as HostPortPath, - operationId, - ); + const parsedUri: TalerUri = { + type: TalerUriAction.Withdraw, + bankIntegrationApiBaseUrl: api.getIntegrationAPI().href as HostPortPath, + withdrawalOperationId: operationId, + }; const uri = TalerUris.stringify(parsedUri); const { i18n } = useTranslationContext(); const [, updateBankState] = useBankState(); diff --git a/packages/taler-exchange-aml-webui/src/pages/Search.tsx b/packages/taler-exchange-aml-webui/src/pages/Search.tsx @@ -18,7 +18,6 @@ import { assertUnreachable, BitcoinBech32, encodeCrock, - getURLHostnamePortPath, HostPortPath, HttpStatusCode, IbanString, @@ -56,7 +55,6 @@ import { HandleSessionNotReady } from "../components/HandleAccountNotReady.js"; import { useAccountDecisions } from "../hooks/decisions.js"; import { useOfficer } from "../hooks/officer.js"; import { ToInvestigateIcon } from "./AccountList.js"; -import { Profile } from "./Profile.js"; const TALER_SCREEN_ID = 111; @@ -158,37 +156,37 @@ function ShowResult({ } if (history.type === "fail") { return ( - <FailLoading - operation={history} - title={i18n.str`Failed to load the account history.`} - translate={(d) => { - switch (d.case) { - case HttpStatusCode.Forbidden: - return ( - <i18n.Translate> - This session signature is invalid, contact administrator or - create a new one. - </i18n.Translate> - ); - case HttpStatusCode.NotFound: - return ( - <i18n.Translate> - The designated AML session is not known, contact - administrator or create a new one. - </i18n.Translate> - ); - case HttpStatusCode.Conflict: - return ( - <i18n.Translate> - The designated AML session is not enabled, contact - administrator or create a new one. - </i18n.Translate> - ); - default: - assertUnreachable(d.case); - } - }} - /> + <FailLoading + operation={history} + title={i18n.str`Failed to load the account history.`} + translate={(d) => { + switch (d.case) { + case HttpStatusCode.Forbidden: + return ( + <i18n.Translate> + This session signature is invalid, contact administrator or + create a new one. + </i18n.Translate> + ); + case HttpStatusCode.NotFound: + return ( + <i18n.Translate> + The designated AML session is not known, contact administrator + or create a new one. + </i18n.Translate> + ); + case HttpStatusCode.Conflict: + return ( + <i18n.Translate> + The designated AML session is not enabled, contact + administrator or create a new one. + </i18n.Translate> + ); + default: + assertUnreachable(d.case); + } + }} + /> ); } @@ -416,7 +414,7 @@ function WalletForm({ const form = useForm<PaytoUriTalerForm>( design, { - exchange: getURLHostnamePortPath(config.keys.base_url), + exchange: config.keys.base_url, }, // createTalerPaytoValidator(i18n), ); diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts @@ -44,6 +44,8 @@ import { TalerMerchantInstanceHttpClient, TalerMerchantManagementHttpClient, TalerProtocolTimestamp, + TalerUriAction, + TalerUris, TemplateType, TokenAuth, TransactionsResponse, @@ -62,7 +64,6 @@ import { rsaBlind, setGlobalLogLevelFromString, signKycAuth, - stringifyPayTemplateUri, succeedOrThrow, toHexString, } from "@gnu-taler/taler-util"; @@ -1112,7 +1113,8 @@ deploymentCli } logger.info(`template default successfully created`); - templateURI = stringifyPayTemplateUri({ + templateURI = TalerUris.stringify({ + type: TalerUriAction.PayTemplate, merchantBaseUrl: instanceURL, templateId: "default", }); diff --git a/packages/taler-harness/src/integrationtests/test-paivana-repurchase.ts b/packages/taler-harness/src/integrationtests/test-paivana-repurchase.ts @@ -22,6 +22,7 @@ import { encodeCrock, getRandomBytes, Logger, + PayTemplateUriResult, Result, stringToBytes, succeedOrThrow, @@ -124,14 +125,13 @@ export async function runPaivanaRepurchaseTest(t: GlobalTestState) { // thrid device so no information produced // here is available - const newTemplate = TalerUris.createTalerPayTemplate( - uri.merchantBaseUrl, - uri.templateId, - { - fulfillmenURL: website, - sessionId: session.paivanaId, - }, - ); + const newTemplate: PayTemplateUriResult = { + type: TalerUriAction.PayTemplate, + merchantBaseUrl: uri.merchantBaseUrl, + templateId: uri.templateId, + fulfillmentUrl: website, + sessionId: session.paivanaId, + }; const talerPayTemplateUri = TalerUris.stringify(newTemplate); logger.info("3) pay template", newTemplate, talerPayTemplateUri); diff --git a/packages/taler-harness/src/integrationtests/test-paivana.ts b/packages/taler-harness/src/integrationtests/test-paivana.ts @@ -23,6 +23,7 @@ import { Result, succeedOrThrow, TalerMerchantInstanceHttpClient, + TalerUri, TalerUriAction, TalerUris, TransactionMajorState, @@ -97,14 +98,14 @@ export async function runPaivanaTest(t: GlobalTestState) { // thrid device so no information produced // here is available - const newTemplate = TalerUris.createTalerPayTemplate( - uri.merchantBaseUrl, - uri.templateId, - { - fulfillmenURL: website, - sessionId: session.paivanaId, - }, - ); + const newTemplate: TalerUri = { + type: TalerUriAction.PayTemplate, + merchantBaseUrl: uri.merchantBaseUrl, + templateId: uri.templateId, + fulfillmentUrl: website, + sessionId: session.paivanaId, + }; + const talerPayTemplateUri = TalerUris.stringify(newTemplate); logger.info("3) pay template", newTemplate, talerPayTemplateUri); diff --git a/packages/taler-merchant-webui/src/paths/instance/orders/details/DetailPage.tsx b/packages/taler-merchant-webui/src/paths/instance/orders/details/DetailPage.tsx @@ -27,10 +27,10 @@ import { MerchantContractVersion, Result, TalerMerchantApi, + TalerUriAction, TalerUris, TransactionWireTransfer, assertUnreachable, - stringifyRefundUri, } from "@gnu-taler/taler-util"; import { NotificationCardBulma, @@ -704,7 +704,8 @@ function PaidPage({ const refundURI = !order.refunded || !someRefundIsPending || wireDeadlineInThePast ? undefined - : stringifyRefundUri({ + : TalerUris.stringify({ + type: TalerUriAction.Refund, merchantBaseUrl: state.backendUrl.href as HostPortPath, orderId: order.contract_terms.order_id, }); @@ -868,9 +869,7 @@ function PaidPage({ justifyContent: "center", }} > - <QR_Taler - uri={Result.unpack(TalerUris.parse(refundURI))} - /> + <QR_Taler uri={Result.unpack(TalerUris.parse(refundURI))} /> </div> </section> )} @@ -1138,7 +1137,7 @@ export function DetailPage({ </div> ); } - }; + } return ( <Fragment> diff --git a/packages/taler-merchant-webui/src/paths/instance/templates/qr/QrPage.tsx b/packages/taler-merchant-webui/src/paths/instance/templates/qr/QrPage.tsx @@ -22,6 +22,8 @@ import { HostPortPath, TalerMerchantApi, + TalerUri, + TalerUriAction, TalerUris, } from "@gnu-taler/taler-util"; import { QR_Taler, useTranslationContext } from "@gnu-taler/web-util/browser"; @@ -44,7 +46,11 @@ export function QrPage({ id: templateId, onBack }: Props): VNode { const { state } = useSessionContext(); const merchantBaseUrl = state.backendUrl.href as HostPortPath; - const uri = TalerUris.createTalerPayTemplate(merchantBaseUrl, templateId); + const uri: TalerUri = { + type: TalerUriAction.PayTemplate, + merchantBaseUrl, + templateId, + }; const stringUri = TalerUris.stringify(uri); const printThis = useRef<HTMLElement>(null); diff --git a/packages/taler-util/src/taleruri.test.ts b/packages/taler-util/src/taleruri.test.ts @@ -14,34 +14,12 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { test } from "node:test"; import assert from "node:assert"; +import { test } from "node:test"; // import { AmountString } from "./types-taler-common.js"; import { HostPortPath } from "./payto.js"; -import { - // TalerUris, - parseAddExchangeUri, - parseDevExperimentUri, - parsePayPullUri, - parsePayPushUri, - parsePayTemplateUri, - parsePayUri, - parseRefundUri, - parseRestoreUri, - parseWithdrawExchangeUri, - parseWithdrawUri, - stringifyAddContact, - stringifyAddExchange, - stringifyDevExperimentUri, - stringifyPayPullUri, - stringifyPayPushUri, - stringifyPayTemplateUri, - stringifyPayUri, - stringifyRefundUri, - stringifyRestoreUri, - stringifyWithdrawExchange, - stringifyWithdrawUri, -} from "./taleruri.js"; +import { Result } from "./result.js"; +import { TalerUriAction, TalerUris } from "./taleruri.js"; import { AmountString } from "./types-taler-common.js"; { @@ -51,7 +29,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler withdraw uri parsing", (t) => { const url1 = "taler://withdraw/bank.example.com/12345"; - const r1 = parseWithdrawUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.Withdraw), + ); if (!r1) { assert.fail(); return; @@ -66,7 +46,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler withdraw uri parsing with external confirmation", (t) => { const url1 = "taler://withdraw/bank.example.com/12345?external-confirmation=1"; - const r1 = parseWithdrawUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.Withdraw), + ); if (!r1) { assert.fail(); return; @@ -81,7 +63,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler withdraw uri parsing (http)", (t) => { const url1 = "taler+http://withdraw/bank.example.com/12345"; - const r1 = parseWithdrawUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.Withdraw), + ); if (!r1) { assert.fail(); return; @@ -94,7 +78,8 @@ import { AmountString } from "./types-taler-common.js"; }); test("taler withdraw URI (stringify)", (t) => { - const url = stringifyWithdrawUri({ + const url = TalerUris.stringify({ + type: TalerUriAction.Withdraw, bankIntegrationApiBaseUrl: "https://bank.taler.test/integration-api/" as HostPortPath, withdrawalOperationId: "123", @@ -110,7 +95,9 @@ import { AmountString } from "./types-taler-common.js"; */ test("taler pay url parsing: defaults", (t) => { const url1 = "taler://pay/example.com/myorder/"; - const r1 = parsePayUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.Pay), + ); if (!r1) { assert.fail(); return; @@ -122,7 +109,9 @@ import { AmountString } from "./types-taler-common.js"; assert.strictEqual(r1.sessionId, ""); const url2 = "taler://pay/example.com/myorder/mysession"; - const r2 = parsePayUri(url2); + const r2 = Result.orUndefined( + TalerUris.parseRestricted(url2, TalerUriAction.Pay), + ); if (!r2) { assert.fail(); return; @@ -136,7 +125,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler pay url parsing: instance", (t) => { const url1 = "taler://pay/example.com/instances/myinst/myorder/"; - const r1 = parsePayUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.Pay), + ); if (!r1) { assert.fail(); return; @@ -150,7 +141,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler pay url parsing (claim token)", (t) => { const url1 = "taler://pay/example.com/instances/myinst/myorder/?c=ASDF"; - const r1 = parsePayUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.Pay), + ); if (!r1) { assert.fail(); return; @@ -165,7 +158,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler pay uri parsing: non-https", (t) => { const url1 = "taler+http://pay/example.com/myorder/"; - const r1 = parsePayUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.Pay), + ); if (!r1) { assert.fail(); return; @@ -179,7 +174,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler pay uri parsing: missing session component", (t) => { const url1 = "taler+http://pay/example.com/myorder"; - const r1 = parsePayUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.Pay), + ); if (r1) { assert.fail(); return; @@ -187,14 +184,16 @@ import { AmountString } from "./types-taler-common.js"; }); test("taler pay URI (stringify)", (t) => { - const url1 = stringifyPayUri({ + const url1 = TalerUris.stringify({ + type: TalerUriAction.Pay, merchantBaseUrl: "http://localhost:123/" as HostPortPath, orderId: "foo", sessionId: "", }); assert.deepStrictEqual(url1, "taler+http://pay/localhost:123/foo/"); - const url2 = stringifyPayUri({ + const url2 = TalerUris.stringify({ + type: TalerUriAction.Pay, merchantBaseUrl: "http://localhost:123/" as HostPortPath, orderId: "foo", sessionId: "bla", @@ -203,14 +202,16 @@ import { AmountString } from "./types-taler-common.js"; }); test("taler pay URI (stringify with https)", (t) => { - const url1 = stringifyPayUri({ + const url1 = TalerUris.stringify({ + type: TalerUriAction.Pay, merchantBaseUrl: "https://localhost:123/" as HostPortPath, orderId: "foo", sessionId: "", }); assert.deepStrictEqual(url1, "taler://pay/localhost:123/foo/"); - const url2 = stringifyPayUri({ + const url2 = TalerUris.stringify({ + type: TalerUriAction.Pay, merchantBaseUrl: "https://localhost/" as HostPortPath, orderId: "foo", sessionId: "bla", @@ -225,7 +226,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler refund uri parsing: non-https #1", (t) => { const url1 = "taler+http://refund/example.com/myorder/"; - const r1 = parseRefundUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.Refund), + ); if (!r1) { assert.fail(); return; @@ -239,7 +242,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler refund uri parsing", (t) => { const url1 = "taler://refund/merchant.example.com/1234/"; - const r1 = parseRefundUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.Refund), + ); if (!r1) { assert.fail(); return; @@ -253,7 +258,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler refund uri parsing with instance", (t) => { const url1 = "taler://refund/merchant.example.com/instances/myinst/1234/"; - const r1 = parseRefundUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.Refund), + ); if (!r1) { assert.fail(); return; @@ -266,7 +273,8 @@ import { AmountString } from "./types-taler-common.js"; }); test("taler refund URI (stringify)", (t) => { - const url = stringifyRefundUri({ + const url = TalerUris.stringify({ + type: TalerUriAction.Refund, merchantBaseUrl: "https://merchant.test/instance/pepe/" as HostPortPath, orderId: "123", }); @@ -282,7 +290,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler peer to peer push URI", (t) => { const url1 = "taler://pay-push/exch.example.com/foo"; - const r1 = parsePayPushUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.PayPush), + ); if (!r1) { assert.fail(); return; @@ -296,7 +306,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler peer to peer push URI (path)", (t) => { const url1 = "taler://pay-push/exch.example.com:123/bla/foo"; - const r1 = parsePayPushUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.PayPush), + ); if (!r1) { assert.fail(); return; @@ -310,7 +322,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler peer to peer push URI (http)", (t) => { const url1 = "taler+http://pay-push/exch.example.com:123/bla/foo"; - const r1 = parsePayPushUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.PayPush), + ); if (!r1) { assert.fail(); return; @@ -323,7 +337,8 @@ import { AmountString } from "./types-taler-common.js"; }); test("taler peer to peer push URI (stringify)", (t) => { - const url = stringifyPayPushUri({ + const url = TalerUris.stringify({ + type: TalerUriAction.PayPush, exchangeBaseUrl: "https://foo.example.com/bla/" as HostPortPath, contractPriv: "123", }); @@ -336,7 +351,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler peer to peer pull URI", (t) => { const url1 = "taler://pay-pull/exch.example.com/foo"; - const r1 = parsePayPullUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.PayPull), + ); if (!r1) { assert.fail(); return; @@ -350,7 +367,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler peer to peer pull URI (path)", (t) => { const url1 = "taler://pay-pull/exch.example.com:123/bla/foo"; - const r1 = parsePayPullUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.PayPull), + ); if (!r1) { assert.fail(); return; @@ -364,7 +383,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler peer to peer pull URI (http)", (t) => { const url1 = "taler+http://pay-pull/exch.example.com:123/bla/foo"; - const r1 = parsePayPullUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.PayPull), + ); if (!r1) { assert.fail(); return; @@ -377,7 +398,8 @@ import { AmountString } from "./types-taler-common.js"; }); test("taler peer to peer pull URI (stringify)", (t) => { - const url = stringifyPayPullUri({ + const url = TalerUris.stringify({ + type: TalerUriAction.PayPull, exchangeBaseUrl: "https://foo.example.com/bla/" as HostPortPath, contractPriv: "123", }); @@ -391,7 +413,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler pay template URI (parsing)", (t) => { const url1 = "taler://pay-template/merchant.example.com/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY"; - const r1 = parsePayTemplateUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.PayTemplate), + ); if (!r1) { assert.fail(); return; @@ -406,7 +430,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler pay template URI (parsing, case-sensitive)", (t) => { const url1 = "taler://pay-template/merchant.example.com/InstanceName/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY"; - const r1 = parsePayTemplateUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.PayTemplate), + ); if (!r1) { assert.fail(); return; @@ -421,7 +447,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler pay template URI (parsing, http with port)", (t) => { const url1 = "taler+http://pay-template/merchant.example.com:1234/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY"; - const r1 = parsePayTemplateUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.PayTemplate), + ); if (!r1) { assert.fail(); return; @@ -434,7 +462,8 @@ import { AmountString } from "./types-taler-common.js"; }); test("taler pay template URI (stringify)", (t) => { - const url1 = stringifyPayTemplateUri({ + const url1 = TalerUris.stringify({ + type: TalerUriAction.PayTemplate, merchantBaseUrl: "http://merchant.example.com:1234/" as HostPortPath, templateId: "FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY", }); @@ -445,7 +474,8 @@ import { AmountString } from "./types-taler-common.js"; }); test("taler pay template URI (stringify, case-sensitive)", (t) => { - const url1 = stringifyPayTemplateUri({ + const url1 = TalerUris.stringify({ + type: TalerUriAction.PayTemplate, merchantBaseUrl: "http://merchant.example.com:1234/InstanceName/" as HostPortPath, templateId: "FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY", @@ -460,8 +490,11 @@ import { AmountString } from "./types-taler-common.js"; * 5.10 action: restore https://lsd.gnunet.org/lsd0006/#name-action-restore */ test("taler restore URI (parsing, http with port)", (t) => { - const r1 = parseRestoreUri( - "taler+http://restore/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0/prov1.example.com,prov2.example.com:123", + const r1 = Result.orUndefined( + TalerUris.parseRestricted( + "taler+http://restore/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0/prov1.example.com,prov2.example.com:123", + TalerUriAction.Restore, + ), ); if (!r1) { assert.fail(); @@ -475,8 +508,11 @@ import { AmountString } from "./types-taler-common.js"; assert.deepStrictEqual(r1.providers[1], "http://prov2.example.com:123/"); }); test("taler restore URI (parsing, https with port)", (t) => { - const r1 = parseRestoreUri( - "taler://restore/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0/prov1.example.com,prov2.example.com:234,https%3A%2F%2Fprov1.example.com%2F", + const r1 = Result.orUndefined( + TalerUris.parseRestricted( + "taler://restore/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0/prov1.example.com,prov2.example.com:234,https%3A%2F%2Fprov1.example.com%2F", + TalerUriAction.Restore, + ), ); if (!r1) { assert.fail(); @@ -491,11 +527,11 @@ import { AmountString } from "./types-taler-common.js"; }); test("taler restore URI (stringify)", (t) => { - const url = stringifyRestoreUri({ + const url = TalerUris.stringify({ + type: TalerUriAction.Restore, walletRootPriv: "GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0", providers: [ - // FIXME: why here the stringify version add a slash in this provider? - "http://prov1.example.com" as HostPortPath, + "http://prov1.example.com/" as HostPortPath, "https://prov2.example.com:234/" as HostPortPath, ], }); @@ -511,7 +547,9 @@ import { AmountString } from "./types-taler-common.js"; test("taler dev exp URI (parsing)", (t) => { const url1 = "taler://dev-experiment/123"; - const r1 = parseDevExperimentUri(url1); + const r1 = Result.orUndefined( + TalerUris.parseRestricted(url1, TalerUriAction.DevExperiment), + ); if (!r1) { assert.fail(); return; @@ -520,12 +558,18 @@ import { AmountString } from "./types-taler-common.js"; }); test("taler dev exp URI (stringify)", (t) => { - const url1 = stringifyDevExperimentUri({ + const url1 = TalerUris.stringify({ + type: TalerUriAction.DevExperiment, devExperimentId: "123", }); assert.deepStrictEqual(url1, "taler://dev-experiment/123"); }); + const parseWithdrawExchangeUri = (s: string) => + Result.orUndefined( + TalerUris.parseRestricted(s, TalerUriAction.WithdrawExchange), + ); + /** * 5.12 action: withdraw-exchange https://lsd.gnunet.org/lsd0006/#name-action-withdraw-exchange */ @@ -601,7 +645,8 @@ import { AmountString } from "./types-taler-common.js"; }); test("taler withdraw exchange URI (stringify)", (t) => { - const url = stringifyWithdrawExchange({ + const url = TalerUris.stringify({ + type: TalerUriAction.WithdrawExchange, exchangeBaseUrl: "https://exchange.demo.taler.net" as HostPortPath, }); assert.deepStrictEqual( @@ -611,7 +656,8 @@ import { AmountString } from "./types-taler-common.js"; }); test("taler withdraw exchange URI with amount (stringify)", (t) => { - const url = stringifyWithdrawExchange({ + const url = TalerUris.stringify({ + type: TalerUriAction.WithdrawExchange, exchangeBaseUrl: "https://exchange.demo.taler.net" as HostPortPath, amount: "KUDOS:19" as AmountString, }); @@ -627,8 +673,11 @@ import { AmountString } from "./types-taler-common.js"; test("taler add exchange URI (parse)", (t) => { { - const r1 = parseAddExchangeUri( - "taler://add-exchange/exchange.example.com/", + const r1 = Result.orUndefined( + TalerUris.parseRestricted( + "taler://add-exchange/exchange.example.com/", + TalerUriAction.AddExchange, + ), ); if (!r1) { assert.fail(); @@ -640,8 +689,11 @@ import { AmountString } from "./types-taler-common.js"; ); } { - const r2 = parseAddExchangeUri( - "taler://add-exchange/exchanges.example.com/api/", + const r2 = Result.orUndefined( + TalerUris.parseRestricted( + "taler://add-exchange/exchanges.example.com/api/", + TalerUriAction.AddExchange, + ), ); if (!r2) { assert.fail(); @@ -655,7 +707,8 @@ import { AmountString } from "./types-taler-common.js"; }); test("taler add exchange URI (stringify)", (t) => { - const url = stringifyAddExchange({ + const url = TalerUris.stringify({ + type: TalerUriAction.AddExchange, exchangeBaseUrl: "https://exchange.demo.taler.net" as HostPortPath, }); assert.deepStrictEqual( @@ -668,7 +721,8 @@ import { AmountString } from "./types-taler-common.js"; * Add contact */ test("taler add contact URI (stringify)", (t) => { - const url = stringifyAddContact({ + const url = TalerUris.stringify({ + type: TalerUriAction.AddContact, alias: "bob@example.com", aliasType: "email", sourceBaseUrl: "https://taldir.example.com", @@ -686,11 +740,11 @@ import { AmountString } from "./types-taler-common.js"; */ test("taler pay url parsing: wrong scheme", (t) => { const url1 = "talerfoo://"; - const r1 = parsePayUri(url1); - assert.strictEqual(r1, undefined); + const r1 = TalerUris.parse(url1); + assert.ok(Result.isError(r1)); const url2 = "taler://refund/a/b/c/d/e/f"; - const r2 = parsePayUri(url2); - assert.strictEqual(r2, undefined); + const r2 = TalerUris.parse(url2); + assert.ok(Result.isError(r2)); }); } diff --git a/packages/taler-util/src/taleruri.ts b/packages/taler-util/src/taleruri.ts @@ -28,7 +28,6 @@ import { Codec, Context, DecodingError, renderContext } from "./codec.js"; import { assertUnreachable } from "./errors.js"; import { HostPortPath, Paytos } from "./payto.js"; import { Result, ResultError, ResultOk } from "./result.js"; -import { TalerErrorCode } from "./taler-error-codes.js"; import { AmountString, HashCodeString } from "./types-taler-common.js"; import { URL, URLSearchParams } from "./url.js"; /** @@ -59,7 +58,7 @@ export function codecForTalerUriString(): Codec<TalerUriString> { `expected string at ${renderContext(c)} but got ${typeof x}`, ); } - if (parseTalerUri(x) === undefined) { + if (Result.isError(TalerUris.parse(x))) { throw new DecodingError( `invalid taler URI at ${renderContext(c)} but got "${x}"`, ); @@ -97,12 +96,16 @@ export enum TalerUriParseError { * The validation of one parameter component failed */ INVALID_PARAMETER, + /** + * The taler action is unsupported. + */ + UNEXPECTED_ACTION, } export namespace TalerUris { export type URI = TalerUri; - const supported_targets: Record<TalerUriAction, true> = { + const supportedTargets: Record<TalerUriAction, true> = { "add-contact": true, "add-exchange": true, "dev-experiment": true, @@ -117,157 +120,8 @@ export namespace TalerUris { "withdrawal-transfer-result": true, }; - export function createTalerPay( - merchantBaseUrl: HostPortPath, - orderId: string, - sessionId: string, - opts: { - claimToken?: string; - noncePriv?: string; - } = {}, - ): PayUriResult { - return { - type: TalerUriAction.Pay, - merchantBaseUrl, - orderId, - sessionId, - ...opts, - }; - } - export function createTalerWithdraw( - bankIntegrationApiBaseUrl: HostPortPath, - withdrawalOperationId: string, - opts: { - externalConfirmation?: boolean; - } = {}, - ): WithdrawUriResult { - return { - type: TalerUriAction.Withdraw, - bankIntegrationApiBaseUrl, - withdrawalOperationId, - ...opts, - }; - } - export function createTalerRefund( - merchantBaseUrl: HostPortPath, - orderId: string, - ): RefundUriResult { - return { - type: TalerUriAction.Refund, - merchantBaseUrl, - orderId, - }; - } - export function createTalerPayPull( - exchangeBaseUrl: HostPortPath, - contractPriv: string, - ): PayPullUriResult { - return { - type: TalerUriAction.PayPull, - exchangeBaseUrl, - contractPriv, - }; - } - export function createTalerPayPush( - exchangeBaseUrl: HostPortPath, - contractPriv: string, - ): PayPushUriResult { - return { - type: TalerUriAction.PayPush, - exchangeBaseUrl, - contractPriv, - }; - } - export function createTalerPayTemplate( - merchantBaseUrl: HostPortPath, - templateId: string, - opts: { - sessionId?: string; - fulfillmenURL?: string; - } = {}, - ): PayTemplateUriResult { - return { - type: TalerUriAction.PayTemplate, - merchantBaseUrl, - templateId, - fulfillmentUrl: opts.fulfillmenURL, - sessionId: opts.sessionId, - }; - } - export function createTalerRestore( - walletRootPriv: string, - providers: HostPortPath[], - ): BackupRestoreUri { - return { - type: TalerUriAction.Restore, - providers, - walletRootPriv, - }; - } - export function createTalerDevExperiment( - devExperimentId: string, - // params: Record<string,string>, - query: URLSearchParams, // FIXME: Wrong type it should be Record<string,string> - ): DevExperimentUri { - return { - type: TalerUriAction.DevExperiment, - devExperimentId, - query, - }; - } - export function createTalerWithdrawExchange( - exchangeBaseUrl: HostPortPath, - opts: { - amount?: AmountString; - } = {}, - ): WithdrawExchangeUri { - return { - type: TalerUriAction.WithdrawExchange, - exchangeBaseUrl, - ...opts, - }; - } - export function createTalerAddExchange( - exchangeBaseUrl: HostPortPath, - ): AddExchangeUri { - return { - type: TalerUriAction.AddExchange, - exchangeBaseUrl, - }; - } - export function createTalerAddContact( - aliasType: string, - alias: string, - mailboxUri: string, - mailboxIdentity: string, - sourceBaseUrl: string, - ): AddContactUri { - return { - type: TalerUriAction.AddContact, - alias: alias, - aliasType: aliasType, - mailboxBaseUri: mailboxUri, - mailboxIdentity: mailboxIdentity, - sourceBaseUrl: sourceBaseUrl, - }; - } - export function createTalerWithdrawalTransferResult( - ref: string, - opts: { - status?: "success" | "aborted"; - } = {}, - ): WithdrawalTransferResultUri { - return { - type: TalerUriAction.WithdrawalTransferResult, - ref, - ...opts, - }; - } function asHost(s: HostPortPath): string { const b = new URL(s); - // if (b.port) { - // return `${b.host}:${b.port}${b.pathname}`; - // } return `${b.host}${b.pathname}`; } @@ -500,7 +354,7 @@ export namespace TalerUris { ? (uriTypeUncased.toLowerCase() as TalerUriAction) : uriTypeUncased; - if (!supported_targets[uriType]) { + if (!supportedTargets[uriType]) { return Result.errorWithDetail(TalerUriParseError.UNSUPPORTED, { uriType, }); @@ -556,17 +410,14 @@ export namespace TalerUris { // get session const sessionId = cs[cs.length - 1]; - return Result.of<URI>( - createTalerPay( - merchant ?? (cs[0] as HostPortPath), - orderId, - sessionId, - { - claimToken: params["c"], - noncePriv: params["n"], - }, - ), - ); + return Result.of<TalerUri>({ + type: TalerUriAction.Pay, + merchantBaseUrl: merchant ?? (cs[0] as HostPortPath), + orderId, + sessionId, + claimToken: params["c"], + noncePriv: params["n"], + }); } case TalerUriAction.Withdraw: { // check number of segments @@ -600,11 +451,12 @@ export namespace TalerUris { ? undefined : params["external-confirmation"] === "1"; - return Result.of<URI>( - createTalerWithdraw(bank ?? (cs[0] as HostPortPath), operationId, { - externalConfirmation, - }), - ); + return Result.of<TalerUri>({ + type: TalerUriAction.Withdraw, + bankIntegrationApiBaseUrl: bank ?? (cs[0] as HostPortPath), + withdrawalOperationId: operationId, + externalConfirmation, + }); } case TalerUriAction.Refund: { // check number of segments @@ -643,9 +495,11 @@ export namespace TalerUris { // get order id const orderId = cs[cs.length - 2]; - return Result.of<URI>( - createTalerRefund(merchant ?? (cs[0] as HostPortPath), orderId), - ); + return Result.of({ + type: TalerUriAction.Refund, + merchantBaseUrl: merchant ?? (cs[0] as HostPortPath), + orderId, + }); } case TalerUriAction.PayPull: { // check number of segments @@ -674,9 +528,11 @@ export namespace TalerUris { // get contract priv const contractPriv = cs[cs.length - 1]; // FIXME: validate private key - return Result.of<URI>( - createTalerPayPull(exchange ?? (cs[0] as HostPortPath), contractPriv), - ); + return Result.of({ + type: TalerUriAction.PayPull, + exchangeBaseUrl: exchange ?? (cs[0] as HostPortPath), + contractPriv, + }); } case TalerUriAction.PayPush: { // check number of segments @@ -706,9 +562,11 @@ export namespace TalerUris { // get contract priv const contractPriv = cs[cs.length - 1]; // FIXME: validate private key - return Result.of<URI>( - createTalerPayPush(exchange ?? (cs[0] as HostPortPath), contractPriv), - ); + return Result.of({ + type: TalerUriAction.PayPush, + exchangeBaseUrl: exchange ?? (cs[0] as HostPortPath), + contractPriv, + }); } case TalerUriAction.PayTemplate: { // check number of segments @@ -734,16 +592,15 @@ export namespace TalerUris { }, ); } - - // get contract priv - const contractPriv = cs[cs.length - 1]; // FIXME: validate private key - - return Result.of<URI>( - createTalerPayTemplate( - merchant ?? (cs[0] as HostPortPath), - contractPriv, - ), - ); + const templateId = cs[cs.length - 1]; // FIXME: validate private key + + return Result.of({ + type: TalerUriAction.PayTemplate, + merchantBaseUrl: merchant ?? (cs[0] as HostPortPath), + templateId, + fulfillmentUrl: params["fulfillment_url"], + sessionId: params["session_id"], + }); } case TalerUriAction.Restore: { // check number of segments @@ -775,9 +632,11 @@ export namespace TalerUris { providers.push(host); }); - return Result.of<URI>( - createTalerRestore(walletPriv ?? (cs[0] as HostPortPath), providers), - ); + return Result.of({ + type: TalerUriAction.Restore, + providers, + walletRootPriv: walletPriv ?? (cs[0] as HostPortPath), + }); } case TalerUriAction.DevExperiment: { // check number of segments @@ -787,10 +646,14 @@ export namespace TalerUris { }); } - const experimentId = cs[0]; + const devExperimentId = cs[0]; const query = new URLSearchParams(search); - return Result.of<URI>(createTalerDevExperiment(experimentId, query)); + return Result.of({ + type: TalerUriAction.DevExperiment, + devExperimentId, + query, + }); } case TalerUriAction.WithdrawExchange: { // check number of segments @@ -850,11 +713,11 @@ export namespace TalerUris { ? Amounts.stringify(amountRes.value) : undefined; - return Result.of<URI>( - createTalerWithdrawExchange(exchange ?? (cs[0] as HostPortPath), { - amount, - }), - ); + return Result.of({ + type: TalerUriAction.WithdrawExchange, + exchangeBaseUrl: exchange ?? (cs[0] as HostPortPath), + amount, + }); } case TalerUriAction.AddExchange: { // check number of segments @@ -881,9 +744,10 @@ export namespace TalerUris { ); } - return Result.of<URI>( - createTalerAddExchange(exchange ?? (cs[0] as HostPortPath)), - ); + return Result.of({ + type: TalerUriAction.AddExchange, + exchangeBaseUrl: exchange ?? (cs[0] as HostPortPath), + }); } case TalerUriAction.WithdrawalTransferResult: { if (cs.length === 0) { @@ -896,12 +760,11 @@ export namespace TalerUris { params["status"] !== "aborted" && params["status"] !== "success" ? undefined : params["status"]; - - return Result.of<URI>( - createTalerWithdrawalTransferResult(ref, { - status, - }), - ); + return Result.of({ + type: TalerUriAction.WithdrawalTransferResult, + ref, + status, + }); } case TalerUriAction.AddContact: { // check number of segments @@ -911,6 +774,9 @@ export namespace TalerUris { }); } + const aliasType = cs[0]; + const alias = cs[1]; + const mailboxBaseUri = Paytos.parseHostPortPath2( cs[2], cs.slice(2, cs.length - 2).join("/"), @@ -928,26 +794,44 @@ export namespace TalerUris { } const mailboxIdentity = cs[cs.length - 1]; - return Result.of<URI>( - createTalerAddContact( - cs[0], - cs[1], - mailboxBaseUri, - mailboxIdentity, - params["sourceBaseUrl"], - ), - ); + return Result.of({ + type: TalerUriAction.AddContact, + aliasType, + alias, + mailboxIdentity, + mailboxBaseUri, + sourceBaseUrl: params["sourceBaseUrl"], + }); } default: { assertUnreachable(uriType); } } } + + type ValueOf<T> = T[keyof T]; + type TalerUriTyped<T extends TalerUriAction> = ValueOf<{ + [K in TalerUri as K["type"] extends T ? K["type"] : never]: K; + }>; + + export function parseRestricted<T extends TalerUriAction>( + s: string, + t: T, + ): Result<TalerUriTyped<T>, TalerUriParseError> { + const p = parse(s); + if (Result.isError(p)) { + return p; + } + if (p.value.type !== t) { + return Result.error(TalerUriParseError.UNEXPECTED_ACTION); + } + // TypeScript's type system is too weak to type check this + // without a cast. + const pt: TalerUriTyped<T> = p.value as TalerUriTyped<T>; + return Result.of(pt); + } } -/** - * - */ export interface PayUriResult { type: TalerUriAction.Pay; merchantBaseUrl: HostPortPath; @@ -1036,154 +920,6 @@ export interface AddContactUri { sourceBaseUrl: string; } -/** - * Parse a taler[+http]://withdraw URI. - * Return undefined if not passed a valid URI. - */ -export function parseWithdrawUriWithError(s: string) { - const pi = parseProtoInfoWithError(s, "withdraw"); - if (pi.tag === "error") { - return pi; - } - - const c = pi.value.rest.split("?", 2); - const path = c[0]; - const q = new URLSearchParams(c[1] ?? ""); - - const parts = path.split("/"); - - if (parts.length < 2) { - return Result.error(TalerErrorCode.WALLET_TALER_URI_MALFORMED); - } - - const host = parts[0].toLowerCase(); - const pathSegments = parts.slice(1, parts.length - 1); - /** - * The statement below does not tolerate a slash-ended URI. - * This results in (1) the withdrawalId being passed as the - * empty string, and (2) the bankIntegrationApi ending with the - * actual withdrawal operation ID. That can be fixed by - * trimming the parts-list. FIXME - */ - const withdrawId = parts[parts.length - 1]; - // const p = [host, ...pathSegments].join("/"); - - const result: WithdrawUriResult = { - type: TalerUriAction.Withdraw, - bankIntegrationApiBaseUrl: Paytos.parseHostPortPath2( - host, - pathSegments.join("/"), - pi.value.innerProto, - )!, - withdrawalOperationId: withdrawId, - externalConfirmation: q.get("external-confirmation") == "1", - }; - return Result.of(result); -} - -/** - * - * @deprecated use parseWithdrawUriWithError - */ -export function parseWithdrawUri(s: string): WithdrawUriResult | undefined { - const r = parseWithdrawUriWithError(s); - if (r.tag === "error") return undefined; - return r.value; -} - -/** - * Parse a taler[+http]://withdraw URI. - * Return undefined if not passed a valid URI. - */ -export function parseAddExchangeUriWithError(s: string) { - const pi = parseProtoInfoWithError(s, "add-exchange"); - if (pi.tag === "error") { - return pi; - } - const parts = pi.value.rest.split("/"); - - if (parts.length < 2) { - return Result.error(TalerErrorCode.WALLET_TALER_URI_MALFORMED); - } - - const host = parts[0].toLowerCase(); - const pathSegments = parts.slice(1, parts.length - 1); - /** - * The statement below does not tolerate a slash-ended URI. - * This results in (1) the withdrawalId being passed as the - * empty string, and (2) the bankIntegrationApi ending with the - * actual withdrawal operation ID. That can be fixed by - * trimming the parts-list. FIXME - */ - // const p = [host, ...pathSegments].join("/"); - - const result: AddExchangeUri = { - type: TalerUriAction.AddExchange, - exchangeBaseUrl: Paytos.parseHostPortPath2( - host, - pathSegments.join("/"), - pi.value.innerProto, - )!, - }; - return Result.of(result); -} - -/** - * Parse a taler[+http]://add-contact URI. - * Return undefined if not passed a valid URI. - */ -export function parseAddContactUriWithError(s: string) { - const pi = parseProtoInfoWithError(s, "add-contact"); - if (pi.tag === "error") { - return pi; - } - const parts = pi.value.rest.split("/"); - - if (parts.length < 4) { - return Result.error(TalerErrorCode.WALLET_TALER_URI_MALFORMED); - } - const mailboxBaseUri = parts[2]; - const pathSegments = parts.slice(3, parts.length - 2); - const lastPart = parts[parts.length - 1]; - const q = new URLSearchParams(lastPart ?? ""); - const mailboxIdentity = lastPart.split("?")[0]; - const sourceBaseUrl = q.get("sourceBaseUrl") ?? ""; - const mailboxHostPort = Paytos.parseHostPortPath2( - mailboxBaseUri, - pathSegments.join("/"), - pi.value.innerProto, - ); - const result: AddContactUri = { - type: TalerUriAction.AddContact, - aliasType: parts[0], - alias: parts[1], - mailboxBaseUri: mailboxHostPort!, - mailboxIdentity: mailboxIdentity, - sourceBaseUrl: sourceBaseUrl, - }; - return Result.of(result); -} - -/** - * - * @deprecated use parseWithdrawUriWithError - */ -export function parseAddExchangeUri(s: string): AddExchangeUri | undefined { - const r = parseAddExchangeUriWithError(s); - if (r.tag === "error") return undefined; - return r.value; -} - -/** - * - * @deprecated use parseWithdrawUriWithError - */ -export function parseAddContactUri(s: string): AddContactUri | undefined { - const r = parseAddContactUriWithError(s); - if (r.tag === "error") return undefined; - return r.value; -} - export enum TalerUriAction { /** * https://lsd.gnunet.org/lsd0006/#section-5.1 @@ -1236,677 +972,6 @@ export enum TalerUriAction { AddContact = "add-contact", } -interface TalerUriProtoInfo { - innerProto: "http" | "https"; - rest: string; -} - -function parseProtoInfo( - s: string, - action: string, -): TalerUriProtoInfo | undefined { - const pfxPlain = `taler://${action}/`; - const pfxHttp = `taler+http://${action}/`; - if (s.toLowerCase().startsWith(pfxPlain)) { - return { - innerProto: "https", - rest: s.substring(pfxPlain.length), - }; - } else if (s.toLowerCase().startsWith(pfxHttp)) { - return { - innerProto: "http", - rest: s.substring(pfxHttp.length), - }; - } else { - return undefined; - } -} - -interface ProtoInfo { - innerProto: "http" | "https"; - rest: string; -} - -/** - * @deprecated - * - * @param s - * @param action - * @returns - */ -function parseProtoInfoWithError( - s: string, - action: string, -): Result<ProtoInfo, TalerErrorCode.WALLET_TALER_URI_MALFORMED> { - if ( - !s.toLowerCase().startsWith("taler://") && - !s.toLowerCase().startsWith("taler+http://") - ) { - return Result.error(TalerErrorCode.WALLET_TALER_URI_MALFORMED); - } - const pfxPlain = `taler://${action}/`; - const pfxHttp = `taler+http://${action}/`; - if (s.toLowerCase().startsWith(pfxPlain)) { - return Result.of({ - innerProto: "https", - rest: s.substring(pfxPlain.length), - }); - } else if (s.toLowerCase().startsWith(pfxHttp)) { - return Result.of({ - innerProto: "http", - rest: s.substring(pfxHttp.length), - }); - } else { - return Result.error(TalerErrorCode.WALLET_TALER_URI_MALFORMED); - } -} - -type Parser = (s: string) => TalerUri | undefined; -const parsers: { [A in TalerUriAction]: Parser } = { - [TalerUriAction.Pay]: parsePayUri, - [TalerUriAction.PayPull]: parsePayPullUri, - [TalerUriAction.PayPush]: parsePayPushUri, - [TalerUriAction.PayTemplate]: parsePayTemplateUri, - [TalerUriAction.Restore]: parseRestoreUri, - [TalerUriAction.Refund]: parseRefundUri, - [TalerUriAction.Withdraw]: parseWithdrawUri, - [TalerUriAction.DevExperiment]: parseDevExperimentUri, - [TalerUriAction.WithdrawExchange]: parseWithdrawExchangeUri, - [TalerUriAction.AddExchange]: parseAddExchangeUri, - [TalerUriAction.AddContact]: parseAddContactUri, - [TalerUriAction.WithdrawalTransferResult]: () => { - throw new Error("not supported"); - }, -}; - -/** - * @deprecated - * - * @param string - * @returns - */ -export function parseTalerUri(string: string): TalerUri | undefined { - const https = string.startsWith("taler://"); - const http = string.startsWith("taler+http://"); - if (!https && !http) return undefined; - const actionStart = https ? 8 : 13; - const actionEnd = string.indexOf("/", actionStart + 1); - const action = string.substring(actionStart, actionEnd); - const found = Object.values(TalerUriAction).find((x) => x === action); - if (!found) return undefined; - return parsers[found](string); -} - -/** - * @deprecated - * - * @param uri - * @returns - */ -export function stringifyTalerUri(uri: TalerUri): string { - switch (uri.type) { - case TalerUriAction.DevExperiment: { - return stringifyDevExperimentUri(uri); - } - case TalerUriAction.Pay: { - return stringifyPayUri(uri); - } - case TalerUriAction.PayPull: { - return stringifyPayPullUri(uri); - } - case TalerUriAction.PayPush: { - return stringifyPayPushUri(uri); - } - case TalerUriAction.PayTemplate: { - return stringifyPayTemplateUri(uri); - } - case TalerUriAction.Restore: { - return stringifyRestoreUri(uri); - } - case TalerUriAction.Refund: { - return stringifyRefundUri(uri); - } - case TalerUriAction.Withdraw: { - return stringifyWithdrawUri(uri); - } - case TalerUriAction.WithdrawExchange: { - return stringifyWithdrawExchange(uri); - } - case TalerUriAction.AddExchange: { - return stringifyAddExchange(uri); - } - case TalerUriAction.AddContact: { - return stringifyAddContact(uri); - } - case TalerUriAction.WithdrawalTransferResult: { - throw Error("not supported"); - } - } -} - -/** - * @deprecated - * - * Parse a taler[+http]://pay URI. - * Return undefined if not passed a valid URI. - */ -export function parsePayUri(s: string): PayUriResult | undefined { - const pi = parseProtoInfo(s, "pay"); - if (!pi) { - return undefined; - } - const c = pi?.rest.split("?"); - const q = new URLSearchParams(c[1] ?? ""); - const claimToken = q.get("c") ?? undefined; - const noncePriv = q.get("n") ?? undefined; - const parts = c[0].split("/"); - if (parts.length < 3) { - return undefined; - } - const host = parts[0].toLowerCase(); - const sessionId = parts[parts.length - 1]; - const orderId = parts[parts.length - 2]; - const pathSegments = parts.slice(1, parts.length - 2); - // const p = [host, ...pathSegments].join("/"); - const merchantBaseUrl = Paytos.parseHostPortPath2( - host, - pathSegments.join("/"), - pi.innerProto, - )!; - - return { - type: TalerUriAction.Pay, - merchantBaseUrl, - orderId, - sessionId, - claimToken, - noncePriv, - }; -} - -/** - * @deprecated - * - * @param s - * @returns - */ -export function parsePayTemplateUri( - uriString: string, -): PayTemplateUriResult | undefined { - const pi = parseProtoInfo(uriString, TalerUriAction.PayTemplate); - if (!pi) { - return undefined; - } - const c = pi.rest.split("?"); - - const parts = c[0].split("/"); - if (parts.length < 2) { - return undefined; - } - - const q = new URLSearchParams(c[1] ?? ""); - const params: Record<string, string> = {}; - q.forEach((v, k) => { - params[k] = v; - }); - - const host = parts[0].toLowerCase(); - const templateId = parts[parts.length - 1]; - const pathSegments = parts.slice(1, parts.length - 1); - const hostAndSegments = [host, ...pathSegments].join("/"); - // const merchantBaseUrl = canonicalizeBaseUrl( - // `${pi.innerProto}://${hostAndSegments}/`, - // ); - - const merchantBaseUrl = Paytos.parseHostPortPath2( - host, - pathSegments.join("/"), - pi.innerProto, - )!; - return { - type: TalerUriAction.PayTemplate, - merchantBaseUrl, - templateId, - fulfillmentUrl: q.get("fulfillment_url") ?? undefined, - sessionId: q.get("session_id") ?? undefined, - }; -} - -/** - * @deprecated - * - * @param s - * @returns - */ -export function parsePayPushUri(s: string): PayPushUriResult | undefined { - const pi = parseProtoInfo(s, TalerUriAction.PayPush); - if (!pi) { - return undefined; - } - const c = pi?.rest.split("?"); - const parts = c[0].split("/"); - if (parts.length < 2) { - return undefined; - } - const host = parts[0].toLowerCase(); - const contractPriv = parts[parts.length - 1]; - const pathSegments = parts.slice(1, parts.length - 1); - const hostAndSegments = [host, ...pathSegments].join("/"); - // const exchangeBaseUrl = canonicalizeBaseUrl( - // `${pi.innerProto}://${hostAndSegments}/`, - // ); - const exchangeBaseUrl = Paytos.parseHostPortPath2( - host, - pathSegments.join("/"), - pi.innerProto, - )!; - - return { - type: TalerUriAction.PayPush, - exchangeBaseUrl, - contractPriv, - }; -} - -/** - * @deprecated - * - * @param s - * @returns - */ -export function parsePayPullUri(s: string): PayPullUriResult | undefined { - const pi = parseProtoInfo(s, TalerUriAction.PayPull); - if (!pi) { - return undefined; - } - const c = pi?.rest.split("?"); - const parts = c[0].split("/"); - if (parts.length < 2) { - return undefined; - } - const host = parts[0].toLowerCase(); - const contractPriv = parts[parts.length - 1]; - const pathSegments = parts.slice(1, parts.length - 1); - const hostAndSegments = [host, ...pathSegments].join("/"); - // const exchangeBaseUrl = canonicalizeBaseUrl( - // `${pi.innerProto}://${hostAndSegments}/`, - // ); - const exchangeBaseUrl = Paytos.parseHostPortPath2( - host, - pathSegments.join("/"), - pi.innerProto, - )!; - - return { - type: TalerUriAction.PayPull, - exchangeBaseUrl, - contractPriv, - }; -} - -/** - * @deprecaed - * - * @param s - * @returns - */ -export function parseWithdrawExchangeUri( - s: string, -): WithdrawExchangeUri | undefined { - const pi = parseProtoInfo(s, "withdraw-exchange"); - if (!pi) { - return undefined; - } - const c = pi?.rest.split("?"); - const parts = c[0].split("/"); - if (parts.length < 1) { - return undefined; - } - const host = parts[0].toLowerCase(); - // Used to be the reserve public key, now it's empty! - const lastPathComponent = - parts.length > 1 ? parts[parts.length - 1] : undefined; - - if (lastPathComponent) { - // invalid taler://withdraw-exchange URI, must end with a slash - return undefined; - } - const pathSegments = parts.slice(1, parts.length - 1); - const hostAndSegments = [host, ...pathSegments].join("/"); - // const exchangeBaseUrl = canonicalizeBaseUrl( - // `${pi.innerProto}://${hostAndSegments}/`, - // ); - const exchangeBaseUrl = Paytos.parseHostPortPath2( - host, - pathSegments.join("/"), - pi.innerProto, - )!; - - const q = new URLSearchParams(c[1] ?? ""); - const amount = (q.get("a") ?? undefined) as AmountString | undefined; - - return { - type: TalerUriAction.WithdrawExchange, - exchangeBaseUrl, - amount, - }; -} - -/** - * @deprecated - * Parse a taler[+http]://refund URI. - * Return undefined if not passed a valid URI. - */ -export function parseRefundUri(s: string): RefundUriResult | undefined { - const pi = parseProtoInfo(s, "refund"); - if (!pi) { - return undefined; - } - const c = pi?.rest.split("?"); - const parts = c[0].split("/"); - if (parts.length < 3) { - return undefined; - } - const host = parts[0].toLowerCase(); - const sessionId = parts[parts.length - 1]; - const orderId = parts[parts.length - 2]; - const pathSegments = parts.slice(1, parts.length - 2); - const hostAndSegments = [host, ...pathSegments].join("/"); - // const merchantBaseUrl = canonicalizeBaseUrl( - // `${pi.innerProto}://${hostAndSegments}/`, - // ); - const merchantBaseUrl = Paytos.parseHostPortPath2( - host, - pathSegments.join("/"), - pi.innerProto, - )!; - - return { - type: TalerUriAction.Refund, - merchantBaseUrl, - orderId, - }; -} - -/** - * @deprecated - * - * @param s - * @returns - */ -export function parseDevExperimentUri(s: string): DevExperimentUri | undefined { - const pi = parseProtoInfo(s, "dev-experiment"); - const c = pi?.rest.split("?"); - if (!c) { - return undefined; - } - const parts = c[0].split("/"); - return { - type: TalerUriAction.DevExperiment, - devExperimentId: parts[0], - query: new URLSearchParams(c[1] ?? ""), - }; -} - -/** - * @deprecated - * - * @param s - * @returns - */ -export function parseRestoreUri(uri: string): BackupRestoreUri | undefined { - const pi = parseProtoInfo(uri, "restore"); - if (!pi) { - return undefined; - } - const c = pi.rest.split("?"); - const parts = c[0].split("/"); - if (parts.length < 2) { - return undefined; - } - - const walletRootPriv = parts[0]; - if (!walletRootPriv) return undefined; - const providers = new Array<HostPortPath>(); - parts[1].split(",").map((name) => { - const url = decodeURIComponent(name); - let isHttp = false; - const withoutScheme = url.startsWith("https://") - ? url.substring(8) - : (isHttp = url.startsWith("http://")) - ? url.substring(7) - : url; - const scheme = - url === withoutScheme ? pi.innerProto : isHttp ? "http" : "https"; - const [hostname, path] = withoutScheme.split("/", 1); - const host = Paytos.parseHostPortPath2(hostname, path ?? "/", scheme)!; - providers.push(host); - }); - return { - type: TalerUriAction.Restore, - walletRootPriv, - providers, - }; -} - -// ================================================ -// To string functions -// ================================================ - -/** - * @deprecated - * @param param0 - * @returns - */ -export function stringifyPayUri({ - merchantBaseUrl, - orderId, - sessionId, - claimToken, - noncePriv, -}: Omit<PayUriResult, "type">): string { - const { proto, path, query } = getUrlInfo(merchantBaseUrl, { - c: claimToken, - n: noncePriv, - }); - return `${proto}://pay/${path}${orderId}/${sessionId}${query}`; -} - -/** - * @deprecated - * @param param0 - * @returns - */ -export function stringifyPayPullUri({ - contractPriv, - exchangeBaseUrl, -}: Omit<PayPullUriResult, "type">): string { - const { proto, path } = getUrlInfo(exchangeBaseUrl); - return `${proto}://pay-pull/${path}${contractPriv}`; -} - -/** - * @deprecated - * @param param0 - * @returns - */ -export function stringifyPayPushUri({ - contractPriv, - exchangeBaseUrl, -}: Omit<PayPushUriResult, "type">): string { - const { proto, path } = getUrlInfo(exchangeBaseUrl); - - return `${proto}://pay-push/${path}${contractPriv}`; -} - -/** - * @deprecated - * @param param0 - * @returns - */ -export function stringifyRestoreUri({ - providers, - walletRootPriv, -}: Omit<BackupRestoreUri, "type">): string { - const list = providers - .map((url) => `${encodeURIComponent(new URL(url).href)}`) - .join(","); - return `taler://restore/${walletRootPriv}/${list}`; -} - -/** - * @deprecated - * @param param0 - * @returns - */ -export function stringifyWithdrawExchange({ - exchangeBaseUrl, - amount, -}: Omit<WithdrawExchangeUri, "type">): string { - const { proto, path, query } = getUrlInfo(exchangeBaseUrl, { - a: amount, - }); - return `${proto}://withdraw-exchange/${path}${query}`; -} - -/** - * @deprecated - * @param param0 - * @returns - */ -export function stringifyAddExchange({ - exchangeBaseUrl, -}: Omit<AddExchangeUri, "type">): string { - const { proto, path } = getUrlInfo(exchangeBaseUrl); - return `${proto}://add-exchange/${path}`; -} - -/** - * @deprecated - * @param param0 - * @returns - */ -export function stringifyAddContact({ - alias, - aliasType, - mailboxBaseUri: mailboxBaseUri, - mailboxIdentity: mailboxIdentity, - sourceBaseUrl, -}: Omit<AddContactUri, "type">): string { - const { proto, path } = getUrlInfo(mailboxBaseUri); - const baseUri = `${proto}://add-contact/${aliasType}/${alias}/${path}${mailboxIdentity}`; - if (sourceBaseUrl) { - return baseUri + `?sourceBaseUrl=${encodeURIComponent(sourceBaseUrl)}`; - } else { - return baseUri; - } -} - -/** - * @deprecated - * @param param0 - * @returns - */ -export function stringifyDevExperimentUri({ - devExperimentId, -}: Omit<DevExperimentUri, "type">): string { - return `taler://dev-experiment/${devExperimentId}`; -} - -/** - * @deprecated - * @param param0 - * @returns - */ -export function stringifyPayTemplateUri({ - merchantBaseUrl, - templateId, - fulfillmentUrl, - sessionId, -}: Omit<PayTemplateUriResult, "type">): string { - const { proto, path, query } = getUrlInfo(merchantBaseUrl, { - session_id: sessionId, - fulfillment_url: fulfillmentUrl, - }); - return `${proto}://pay-template/${path}${templateId}${query}`; -} - -/** - * @deprecated - * @param param0 - * @returns - */ -export function stringifyRefundUri({ - merchantBaseUrl, - orderId, -}: Omit<RefundUriResult, "type">): string { - const { proto, path } = getUrlInfo(merchantBaseUrl); - return `${proto}://refund/${path}${orderId}/`; -} - -/** - * @deprecated - * @param param0 - * @returns - */ -export function stringifyWithdrawUri({ - bankIntegrationApiBaseUrl, - withdrawalOperationId, -}: Omit<WithdrawUriResult, "type">): string { - const { proto, path } = getUrlInfo(bankIntegrationApiBaseUrl); - return `${proto}://withdraw/${path}${withdrawalOperationId}`; -} - -export function getURLHostnamePortPath(baseUrl: string) { - const path = getUrlInfo(baseUrl).path; - if (path.endsWith("/")) { - return path.substring(0, path.length - 1); - } - return path; -} - -/** - * Use baseUrl to defined http or https - * create path using host+port+pathname - * use params to create a query parameter string or empty - */ -function getUrlInfo( - baseUrl: string, - params: Record<string, string | undefined> = {}, -): { proto: string; path: string; query: string } { - const url = new URL(baseUrl); - let proto: string; - if (url.protocol === "https:") { - proto = "taler"; - } else if (url.protocol === "http:") { - proto = "taler+http"; - } else { - throw Error(`Unsupported URL protocol in ${baseUrl}`); - } - let path = url.hostname; - if (url.port) { - path = path + ":" + url.port; - } - if (url.pathname) { - path = path + url.pathname; - } - if (!path.endsWith("/")) { - path = path + "/"; - } - - const qp = new URLSearchParams(); - let withParams = false; - Object.entries(params).forEach(([name, value]) => { - if (value !== undefined) { - withParams = true; - qp.append(name, value); - } - }); - const query = withParams ? "?" + qp.toString() : ""; - - return { proto, path, query }; -} - /** * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#encoding_for_rfc3986 */ diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts @@ -39,13 +39,14 @@ import { Logger, NotificationType, parsePaytoUri, - parseTalerUri, PreparePayResultType, + Result, sampleWalletCoreTransactions, setDangerousTimetravel, setGlobalLogLevelFromString, summarizeTalerErrorDetail, TalerUriAction, + TalerUris, TransactionIdStr, TransactionMajorState, TransactionMinorState, @@ -975,7 +976,7 @@ walletCli } else { uri = await readlinePrompt("Taler URI: "); } - const parsedTalerUri = parseTalerUri(uri); + const parsedTalerUri = Result.orUndefined(TalerUris.parse(uri)); if (!parsedTalerUri) { throw Error("invalid taler URI"); } diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts @@ -38,13 +38,15 @@ import { MerchantContractVersion, PeerContractTerms, RefreshReason, + Result, TalerError, TalerErrorCode, TalerPreciseTimestamp, + TalerUriAction, + TalerUris, encodeCrock, getRandomBytes, j2s, - parseDevExperimentUri, } from "@gnu-taler/taler-util"; import { HttpRequestLibrary, @@ -92,7 +94,9 @@ export async function applyDevExperiment( uri: string, ): Promise<void> { logger.info(`applying dev experiment ${uri}`); - const parsedUri = parseDevExperimentUri(uri); + const parsedUri = Result.orUndefined( + TalerUris.parseRestricted(uri, TalerUriAction.DevExperiment), + ); if (!parsedUri) { logger.info("unable to parse dev experiment URI"); return; diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -77,9 +77,6 @@ import { MerchantPayResponse, NotificationType, OrderShortInfo, - parsePayTemplateUri, - parsePayUri, - parseTalerUri, PayCoinSelection, PaymentInsufficientBalanceDetails, PayWalletData, @@ -91,6 +88,7 @@ import { RefreshReason, RefundInfoShort, RefundPaymentInfo, + Result, safeStringifyException, ScopeInfo, ScopeType, @@ -98,8 +96,6 @@ import { SharePaymentResult, SignedTokenEnvelope, StartRefundQueryForUriResponse, - stringifyPayUri, - stringifyTalerUri, TalerError, TalerErrorCode, TalerErrorDetail, @@ -107,6 +103,7 @@ import { TalerMerchantInstanceHttpClient, TalerPreciseTimestamp, TalerUriAction, + TalerUris, TemplateType, TokenUseSig, Transaction, @@ -1910,7 +1907,7 @@ async function checkPaymentByProposalId( const transactionId = ctx.transactionId; - const talerUri = stringifyTalerUri({ + const talerUri = TalerUris.stringify({ type: TalerUriAction.Pay, merchantBaseUrl: purchaseRec.merchantBaseUrl as HostPortPath, // FIXME: change record type orderId: purchaseRec.orderId, @@ -2176,7 +2173,9 @@ export async function preparePayForUri( wex: WalletExecutionContext, talerPayUri: string, ): Promise<PreparePayResult> { - const uriResult = parsePayUri(talerPayUri); + const uriResult = Result.orUndefined( + TalerUris.parseRestricted(talerPayUri, TalerUriAction.Pay), + ); if (!uriResult) { throw TalerError.fromDetail( @@ -2210,7 +2209,9 @@ export async function preparePayForUriV2( wex: WalletExecutionContext, talerPayUri: string, ): Promise<PreparePayV2Result> { - const uriResult = parsePayUri(talerPayUri); + const uriResult = Result.orUndefined( + TalerUris.parseRestricted(talerPayUri, TalerUriAction.Pay), + ); if (!uriResult) { throw TalerError.fromDetail( @@ -2307,7 +2308,12 @@ export async function checkPayForTemplate( wex: WalletExecutionContext, req: CheckPayTemplateRequest, ): Promise<CheckPayTemplateReponse> { - const parsedUri = parsePayTemplateUri(req.talerPayTemplateUri); + const parsedUri = Result.orUndefined( + TalerUris.parseRestricted( + req.talerPayTemplateUri, + TalerUriAction.PayTemplate, + ), + ); if (!parsedUri) { throw Error("invalid taler-template URI"); } @@ -2351,7 +2357,12 @@ export async function instantiateTemplate( wex: WalletExecutionContext, req: PreparePayTemplateRequest, ): Promise<string> { - const parsedUri = parsePayTemplateUri(req.talerPayTemplateUri); + const parsedUri = Result.orUndefined( + TalerUris.parseRestricted( + req.talerPayTemplateUri, + TalerUriAction.PayTemplate, + ), + ); if (!parsedUri) { throw Error("invalid taler-template URI"); @@ -2423,7 +2434,8 @@ export async function instantiateTemplate( codecForPostOrderResponse(), ); - return stringifyPayUri({ + return TalerUris.stringify({ + type: TalerUriAction.Pay, merchantBaseUrl: parsedUri.merchantBaseUrl, orderId: resp.order_id, sessionId: parsedUri.sessionId ?? "", @@ -4193,7 +4205,8 @@ export async function sharePayment( // schedule a task to watch for the status wex.taskScheduler.startShepherdTask(ctx.taskId); - const privatePayUri = stringifyPayUri({ + const privatePayUri = TalerUris.stringify({ + type: TalerUriAction.Pay, merchantBaseUrl: merchantBaseUrl as HostPortPath, // FIXME: change function argument orderId, sessionId: result.session ?? "", @@ -4684,7 +4697,7 @@ export async function startRefundQueryForUri( wex: WalletExecutionContext, talerUri: string, ): Promise<StartRefundQueryForUriResponse> { - const parsedUri = parseTalerUri(talerUri); + const parsedUri = Result.orUndefined(TalerUris.parse(talerUri)); if (!parsedUri) { throw Error("invalid taler:// URI"); } diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -34,6 +34,7 @@ import { TalerErrorDetail, TalerPreciseTimestamp, TalerUriAction, + TalerUris, Transaction, TransactionAction, TransactionIdStr, @@ -48,8 +49,6 @@ import { encodeCrock, getRandomBytes, j2s, - stringifyPayPullUri, - stringifyTalerUri, talerPaytoFromExchangeReserve, } from "@gnu-taler/taler-util"; import { @@ -233,7 +232,8 @@ export class PeerPullCreditTransactionContext implements TransactionContext { summary: peerContractTerms.summary, iconId: peerContractTerms.icon_id, }, - talerUri: stringifyPayPullUri({ + talerUri: TalerUris.stringify({ + type: TalerUriAction.PayPull, exchangeBaseUrl: wsr.exchangeBaseUrl as HostPortPath, // FIXME: change record type contractPriv: wsr.wgInfo.contractPriv, }), @@ -270,7 +270,8 @@ export class PeerPullCreditTransactionContext implements TransactionContext { summary: peerContractTerms.summary, iconId: peerContractTerms.icon_id, }, - talerUri: stringifyPayPullUri({ + talerUri: TalerUris.stringify({ + type: TalerUriAction.PayPull, exchangeBaseUrl: pullCredit.exchangeBaseUrl as HostPortPath, // FIXME: change record type contractPriv: pullCredit.contractPriv, }), @@ -1236,7 +1237,7 @@ export async function initiatePeerPullPayment( wex.taskScheduler.startShepherdTask(ctx.taskId); return { - talerUri: stringifyTalerUri({ + talerUri: TalerUris.stringify({ type: TalerUriAction.PayPull, exchangeBaseUrl: exchangeBaseUrl as HostPortPath, // FIXME: change record type, contractPriv: contractKeyPair.priv, diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts @@ -36,12 +36,15 @@ import { PreparePeerPullDebitResponse, PurseConflict, RefreshReason, + Result, ScopeType, SelectedProspectiveCoin, TalerError, TalerErrorCode, TalerErrorDetail, TalerPreciseTimestamp, + TalerUriAction, + TalerUris, Transaction, TransactionAction, TransactionIdStr, @@ -59,7 +62,6 @@ import { getRandomBytes, j2s, makeTalerErrorDetail, - parsePayPullUri, } from "@gnu-taler/taler-util"; import { PreviousPayCoins, selectPeerCoins } from "./coinSelection.js"; import { @@ -819,7 +821,9 @@ export async function preparePeerPullDebit( let uri: PayPullUriResult | undefined; if (req.talerUri) { - uri = parsePayPullUri(req.talerUri); + uri = Result.orUndefined( + TalerUris.parseRestricted(req.talerUri, TalerUriAction.PayPull), + ); if (!uri) { throw Error("got invalid taler://pay-push URI"); } diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -30,8 +30,11 @@ import { PeerContractTerms, PreparePeerPushCreditRequest, PreparePeerPushCreditResponse, + Result, TalerErrorDetail, TalerPreciseTimestamp, + TalerUriAction, + TalerUris, Transaction, TransactionAction, TransactionIdStr, @@ -49,7 +52,6 @@ import { encodeCrock, getRandomBytes, j2s, - parsePayPushUri, talerPaytoFromExchangeReserve, } from "@gnu-taler/taler-util"; import { @@ -476,7 +478,9 @@ export async function preparePeerPushCredit( let uri: PayPushUriResult | undefined; if (req.talerUri) { - uri = parsePayPushUri(req.talerUri); + uri = Result.orUndefined( + TalerUris.parseRestricted(req.talerUri, TalerUriAction.PayPush), + ); if (!uri) { throw Error("got invalid taler://pay-push URI"); } diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.ts @@ -41,6 +41,8 @@ import { TalerPreciseTimestamp, TalerProtocolDuration, TalerProtocolTimestamp, + TalerUriAction, + TalerUris, Transaction, TransactionAction, TransactionIdStr, @@ -53,7 +55,6 @@ import { encodeCrock, getRandomBytes, j2s, - stringifyPayPushUri, throwUnexpectedResponse, } from "@gnu-taler/taler-util"; import { @@ -165,7 +166,8 @@ export class PeerPushDebitTransactionContext implements TransactionContext { switch (pushDebitRec.status) { case PeerPushDebitStatus.PendingReady: case PeerPushDebitStatus.SuspendedReady: - talerUri = stringifyPayPushUri({ + talerUri = TalerUris.stringify({ + type: TalerUriAction.PayPush, exchangeBaseUrl: pushDebitRec.exchangeBaseUrl as HostPortPath, // FIXME: change record type contractPriv: pushDebitRec.contractPriv, }); diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts @@ -279,7 +279,6 @@ import { openPromise, parseIban, parsePaytoUri, - parseTalerUri, performanceDelta, performanceNow, safeStringifyException, @@ -855,7 +854,7 @@ async function handlePrepareWithdrawExchange( wex: WalletExecutionContext, req: PrepareWithdrawExchangeRequest, ): Promise<PrepareWithdrawExchangeResponse> { - const parsedUri = parseTalerUri(req.talerUri); + const parsedUri = Result.orUndefined(TalerUris.parse(req.talerUri)); if (parsedUri?.type !== TalerUriAction.WithdrawExchange) { throw Error("expected a taler://withdraw-exchange URI"); } @@ -1040,7 +1039,7 @@ async function handleAddExchange( exchangeBaseUrl = req.exchangeBaseUrl; } else if (req.uri) { if (req.uri.startsWith("taler")) { - const p = parseTalerUri(req.uri); + const p = Result.orUndefined(TalerUris.parse(req.uri)); if (p?.type !== TalerUriAction.AddExchange) { throw Error("invalid taler://add-exchange/ URI"); } @@ -1983,11 +1982,10 @@ export async function handleGetDefaultExchanges( defaultExchanges.push({ currency: exch.currency, currencySpec: exch.currencySpec, - talerUri: TalerUris.stringify( - TalerUris.createTalerWithdrawExchange( - exch.exchangeBaseUrl as HostPortPath, - ), - ), + talerUri: TalerUris.stringify({ + type: TalerUriAction.WithdrawExchange, + exchangeBaseUrl: exch.exchangeBaseUrl as HostPortPath, + }), }); } if (wex.ws.devExperimentState.fakeDefaultExchangeDemo) { diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -59,6 +59,7 @@ import { NotificationType, ObservabilityEventType, PrepareBankIntegratedWithdrawalResponse, + Result, ScopeInfo, TalerBankIntegrationHttpClient, TalerError, @@ -67,6 +68,7 @@ import { TalerPreciseTimestamp, TalerProtocolTimestamp, TalerUriAction, + TalerUris, Transaction, TransactionAction, TransactionIdStr, @@ -102,8 +104,6 @@ import { j2s, makeErrorDetail, parsePaytoUri, - parseTalerUri, - parseWithdrawUri, succeedOrThrow, } from "@gnu-taler/taler-util"; import { @@ -1108,9 +1108,12 @@ async function processWithdrawalGroupDialogProposed( const talerWithdrawUri = withdrawalGroup.wgInfo.bankInfo.talerWithdrawUri; - const parsedUri = parseWithdrawUri(talerWithdrawUri); + const uriRes = TalerUris.parse(talerWithdrawUri); - checkLogicInvariant(!!parsedUri); + checkLogicInvariant( + Result.isOk(uriRes) && uriRes.value.type === TalerUriAction.Withdraw, + ); + const parsedUri = uriRes.value; const wopid = parsedUri.withdrawalOperationId; @@ -1167,7 +1170,9 @@ export async function getBankWithdrawalInfo( http: HttpRequestLibrary, talerWithdrawUri: string, ): Promise<BankWithdrawDetails> { - const uriResult = parseWithdrawUri(talerWithdrawUri); + const uriResult = Result.orUndefined( + TalerUris.parseRestricted(talerWithdrawUri, TalerUriAction.Withdraw), + ); if (!uriResult) { throw Error(`can't parse URL ${talerWithdrawUri}`); } @@ -3078,7 +3083,9 @@ export function getReserveRequestTimeout(r: WithdrawalGroupRecord): Duration { } export function getBankStatusUrl(talerWithdrawUri: string): URL { - const uriResult = parseWithdrawUri(talerWithdrawUri); + const uriResult = Result.orUndefined( + TalerUris.parseRestricted(talerWithdrawUri, TalerUriAction.Withdraw), + ); if (!uriResult) { throw Error(`can't parse withdrawal URL ${talerWithdrawUri}`); } @@ -3090,7 +3097,9 @@ export function getBankStatusUrl(talerWithdrawUri: string): URL { } export function getBankAbortUrl(talerWithdrawUri: string): URL { - const uriResult = parseWithdrawUri(talerWithdrawUri); + const uriResult = Result.orUndefined( + TalerUris.parseRestricted(talerWithdrawUri, TalerUriAction.Withdraw), + ); if (!uriResult) { throw Error(`can't parse withdrawal URL ${talerWithdrawUri}`); } @@ -3257,7 +3266,13 @@ async function processBankRegisterReserve( throw Error("no bank info in bank-integrated withdrawal"); } - const uriResult = parseWithdrawUri(bankInfo.talerWithdrawUri); + const uriResult = Result.orUndefined( + TalerUris.parseRestricted( + bankInfo.talerWithdrawUri, + TalerUriAction.Withdraw, + ), + ); + if (!uriResult) { throw Error(`can't parse withdrawal URL ${bankInfo.talerWithdrawUri}`); } @@ -3335,7 +3350,12 @@ async function processReserveBankStatus( throw Error("no bank info in bank-integrated withdrawal"); } - const uriResult = parseWithdrawUri(bankInfo.talerWithdrawUri); + const uriResult = Result.orUndefined( + TalerUris.parseRestricted( + bankInfo.talerWithdrawUri, + TalerUriAction.Withdraw, + ), + ); if (!uriResult) { throw Error(`can't parse withdrawal URL ${bankInfo.talerWithdrawUri}`); } @@ -3652,7 +3672,9 @@ export async function prepareBankIntegratedWithdrawal( tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(req.talerWithdrawUri), ); - const parsedUri = parseTalerUri(req.talerWithdrawUri); + const parsedUri = Result.orUndefined( + TalerUris.parseRestricted(req.talerWithdrawUri, TalerUriAction.Withdraw), + ); if (parsedUri?.type !== TalerUriAction.Withdraw) { throw TalerError.fromDetail(TalerErrorCode.WALLET_TALER_URI_MALFORMED, {}); } diff --git a/packages/taler-wallet-webextension/src/NavigationBar.tsx b/packages/taler-wallet-webextension/src/NavigationBar.tsx @@ -24,7 +24,7 @@ /** * Imports. */ -import { parseTalerUri, TalerUriAction } from "@gnu-taler/taler-util"; +import { Result, TalerUriAction, TalerUris } from "@gnu-taler/taler-util"; import { encodeCrockForURI, useTranslationContext, @@ -205,7 +205,7 @@ const talerUriActionToPageName: { }; export function getPathnameForTalerURI(talerUri: string): string | undefined { - const uri = parseTalerUri(talerUri); + const uri = Result.orUndefined(TalerUris.parse(talerUri)); if (!uri) { return undefined; } diff --git a/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx b/packages/taler-wallet-webextension/src/components/PaymentButtons.tsx @@ -22,7 +22,6 @@ import { TalerUris, TranslatedString, assertUnreachable, - parsePayUri, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { QR_Taler, useTranslationContext } from "@gnu-taler/web-util/browser"; diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/Deposit.test.ts b/packages/taler-wallet-webextension/src/cta/Deposit/Deposit.test.ts @@ -19,21 +19,19 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { describe, it, after } from "node:test"; +import { describe, it } from "node:test"; import { AmountString, Amounts, ScopeType, - parsePayUri, parsePaytoUri, } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import * as tests from "@gnu-taler/web-util/testing"; import { expect } from "chai"; import { createWalletApiMock } from "../../test-utils.js"; import { useComponentState } from "./state.js"; -import * as tests from "@gnu-taler/web-util/testing"; -import { Props } from "./index.js"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; describe("Deposit CTA states", () => { it("should be ready after loading", async () => { diff --git a/packages/taler-wallet-webextension/src/cta/DevExperiment/state.ts b/packages/taler-wallet-webextension/src/cta/DevExperiment/state.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { parseDevExperimentUri } from "@gnu-taler/taler-util"; +import { Result, TalerUriAction, TalerUris } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { useAlertContext } from "../../context/alert.js"; @@ -44,7 +44,12 @@ export function useComponentState({ const uri = talerExperimentUri === undefined ? undefined - : parseDevExperimentUri(talerExperimentUri); + : Result.orUndefined( + TalerUris.parseRestricted( + talerExperimentUri, + TalerUriAction.DevExperiment, + ), + ); if (!uri) { return { diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts @@ -21,11 +21,13 @@ import { ExchangeFullDetails, ExchangeListItem, NotificationType, + Result, ScopeInfo, ScopeType, + TalerUriAction, + TalerUris, TransactionMajorState, TransactionMinorState, - parseWithdrawExchangeUri, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; @@ -50,7 +52,12 @@ export function useComponentStateFromParams({ const [updatedExchangeByUser, setUpdatedExchangeByUser] = useState<string>(); const uriInfoHook = useAsyncAsHook(async () => { const uri = maybeTalerUri - ? parseWithdrawExchangeUri(maybeTalerUri) + ? Result.orUndefined( + TalerUris.parseRestricted( + maybeTalerUri, + TalerUriAction.WithdrawExchange, + ), + ) : undefined; const exchangeUrlByScope = diff --git a/packages/taler-wallet-webextension/src/platform/chrome.ts b/packages/taler-wallet-webextension/src/platform/chrome.ts @@ -20,7 +20,7 @@ import { TalerErrorCode, TalerUri, TalerUriAction, - stringifyTalerUri, + TalerUris, } from "@gnu-taler/taler-util"; import { WalletOperations } from "@gnu-taler/taler-wallet-core"; import { encodeCrockForURI } from "@gnu-taler/web-util/browser"; @@ -169,7 +169,7 @@ function notifyWhenAppIsReady(): Promise<void> { } function openWalletURIFromPopup(uri: TalerUri): void { - const talerUri = stringifyTalerUri(uri); + const talerUri = TalerUris.stringify(uri); //FIXME: this should redirect to just one place // the target pathname should handle what happens if the endpoint is not there // like "trying to open from popup but this uri is not handled" diff --git a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx @@ -19,7 +19,12 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { parseTalerUri, TalerUri, TalerUriAction } from "@gnu-taler/taler-util"; +import { + Result, + TalerUri, + TalerUriAction, + TalerUris, +} from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; import { Title } from "../components/styled/index.js"; @@ -117,7 +122,7 @@ function ContentByUriType({ } export function TalerActionFound({ url, onDismiss }: Props): VNode { - const talerUri = parseTalerUri(url); + const talerUri = Result.orUndefined(TalerUris.parse(url)); const { i18n } = useTranslationContext(); async function redirectToWallet(): Promise<void> { platform.openWalletURIFromPopup(talerUri!); diff --git a/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx @@ -13,7 +13,7 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { parseTalerUri, TalerUriAction } from "@gnu-taler/taler-util"; +import { Result, TalerUriAction, TalerUris } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -27,7 +27,7 @@ export interface Props { export function AddNewActionView({ onCancel }: Props): VNode { const [url, setUrl] = useState(""); - const uri = parseTalerUri(url.toLowerCase()); + const uri = Result.orUndefined(TalerUris.parse(url.toLowerCase())); const { i18n } = useTranslationContext(); async function redirectToWallet(): Promise<void> { diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -22,16 +22,16 @@ import { Amounts, + Result, ScopeInfo, TalerUri, TalerUriAction, + TalerUris, TransactionIdStr, TranslatedString, parsePaytoUri, parseScopeInfoShort, - parseTalerUri, stringifyScopeInfoShort, - stringifyTalerUri, } from "@gnu-taler/taler-util"; import { TranslationProvider, @@ -132,7 +132,7 @@ export function Application(): VNode { redirectTo( Pages.defaultCta({ uri: encodeCrockForURI( - stringifyTalerUri(talerActionUrl), + TalerUris.stringify(talerActionUrl), ), }), ); @@ -770,8 +770,8 @@ export function Application(): VNode { <Route path={Pages.ctaAddExchange} component={({ talerUri }: { talerUri: string }) => { - const tUri = parseTalerUri( - decodeCrockFromURI(talerUri).toLowerCase(), + const tUri = Result.orUndefined( + TalerUris.parse(decodeCrockFromURI(talerUri).toLowerCase()), ); const baseUrl = tUri?.type === TalerUriAction.AddExchange @@ -797,8 +797,8 @@ export function Application(): VNode { <Route path={Pages.ctaAddContact} component={({ talerUri }: { talerUri: string }) => { - const tUri = parseTalerUri( - decodeCrockFromURI(talerUri).toLowerCase(), + const tUri = Result.orUndefined( + TalerUris.parse(decodeCrockFromURI(talerUri).toLowerCase()), ); const contact = tUri?.type === TalerUriAction.AddContact diff --git a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx @@ -18,13 +18,13 @@ import { AbsoluteTime, Amounts, CoinStatus, - ExchangeListItem, ExchangeTosStatus, HostPortPath, LogLevel, NotificationType, ScopeType, - stringifyWithdrawExchange, + TalerUriAction, + TalerUris, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { @@ -32,7 +32,7 @@ import { useTranslationContext, } from "@gnu-taler/web-util/browser"; import { format } from "date-fns"; -import { Fragment, VNode, h } from "preact"; +import { Fragment, h, VNode } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; import { Pages } from "../NavigationBar.js"; import { Checkbox } from "../components/Checkbox.js"; @@ -144,7 +144,8 @@ function ExchangeActions({}: {}): VNode { } const uri = !e.masterPub ? undefined - : stringifyWithdrawExchange({ + : TalerUris.stringify({ + type: TalerUriAction.WithdrawExchange, exchangeBaseUrl: e.exchangeBaseUrl as HostPortPath, }); return ( diff --git a/packages/web-util/src/context/wallet-integration.ts b/packages/web-util/src/context/wallet-integration.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { stringifyTalerUri, TalerUri } from "@gnu-taler/taler-util"; +import { TalerUri, TalerUris } from "@gnu-taler/taler-util"; import { ComponentChildren, createContext, h, VNode } from "preact"; import { useContext } from "preact/hooks"; @@ -26,7 +26,7 @@ import { useContext } from "preact/hooks"; function createHeadMetaTag(uri: TalerUri, onNotFound?: () => void) { const meta = document.createElement("meta"); meta.setAttribute("name", "taler-uri"); - meta.setAttribute("content", stringifyTalerUri(uri)); + meta.setAttribute("content", TalerUris.stringify(uri)); document.head.appendChild(meta);