taler-typescript-core

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

commit c39124414fa013a6bd42ee48c646775429776aa5
parent 8027c7c743e0e7b5d117808c7dd50bde50ee2465
Author: Florian Dold <dold@taler.net>
Date:   Wed, 19 Aug 2026 21:42:01 +0200

wallet-core: batch purchase and token lookups

Diffstat:
Mpackages/taler-wallet-core/src/tokenFamilies.ts | 129+++++++++++++++++++++++++++++++++----------------------------------------------
1 file changed, 53 insertions(+), 76 deletions(-)

diff --git a/packages/taler-wallet-core/src/tokenFamilies.ts b/packages/taler-wallet-core/src/tokenFamilies.ts @@ -45,45 +45,31 @@ function groupDiscounts( tokens: WalletToken[], merchantInfos: MerchantInfos, ): DiscountListDetail[] { - const groupedIdx: number[] = []; - const items: DiscountListDetail[] = []; - tokens = tokens + const grouped = new Map<string, DiscountListDetail>(); + const sortedTokens = tokens .filter((t) => t.tokenFamilyHash) .sort((a, b) => a.validBefore - b.validBefore); - - // compare tokens against each other, - // except the ones already in a group. - for (let a = 0; a < tokens.length; a++) { - if (groupedIdx.includes(a)) continue; - let tokenA = tokens[a]; - const item: DiscountListDetail = { - tokenFamilyHash: tokenA.tokenFamilyHash!, - tokenIssuePubHash: tokenA.tokenIssuePubHash, - merchantBaseUrl: tokenA.merchantBaseUrl, - merchantInfo: merchantInfos[tokenA.purchaseId], - name: tokenA.name, - description: tokenA.description, - descriptionI18n: tokenA.descriptionI18n, - validityStart: tokenA.tokenIssuePub.signature_validity_start, - validityEnd: tokenA.tokenIssuePub.signature_validity_end, - tokensAvailable: 1, - }; - - for (let b = 0; b < tokens.length; b++) { - if (b === a) continue; - if (groupedIdx.includes(b)) continue; - let tokenB = tokens[b]; - if (tokenA.tokenFamilyHash === tokenB.tokenFamilyHash) { - item.tokensAvailable += 1; - groupedIdx.push(b); - } + for (const token of sortedTokens) { + const familyHash = token.tokenFamilyHash!; + const existing = grouped.get(familyHash); + if (existing) { + existing.tokensAvailable++; + continue; } - - groupedIdx.push(a); - items.push(item); + grouped.set(familyHash, { + tokenFamilyHash: familyHash, + tokenIssuePubHash: token.tokenIssuePubHash, + merchantBaseUrl: token.merchantBaseUrl, + merchantInfo: merchantInfos[token.purchaseId], + name: token.name, + description: token.description, + descriptionI18n: token.descriptionI18n, + validityStart: token.tokenIssuePub.signature_validity_start, + validityEnd: token.tokenIssuePub.signature_validity_end, + tokensAvailable: 1, + }); } - - return items; + return [...grouped.values()]; } // FIXME: unit test for subscription grouping @@ -91,43 +77,29 @@ function groupSubscriptions( tokens: WalletToken[], merchantInfos: MerchantInfos, ): SubscriptionListDetail[] { - const groupedIdx: number[] = []; - const items: SubscriptionListDetail[] = []; - tokens = tokens + const grouped = new Map<string, SubscriptionListDetail>(); + const sortedTokens = tokens .filter((t) => t.tokenFamilyHash) .sort((a, b) => a.validBefore - b.validBefore); - // compare tokens against each other, - // except the ones already in a group. - for (let a = 0; a < tokens.length; a++) { - if (groupedIdx.includes(a)) continue; - let tokenA = tokens[a]; - const item: SubscriptionListDetail = { - tokenFamilyHash: tokenA.tokenFamilyHash!, - tokenIssuePubHash: tokenA.tokenIssuePubHash, - merchantBaseUrl: tokenA.merchantBaseUrl, - merchantInfo: merchantInfos[tokenA.purchaseId], - name: tokenA.name, - description: tokenA.description, - descriptionI18n: tokenA.descriptionI18n, - validityStart: tokenA.tokenIssuePub.signature_validity_start, - validityEnd: tokenA.tokenIssuePub.signature_validity_end, - }; - - for (let b = 0; b < tokens.length; b++) { - if (b === a) continue; - if (groupedIdx.includes(b)) continue; - let tokenB = tokens[b]; - if (tokenA.tokenFamilyHash === tokenB.tokenFamilyHash) { - groupedIdx.push(b); - } + for (const token of sortedTokens) { + const familyHash = token.tokenFamilyHash!; + if (grouped.has(familyHash)) { + continue; } - - groupedIdx.push(a); - items.push(item); + grouped.set(familyHash, { + tokenFamilyHash: familyHash, + tokenIssuePubHash: token.tokenIssuePubHash, + merchantBaseUrl: token.merchantBaseUrl, + merchantInfo: merchantInfos[token.purchaseId], + name: token.name, + description: token.description, + descriptionI18n: token.descriptionI18n, + validityStart: token.tokenIssuePub.signature_validity_start, + validityEnd: token.tokenIssuePub.signature_validity_end, + }); } - - return items; + return [...grouped.values()]; } async function getMerchantInfos( @@ -136,13 +108,12 @@ async function getMerchantInfos( ): Promise<MerchantInfos> { const merchantInfos: MerchantInfos = {}; await wex.runWalletDbTx(async (tx) => { - for (const t of tokens) { - if (t.purchaseId in merchantInfos) continue; - const purchase = await tx.getPurchase(t.purchaseId); - if (!purchase) continue; + const purchaseIds = [...new Set(tokens.map((t) => t.purchaseId))]; + const purchases = await tx.getPurchasesByIds(purchaseIds); + for (const purchase of purchases) { const download = await expectProposalDownloadInTx(wex, tx, purchase); const contractData = download.contractTerms; - merchantInfos[t.purchaseId] = contractData.merchant; + merchantInfos[purchase.proposalId] = contractData.merchant; } }); return merchantInfos; @@ -154,7 +125,10 @@ export async function listDiscounts( merchantBaseUrl?: string, ): Promise<ListDiscountsResponse> { const tokens: WalletToken[] = await wex.runWalletDbTx(async (tx) => { - return (await tx.listTokens()) + const candidates = tokenIssuePubHash + ? await tx.getTokensByIssuePubHash(tokenIssuePubHash) + : await tx.listTokens(); + return candidates .filter((t) => isTokenValid(t)) .filter((t) => t.kind === MerchantContractTokenKind.Discount) .filter( @@ -178,7 +152,10 @@ export async function listSubscriptions( merchantBaseUrl?: string, ): Promise<ListSubscriptionsResponse> { const tokens: WalletToken[] = await wex.runWalletDbTx(async (tx) => { - return (await tx.listTokens()) + const candidates = tokenIssuePubHash + ? await tx.getTokensByIssuePubHash(tokenIssuePubHash) + : await tx.listTokens(); + return candidates .filter((t) => isTokenValid(t)) .filter((t) => t.kind === MerchantContractTokenKind.Subscription) .filter( @@ -204,7 +181,7 @@ export async function deleteDiscount( tokenFamilyHash: string, ): Promise<EmptyObject> { await wex.runWalletDbTx(async (tx) => { - const tokens = (await tx.listTokens()) + const tokens = (await tx.getTokensByFamilyHash(tokenFamilyHash)) .filter((t) => t.kind === MerchantContractTokenKind.Discount) .filter((t) => t.tokenFamilyHash === tokenFamilyHash); @@ -242,7 +219,7 @@ export async function deleteSubscription( tokenFamilyHash: string, ): Promise<EmptyObject> { await wex.runWalletDbTx(async (tx) => { - const tokens = (await tx.listTokens()) + const tokens = (await tx.getTokensByFamilyHash(tokenFamilyHash)) .filter((t) => t.kind === MerchantContractTokenKind.Subscription) .filter((t) => t.tokenFamilyHash === tokenFamilyHash) .sort((a, b) => a.validBefore - b.validBefore);