diff --git a/packages/demobank-ui/src/pages/AccountPage.tsx b/packages/demobank-ui/src/pages/AccountPage.tsx index edc5da5ae..820c59984 100644 --- a/packages/demobank-ui/src/pages/AccountPage.tsx +++ b/packages/demobank-ui/src/pages/AccountPage.tsx @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see */ -import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util"; +import { Amounts, HttpStatusCode, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util"; import { ErrorType, HttpResponsePaginated, @@ -27,6 +27,7 @@ import { useAccountDetails } from "../hooks/access.js"; import { LoginForm } from "./LoginForm.js"; import { PaymentOptions } from "./PaymentOptions.js"; import { notifyError } from "../hooks/notification.js"; +import { useEffect, useState } from "preact/hooks"; interface Props { account: string; @@ -34,6 +35,60 @@ interface Props { error: HttpResponsePaginated, ) => VNode; } + +export const CopyIcon = (): VNode => ( + + + + +); + +export const CopiedIcon = (): VNode => ( + + + +); + +function CopyButton({ getContent }: { getContent: () => string }): VNode { + const [copied, setCopied] = useState(false); + function copyText(): void { + navigator.clipboard.writeText(getContent() || ""); + setCopied(true); + } + useEffect(() => { + if (copied) { + setTimeout(() => { + setCopied(false); + }, 1000); + } + }, [copied]); + + if (!copied) { + return ( + + ); + } + return ( +
+ +
+ ); +} + + /** * Query account information and show QR code if there is pending withdrawal */ @@ -66,7 +121,6 @@ export function AccountPage({ account, onLoadNotOk }: Props): VNode {
Payto from server is not valid "{data.paytoUri}"
); } - const accountNumber = payto.iban; const balanceIsDebit = data.balance.credit_debit_indicator == "debit"; const limit = balanceIsDebit ? Amounts.sub(debitThreshold, balance).amount @@ -76,8 +130,7 @@ export function AccountPage({ account, onLoadNotOk }: Props): VNode {

- Welcome, {accountNumber ? `${account} (${accountNumber})` : account} - ! + Welcome, {account} ({payto.iban})! stringifyPaytoUri(payto)} />

diff --git a/packages/merchant-backoffice-ui/src/components/form/InputSelector.tsx b/packages/merchant-backoffice-ui/src/components/form/InputSelector.tsx index 8a57a9de8..a8dad5d89 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputSelector.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputSelector.tsx @@ -44,7 +44,7 @@ export function InputSelector({ fromStr = defaultFromString, toStr = defaultToString, }: Props): VNode { - const { error, value, onChange } = useField(name); + const { error, value, onChange, required } = useField(name); return (
@@ -58,8 +58,8 @@ export function InputSelector({
-
-

+

+

+ {help}

+ {required && ( + + + + )} {error &&

{error}

}
diff --git a/packages/merchant-backoffice-ui/src/declaration.d.ts b/packages/merchant-backoffice-ui/src/declaration.d.ts index 8afa2f781..db3122266 100644 --- a/packages/merchant-backoffice-ui/src/declaration.d.ts +++ b/packages/merchant-backoffice-ui/src/declaration.d.ts @@ -1331,12 +1331,13 @@ export namespace MerchantBackend { } namespace Webhooks { + type MerchantWebhookType = "pay" | "refund"; interface WebhookAddDetails { // Webhook ID to use. webhook_id: string; // The event of the webhook: why the webhook is used. - event_type: string; + event_type: MerchantWebhookType; // URL of the webhook where the customer will be redirected. url: string; diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx index ed11f86a5..7b07e689e 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/CreatePage.tsx @@ -33,6 +33,7 @@ import { InputDuration } from "../../../../components/form/InputDuration.js"; import { InputNumber } from "../../../../components/form/InputNumber.js"; import { useBackendContext } from "../../../../context/backend.js"; import { MerchantBackend } from "../../../../declaration.js"; +import { InputSelector } from "../../../../components/form/InputSelector.js"; type Entity = MerchantBackend.Webhooks.WebhookAddDetails; @@ -50,7 +51,9 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { const errors: FormErrors = { webhook_id: !state.webhook_id ? i18n.str`required` : undefined, - event_type: !state.event_type ? i18n.str`required` : undefined, + event_type: !state.event_type ? i18n.str`required` + : state.event_type !== "pay" && state.event_type !== "refund" ? i18n.str`it should be "pay" or "refund"` + : undefined, http_method: !state.http_method ? i18n.str`required` : !validMethod.includes(state.http_method) @@ -84,16 +87,30 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { label={i18n.str`ID`} tooltip={i18n.str`Webhook ID to use`} /> - + - + + name="url" label={i18n.str`URL`} diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts index 940e4258a..c9202c60e 100644 --- a/packages/taler-harness/src/harness/harness.ts +++ b/packages/taler-harness/src/harness/harness.ts @@ -51,8 +51,8 @@ import { stringToBytes, TalerError, TalerProtocolDuration, - TipCreateConfirmation, - TipCreateRequest, + RewardCreateConfirmation, + RewardCreateRequest, TippingReserveStatus, WalletNotification, } from "@gnu-taler/taler-util"; @@ -1751,8 +1751,8 @@ export namespace MerchantPrivateApi { export async function giveTip( merchantService: MerchantServiceInterface, instance: string, - req: TipCreateRequest, - ): Promise { + req: RewardCreateRequest, + ): Promise { const reqUrl = new URL( `private/tips`, merchantService.makeInstanceBaseUrl(instance), diff --git a/packages/taler-harness/src/integrationtests/test-age-restrictions-merchant.ts b/packages/taler-harness/src/integrationtests/test-age-restrictions-merchant.ts index c4db7022d..919097deb 100644 --- a/packages/taler-harness/src/integrationtests/test-age-restrictions-merchant.ts +++ b/packages/taler-harness/src/integrationtests/test-age-restrictions-merchant.ts @@ -191,12 +191,12 @@ export async function runAgeRestrictionsMerchantTest(t: GlobalTestState) { const walletTipping = new WalletCli(t, "age-tipping"); - const ptr = await walletTipping.client.call(WalletApiOperation.PrepareTip, { - talerTipUri: tip.taler_tip_uri, + const ptr = await walletTipping.client.call(WalletApiOperation.PrepareReward, { + talerRewardUri: tip.taler_reward_uri, }); - await walletTipping.client.call(WalletApiOperation.AcceptTip, { - walletTipId: ptr.walletTipId, + await walletTipping.client.call(WalletApiOperation.AcceptReward, { + walletRewardId: ptr.walletRewardId, }); await walletTipping.runUntilDone(); diff --git a/packages/taler-harness/src/integrationtests/test-age-restrictions-peer.ts b/packages/taler-harness/src/integrationtests/test-age-restrictions-peer.ts index eae04cd2e..d15858322 100644 --- a/packages/taler-harness/src/integrationtests/test-age-restrictions-peer.ts +++ b/packages/taler-harness/src/integrationtests/test-age-restrictions-peer.ts @@ -17,13 +17,20 @@ /** * Imports. */ -import { AbsoluteTime, Duration } from "@gnu-taler/taler-util"; +import { + AbsoluteTime, + Duration, + NotificationType, + TransactionMajorState, + TransactionMinorState, +} from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { defaultCoinConfig } from "../harness/denomStructures.js"; import { GlobalTestState, WalletCli } from "../harness/harness.js"; import { - createSimpleTestkudosEnvironment, - withdrawViaBank, + createSimpleTestkudosEnvironmentV2, + createWalletDaemonWithClient, + withdrawViaBankV2, } from "../harness/helpers.js"; /** @@ -32,12 +39,7 @@ import { export async function runAgeRestrictionsPeerTest(t: GlobalTestState) { // Set up test environment - const { - wallet: walletOne, - bank, - exchange, - merchant, - } = await createSimpleTestkudosEnvironment( + const { bank, exchange } = await createSimpleTestkudosEnvironmentV2( t, defaultCoinConfig.map((x) => x("TESTKUDOS")), { @@ -45,20 +47,29 @@ export async function runAgeRestrictionsPeerTest(t: GlobalTestState) { }, ); - const walletTwo = new WalletCli(t, "walletTwo"); - const walletThree = new WalletCli(t, "walletThree"); + const w1 = await createWalletDaemonWithClient(t, { + name: "w1", + persistent: true, + }); + const w2 = await createWalletDaemonWithClient(t, { + name: "w2", + persistent: true, + }); + + const wallet1 = w1.walletClient; + const wallet2 = w2.walletClient; { - const wallet = walletOne; - - await withdrawViaBank(t, { - wallet, + const withdrawalRes = await withdrawViaBankV2(t, { + walletClient: wallet1, bank, exchange, amount: "TESTKUDOS:20", restrictAge: 13, }); + await withdrawalRes.withdrawalFinishedCond; + const purse_expiration = AbsoluteTime.toProtocolTimestamp( AbsoluteTime.addDuration( AbsoluteTime.now(), @@ -66,7 +77,7 @@ export async function runAgeRestrictionsPeerTest(t: GlobalTestState) { ), ); - const initResp = await wallet.client.call( + const initResp = await wallet1.client.call( WalletApiOperation.InitiatePeerPushDebit, { partialContractTerms: { @@ -77,20 +88,35 @@ export async function runAgeRestrictionsPeerTest(t: GlobalTestState) { }, ); - await wallet.runUntilDone(); + const peerPushReadyCond = wallet1.waitForNotificationCond( + (x) => + x.type === NotificationType.TransactionStateTransition && + x.newTxState.major === TransactionMajorState.Pending && + x.newTxState.minor === TransactionMinorState.Ready && + x.transactionId === initResp.transactionId, + ); - const checkResp = await walletTwo.client.call( + await peerPushReadyCond; + + const checkResp = await wallet2.call( WalletApiOperation.PreparePeerPushCredit, { talerUri: initResp.talerUri, }, ); - await walletTwo.client.call(WalletApiOperation.ConfirmPeerPushCredit, { + await wallet2.call(WalletApiOperation.ConfirmPeerPushCredit, { peerPushPaymentIncomingId: checkResp.peerPushPaymentIncomingId, }); - await walletTwo.runUntilDone(); + const peerPullCreditDoneCond = wallet2.waitForNotificationCond( + (x) => + x.type === NotificationType.TransactionStateTransition && + x.newTxState.major === TransactionMajorState.Done && + x.transactionId === checkResp.transactionId, + ); + + await peerPullCreditDoneCond; } } diff --git a/packages/taler-harness/src/integrationtests/test-peer-to-peer-pull.ts b/packages/taler-harness/src/integrationtests/test-peer-to-peer-pull.ts index 30287b51b..5b55b1de1 100644 --- a/packages/taler-harness/src/integrationtests/test-peer-to-peer-pull.ts +++ b/packages/taler-harness/src/integrationtests/test-peer-to-peer-pull.ts @@ -23,6 +23,7 @@ import { j2s, NotificationType, TransactionMajorState, + TransactionMinorState, WalletNotification, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; @@ -46,12 +47,14 @@ export async function runPeerToPeerPullTest(t: GlobalTestState) { const w1 = await createWalletDaemonWithClient(t, { name: "w1", + persistent: true, handleNotification(wn) { allW1Notifications.push(wn); }, }); const w2 = await createWalletDaemonWithClient(t, { name: "w2", + persistent: true, handleNotification(wn) { allW2Notifications.push(wn); }, @@ -89,6 +92,15 @@ export async function runPeerToPeerPullTest(t: GlobalTestState) { }, ); + const peerPullCreditReadyCond = wallet1.waitForNotificationCond( + (x) => x.type === NotificationType.TransactionStateTransition && + x.transactionId === resp.transactionId && + x.newTxState.major === TransactionMajorState.Pending && + x.newTxState.minor === TransactionMinorState.Ready, + ); + + await peerPullCreditReadyCond; + const checkResp = await wallet2.client.call( WalletApiOperation.PreparePeerPullDebit, { @@ -98,8 +110,6 @@ export async function runPeerToPeerPullTest(t: GlobalTestState) { console.log(`checkResp: ${j2s(checkResp)}`); - // FIXME: The wallet should emit a more appropriate notification here. - // Yes, it's technically a withdrawal. const peerPullCreditDoneCond = wallet1.waitForNotificationCond( (x) => x.type === NotificationType.TransactionStateTransition && x.transactionId === resp.transactionId && diff --git a/packages/taler-harness/src/integrationtests/test-tipping.ts b/packages/taler-harness/src/integrationtests/test-tipping.ts index ff6fc9ceb..332f702d7 100644 --- a/packages/taler-harness/src/integrationtests/test-tipping.ts +++ b/packages/taler-harness/src/integrationtests/test-tipping.ts @@ -99,17 +99,17 @@ export async function runTippingTest(t: GlobalTestState) { console.log("created tip", tip); const doTip = async (): Promise => { - const ptr = await wallet.client.call(WalletApiOperation.PrepareTip, { - talerTipUri: tip.taler_tip_uri, + const ptr = await wallet.client.call(WalletApiOperation.PrepareReward, { + talerRewardUri: tip.taler_reward_uri, }); console.log(ptr); - t.assertAmountEquals(ptr.tipAmountRaw, "TESTKUDOS:5"); - t.assertAmountEquals(ptr.tipAmountEffective, "TESTKUDOS:4.85"); + t.assertAmountEquals(ptr.rewardAmountRaw, "TESTKUDOS:5"); + t.assertAmountEquals(ptr.rewardAmountEffective, "TESTKUDOS:4.85"); - await wallet.client.call(WalletApiOperation.AcceptTip, { - walletTipId: ptr.walletTipId, + await wallet.client.call(WalletApiOperation.AcceptReward, { + walletRewardId: ptr.walletRewardId, }); await wallet.runUntilDone(); @@ -127,7 +127,7 @@ export async function runTippingTest(t: GlobalTestState) { console.log("Transactions:", JSON.stringify(txns, undefined, 2)); - t.assertDeepEqual(txns.transactions[0].type, "tip"); + t.assertDeepEqual(txns.transactions[0].type, "reward"); t.assertDeepEqual(txns.transactions[0].txState.major, TransactionMajorState.Done); t.assertAmountEquals( txns.transactions[0].amountEffective, diff --git a/packages/taler-util/src/backup-types.ts b/packages/taler-util/src/backup-types.ts index 2920838dd..0211ff740 100644 --- a/packages/taler-util/src/backup-types.ts +++ b/packages/taler-util/src/backup-types.ts @@ -499,7 +499,7 @@ export interface BackupRecoupGroup { export enum BackupCoinSourceType { Withdraw = "withdraw", Refresh = "refresh", - Tip = "tip", + Reward = "reward", } /** @@ -546,7 +546,7 @@ export interface BackupRefreshCoinSource { * Metadata about a coin obtained from a tip. */ export interface BackupTipCoinSource { - type: BackupCoinSourceType.Tip; + type: BackupCoinSourceType.Reward; /** * Wallet's identifier for the tip that this coin diff --git a/packages/taler-util/src/http-impl.node.ts b/packages/taler-util/src/http-impl.node.ts index 639043201..07648a28d 100644 --- a/packages/taler-util/src/http-impl.node.ts +++ b/packages/taler-util/src/http-impl.node.ts @@ -183,7 +183,16 @@ export class HttpLibImpl implements HttpRequestLibrary { resolve(resp); }); res.on("error", (e) => { - reject(e); + const err = TalerError.fromDetail( + TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, + { + requestUrl: url, + requestMethod: method, + httpStatusCode: 0, + }, + `Error in HTTP response handler: ${e.message}`, + ); + reject(err); }); }; @@ -197,7 +206,16 @@ export class HttpLibImpl implements HttpRequestLibrary { } req.on("error", (e: Error) => { - reject(e); + const err = TalerError.fromDetail( + TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, + { + requestUrl: url, + requestMethod: method, + httpStatusCode: 0, + }, + `Error in HTTP request: ${e.message}`, + ); + reject(err); }); if (reqBody) { diff --git a/packages/taler-util/src/merchant-api-types.ts b/packages/taler-util/src/merchant-api-types.ts index d7a5cf576..9f00173f2 100644 --- a/packages/taler-util/src/merchant-api-types.ts +++ b/packages/taler-util/src/merchant-api-types.ts @@ -290,22 +290,22 @@ export interface ReserveStatusEntry { active: boolean; } -export interface TipCreateConfirmation { +export interface RewardCreateConfirmation { // Unique tip identifier for the tip that was created. - tip_id: string; + reward_id: string; // taler://tip URI for the tip - taler_tip_uri: string; + taler_reward_uri: string; // URL that will directly trigger processing // the tip when the browser is redirected to it - tip_status_url: string; + reward_status_url: string; - // when does the tip expire - tip_expiration: AbsoluteTime; + // when does the reward expire + reward_expiration: AbsoluteTime; } -export interface TipCreateRequest { +export interface RewardCreateRequest { // Amount that the customer should be tipped amount: AmountString; diff --git a/packages/taler-util/src/taleruri.test.ts b/packages/taler-util/src/taleruri.test.ts index eb74436cb..0dcf92252 100644 --- a/packages/taler-util/src/taleruri.test.ts +++ b/packages/taler-util/src/taleruri.test.ts @@ -21,7 +21,7 @@ import { parsePayUri, parseRefundUri, parseRestoreUri, - parseTipUri, + parseRewardUri, parseWithdrawExchangeUri, parseWithdrawUri, stringifyPayPushUri, @@ -161,7 +161,7 @@ test("taler refund uri parsing with instance", (t) => { test("taler tip pickup uri", (t) => { const url1 = "taler://tip/merchant.example.com/tipid"; - const r1 = parseTipUri(url1); + const r1 = parseRewardUri(url1); if (!r1) { t.fail(); return; @@ -171,7 +171,7 @@ test("taler tip pickup uri", (t) => { test("taler tip pickup uri with instance", (t) => { const url1 = "taler://tip/merchant.example.com/instances/tipm/tipid"; - const r1 = parseTipUri(url1); + const r1 = parseRewardUri(url1); if (!r1) { t.fail(); return; @@ -182,7 +182,7 @@ test("taler tip pickup uri with instance", (t) => { test("taler tip pickup uri with instance and prefix", (t) => { const url1 = "taler://tip/merchant.example.com/my/pfx/tipm/tipid"; - const r1 = parseTipUri(url1); + const r1 = parseRewardUri(url1); if (!r1) { t.fail(); return; @@ -367,6 +367,6 @@ test("taler withdraw exchange URI with amount (stringify)", (t) => { }); t.deepEqual( url, - "taler://withdraw-exchange/exchange.demo.taler.net/JFX1NE38C65A5XT8VSNQXX7R7BBG4GNZ63F5T7Y6859V4J8KBKF0?a=KUDOS%3A19", + "taler://withdraw-exchange/exchange.demo.taler.net/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0?a=KUDOS%3A19", ); }); diff --git a/packages/taler-util/src/taleruri.ts b/packages/taler-util/src/taleruri.ts index fc140811b..777cb5245 100644 --- a/packages/taler-util/src/taleruri.ts +++ b/packages/taler-util/src/taleruri.ts @@ -26,7 +26,7 @@ export type TalerUri = | PayPushUriResult | BackupRestoreUri | RefundUriResult - | TipUriResult + | RewardUriResult | WithdrawUriResult | ExchangeUri | WithdrawExchangeUri @@ -60,8 +60,8 @@ export interface RefundUriResult { orderId: string; } -export interface TipUriResult { - type: TalerUriAction.Tip; +export interface RewardUriResult { + type: TalerUriAction.Reward; merchantBaseUrl: string; merchantTipId: string; } @@ -167,7 +167,7 @@ export enum TalerUriAction { Pay = "pay", Withdraw = "withdraw", Refund = "refund", - Tip = "tip", + Reward = "reward", PayPull = "pay-pull", PayPush = "pay-push", PayTemplate = "pay-template", @@ -212,7 +212,7 @@ const parsers: { [A in TalerUriAction]: Parser } = { [TalerUriAction.PayTemplate]: parsePayTemplateUri, [TalerUriAction.Restore]: parseRestoreUri, [TalerUriAction.Refund]: parseRefundUri, - [TalerUriAction.Tip]: parseTipUri, + [TalerUriAction.Reward]: parseRewardUri, [TalerUriAction.Withdraw]: parseWithdrawUri, [TalerUriAction.DevExperiment]: parseDevExperimentUri, [TalerUriAction.Exchange]: parseExchangeUri, @@ -255,8 +255,8 @@ export function stringifyTalerUri(uri: TalerUri): string { case TalerUriAction.Refund: { return stringifyRefundUri(uri); } - case TalerUriAction.Tip: { - return stringifyTipUri(uri); + case TalerUriAction.Reward: { + return stringifyRewardUri(uri); } case TalerUriAction.Withdraw: { return stringifyWithdrawUri(uri); @@ -394,11 +394,11 @@ export function parsePayPullUri(s: string): PayPullUriResult | undefined { } /** - * Parse a taler[+http]://tip URI. + * Parse a taler[+http]://reward URI. * Return undefined if not passed a valid URI. */ -export function parseTipUri(s: string): TipUriResult | undefined { - const pi = parseProtoInfo(s, "tip"); +export function parseRewardUri(s: string): RewardUriResult | undefined { + const pi = parseProtoInfo(s, "reward"); if (!pi) { return undefined; } @@ -416,7 +416,7 @@ export function parseTipUri(s: string): TipUriResult | undefined { ); return { - type: TalerUriAction.Tip, + type: TalerUriAction.Reward, merchantBaseUrl, merchantTipId: tipId, }; @@ -699,12 +699,12 @@ export function stringifyRefundUri({ const { proto, path } = getUrlInfo(merchantBaseUrl); return `${proto}://refund/${path}${orderId}`; } -export function stringifyTipUri({ +export function stringifyRewardUri({ merchantBaseUrl, merchantTipId, -}: Omit): string { +}: Omit): string { const { proto, path } = getUrlInfo(merchantBaseUrl); - return `${proto}://tip/${path}${merchantTipId}`; + return `${proto}://reward/${path}${merchantTipId}`; } export function stringifyExchangeUri({ diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts index a498d3471..2d278e3e8 100644 --- a/packages/taler-util/src/transactions-types.ts +++ b/packages/taler-util/src/transactions-types.ts @@ -186,7 +186,7 @@ export type Transaction = | TransactionWithdrawal | TransactionPayment | TransactionRefund - | TransactionTip + | TransactionReward | TransactionRefresh | TransactionDeposit | TransactionPeerPullCredit @@ -201,7 +201,7 @@ export enum TransactionType { Payment = "payment", Refund = "refund", Refresh = "refresh", - Tip = "tip", + Reward = "reward", Deposit = "deposit", PeerPushDebit = "peer-push-debit", PeerPushCredit = "peer-push-credit", @@ -591,8 +591,8 @@ export interface TransactionRefund extends TransactionCommon { paymentInfo: RefundPaymentInfo | undefined; } -export interface TransactionTip extends TransactionCommon { - type: TransactionType.Tip; +export interface TransactionReward extends TransactionCommon { + type: TransactionType.Reward; // Raw amount of the tip, without extra fees that apply amountRaw: AmountString; diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 93653240b..38e5787ba 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -605,7 +605,7 @@ export interface PrepareTipResult { * * @deprecated use transactionId instead */ - walletTipId: string; + walletRewardId: string; /** * Tip transaction ID. @@ -620,13 +620,13 @@ export interface PrepareTipResult { /** * Amount that the merchant gave. */ - tipAmountRaw: AmountString; + rewardAmountRaw: AmountString; /** * Amount that arrived at the wallet. * Might be lower than the raw amount due to fees. */ - tipAmountEffective: AmountString; + rewardAmountEffective: AmountString; /** * Base URL of the merchant backend giving then tip. @@ -654,14 +654,14 @@ export interface AcceptTipResponse { export const codecForPrepareTipResult = (): Codec => buildCodecForObject() .property("accepted", codecForBoolean()) - .property("tipAmountRaw", codecForAmountString()) - .property("tipAmountEffective", codecForAmountString()) + .property("rewardAmountRaw", codecForAmountString()) + .property("rewardAmountEffective", codecForAmountString()) .property("exchangeBaseUrl", codecForString()) .property("merchantBaseUrl", codecForString()) .property("expirationTimestamp", codecForTimestamp) - .property("walletTipId", codecForString()) + .property("walletRewardId", codecForString()) .property("transactionId", codecForString()) - .build("PrepareTipResult"); + .build("PrepareRewardResult"); export interface BenchmarkResult { time: { [s: string]: number }; @@ -994,6 +994,9 @@ export interface ExchangeDetailedResponse { } export interface WalletCoreVersion { + /** + * @deprecated + */ hash: string | undefined; version: string; exchange: string; @@ -1930,23 +1933,23 @@ export const codecForStartRefundQueryRequest = .property("transactionId", codecForTransactionIdStr()) .build("StartRefundQueryRequest"); -export interface PrepareTipRequest { - talerTipUri: string; +export interface PrepareRewardRequest { + talerRewardUri: string; } -export const codecForPrepareTipRequest = (): Codec => - buildCodecForObject() - .property("talerTipUri", codecForString()) - .build("PrepareTipRequest"); +export const codecForPrepareRewardRequest = (): Codec => + buildCodecForObject() + .property("talerRewardUri", codecForString()) + .build("PrepareRewardRequest"); -export interface AcceptTipRequest { - walletTipId: string; +export interface AcceptRewardRequest { + walletRewardId: string; } -export const codecForAcceptTipRequest = (): Codec => - buildCodecForObject() - .property("walletTipId", codecForString()) - .build("AcceptTipRequest"); +export const codecForAcceptTipRequest = (): Codec => + buildCodecForObject() + .property("walletRewardId", codecForString()) + .build("AcceptRewardRequest"); export interface FailTransactionRequest { transactionId: TransactionIdStr; diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts index 4e56a40f4..e2787db66 100644 --- a/packages/taler-wallet-cli/src/index.ts +++ b/packages/taler-wallet-cli/src/index.ts @@ -651,13 +651,13 @@ walletCli alwaysYes: args.handleUri.autoYes, }); break; - case TalerUriAction.Tip: { - const res = await wallet.client.call(WalletApiOperation.PrepareTip, { - talerTipUri: uri, + case TalerUriAction.Reward: { + const res = await wallet.client.call(WalletApiOperation.PrepareReward, { + talerRewardUri: uri, }); console.log("tip status", res); - await wallet.client.call(WalletApiOperation.AcceptTip, { - walletTipId: res.walletTipId, + await wallet.client.call(WalletApiOperation.AcceptReward, { + walletRewardId: res.walletRewardId, }); break; } diff --git a/packages/taler-wallet-core/.gitignore b/packages/taler-wallet-core/.gitignore index cb5a51ac5..13d7285e1 100644 --- a/packages/taler-wallet-core/.gitignore +++ b/packages/taler-wallet-core/.gitignore @@ -1,2 +1,3 @@ /lib /coverage +/src/version.json diff --git a/packages/taler-wallet-core/package.json b/packages/taler-wallet-core/package.json index bf4efe814..25e7c2eb2 100644 --- a/packages/taler-wallet-core/package.json +++ b/packages/taler-wallet-core/package.json @@ -85,4 +85,4 @@ "lib/**/*test.*" ] } -} \ No newline at end of file +} diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts index 20d696e97..01d2677dc 100644 --- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts @@ -28,7 +28,6 @@ import { AgeCommitmentProof, AgeRestriction, AmountJson, - AmountLike, Amounts, AmountString, amountToBuffer, @@ -64,7 +63,6 @@ import { hashCoinPub, hashDenomPub, hashTruncate32, - j2s, kdf, kdfKw, keyExchangeEcdhEddsa, @@ -81,16 +79,13 @@ import { rsaVerify, setupTipPlanchet, stringToBytes, - TalerProtocolDuration, TalerProtocolTimestamp, TalerSignaturePurpose, timestampRoundedToBuffer, UnblindedSignature, - validateIban, WireFee, WithdrawalPlanchet, } from "@gnu-taler/taler-util"; -import bigint from "big-integer"; // FIXME: Crypto should not use DB Types! import { DenominationRecord } from "../db.js"; import { @@ -108,7 +103,6 @@ import { EncryptContractForDepositResponse, EncryptContractRequest, EncryptContractResponse, - EncryptedContract, SignDeletePurseRequest, SignDeletePurseResponse, SignPurseMergeRequest, diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 6a7a26f2f..3d2878d93 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -677,7 +677,7 @@ export interface PlanchetRecord { export enum CoinSourceType { Withdraw = "withdraw", Refresh = "refresh", - Tip = "tip", + Reward = "reward", } export interface WithdrawCoinSource { @@ -705,13 +705,13 @@ export interface RefreshCoinSource { oldCoinPub: string; } -export interface TipCoinSource { - type: CoinSourceType.Tip; - walletTipId: string; +export interface RewardCoinSource { + type: CoinSourceType.Reward; + walletRewardId: string; coinIndex: number; } -export type CoinSource = WithdrawCoinSource | RefreshCoinSource | TipCoinSource; +export type CoinSource = WithdrawCoinSource | RefreshCoinSource | RewardCoinSource; /** * CoinRecord as stored in the "coins" data store @@ -815,9 +815,9 @@ export interface CoinAllocation { } /** - * Status of a tip we got from a merchant. + * Status of a reward we got from a merchant. */ -export interface TipRecord { +export interface RewardRecord { /** * Has the user accepted the tip? Only after the tip has been accepted coins * withdrawn from the tip may be used. @@ -827,17 +827,17 @@ export interface TipRecord { /** * The tipped amount. */ - tipAmountRaw: AmountString; + rewardAmountRaw: AmountString; /** * Effect on the balance (including fees etc). */ - tipAmountEffective: AmountString; + rewardAmountEffective: AmountString; /** * Timestamp, the tip can't be picked up anymore after this deadline. */ - tipExpiration: TalerProtocolTimestamp; + rewardExpiration: TalerProtocolTimestamp; /** * The exchange that will sign our coins, chosen by the merchant. @@ -863,7 +863,7 @@ export interface TipRecord { /** * Tip ID chosen by the wallet. */ - walletTipId: string; + walletRewardId: string; /** * Secret seed used to derive planchets for this tip. @@ -871,9 +871,9 @@ export interface TipRecord { secretSeed: string; /** - * The merchant's identifier for this tip. + * The merchant's identifier for this reward. */ - merchantTipId: string; + merchantRewardId: string; createdTimestamp: TalerPreciseTimestamp; @@ -888,10 +888,10 @@ export interface TipRecord { */ pickedUpTimestamp: TalerPreciseTimestamp | undefined; - status: TipRecordStatus; + status: RewardRecordStatus; } -export enum TipRecordStatus { +export enum RewardRecordStatus { PendingPickup = 10, SuspendidPickup = 20, @@ -1420,7 +1420,7 @@ export interface KycPendingInfo { } /** * Group of withdrawal operations that need to be executed. - * (Either for a normal withdrawal or from a tip.) + * (Either for a normal withdrawal or from a reward.) * * The withdrawal group record is only created after we know * the coin selection we want to withdraw. @@ -2480,12 +2480,12 @@ export const WalletStoresV1 = { ]), }, ), - tips: describeStore( - "tips", - describeContents({ keyPath: "walletTipId" }), + rewards: describeStore( + "rewards", + describeContents({ keyPath: "walletRewardId" }), { - byMerchantTipIdAndBaseUrl: describeIndex("byMerchantTipIdAndBaseUrl", [ - "merchantTipId", + byMerchantTipIdAndBaseUrl: describeIndex("byMerchantRewardIdAndBaseUrl", [ + "merchantRewardId", "merchantBaseUrl", ]), byStatus: describeIndex("byStatus", "status", { @@ -2935,22 +2935,6 @@ export const walletDbFixups: FixupDescription[] = [ }); }, }, - { - name: "TipRecordRecord_status_add", - async fn(tx): Promise { - await tx.tips.iter().forEachAsync(async (r) => { - // Remove legacy transactions that don't have the totalCost field yet. - if (r.status == null) { - if (r.pickedUpTimestamp) { - r.status = TipRecordStatus.Done; - } else { - r.status = TipRecordStatus.PendingPickup; - } - await tx.tips.put(r); - } - }); - }, - }, { name: "CoinAvailabilityRecord_visibleCoinCount_add", async fn(tx): Promise { diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts index 21ba5dc37..c9446a05f 100644 --- a/packages/taler-wallet-core/src/operations/backup/export.ts +++ b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -96,7 +96,7 @@ export async function exportBackup( x.purchases, x.refreshGroups, x.backupProviders, - x.tips, + x.rewards, x.recoupGroups, x.withdrawalGroups, ]) @@ -184,12 +184,12 @@ export async function exportBackup( }); }); - await tx.tips.iter().forEach((tip) => { + await tx.rewards.iter().forEach((tip) => { backupTips.push({ exchange_base_url: tip.exchangeBaseUrl, merchant_base_url: tip.merchantBaseUrl, - merchant_tip_id: tip.merchantTipId, - wallet_tip_id: tip.walletTipId, + merchant_tip_id: tip.merchantRewardId, + wallet_tip_id: tip.walletRewardId, next_url: tip.next_url, secret_seed: tip.secretSeed, selected_denoms: tip.denomsSel.selectedDenoms.map((x) => ({ @@ -199,8 +199,8 @@ export async function exportBackup( timestamp_finished: tip.pickedUpTimestamp, timestamp_accepted: tip.acceptedTimestamp, timestamp_created: tip.createdTimestamp, - timestamp_expiration: tip.tipExpiration, - tip_amount_raw: Amounts.stringify(tip.tipAmountRaw), + timestamp_expiration: tip.rewardExpiration, + tip_amount_raw: Amounts.stringify(tip.rewardAmountRaw), selected_denoms_uid: tip.denomSelUid, }); }); @@ -244,11 +244,11 @@ export async function exportBackup( refresh_group_id: coin.coinSource.refreshGroupId, }; break; - case CoinSourceType.Tip: + case CoinSourceType.Reward: bcs = { - type: BackupCoinSourceType.Tip, + type: BackupCoinSourceType.Reward, coin_index: coin.coinSource.coinIndex, - wallet_tip_id: coin.coinSource.walletTipId, + wallet_tip_id: coin.coinSource.walletRewardId, }; break; case CoinSourceType.Withdraw: diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index b161aa8f2..a53b624e8 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -56,7 +56,7 @@ import { WithdrawalGroupStatus, WithdrawalRecordType, RefreshOperationStatus, - TipRecordStatus, + RewardRecordStatus, } from "../../db.js"; import { InternalWalletState } from "../../internal-wallet-state.js"; import { assertUnreachable } from "../../util/assertUnreachable.js"; @@ -250,11 +250,11 @@ export async function importCoin( refreshGroupId: backupCoin.coin_source.refresh_group_id, }; break; - case BackupCoinSourceType.Tip: + case BackupCoinSourceType.Reward: coinSource = { - type: CoinSourceType.Tip, + type: CoinSourceType.Reward, coinIndex: backupCoin.coin_source.coin_index, - walletTipId: backupCoin.coin_source.wallet_tip_id, + walletRewardId: backupCoin.coin_source.wallet_tip_id, }; break; case BackupCoinSourceType.Withdraw: @@ -311,7 +311,7 @@ export async function importBackup( x.purchases, x.refreshGroups, x.backupProviders, - x.tips, + x.rewards, x.recoupGroups, x.withdrawalGroups, x.tombstones, @@ -812,13 +812,13 @@ export async function importBackup( for (const backupTip of backupBlob.tips) { const ts = constructTombstone({ - tag: TombstoneTag.DeleteTip, + tag: TombstoneTag.DeleteReward, walletTipId: backupTip.wallet_tip_id, }); if (tombstoneSet.has(ts)) { continue; } - const existingTip = await tx.tips.get(backupTip.wallet_tip_id); + const existingTip = await tx.rewards.get(backupTip.wallet_tip_id); if (!existingTip) { const tipAmountRaw = Amounts.parseOrThrow(backupTip.tip_amount_raw); const denomsSel = await getDenomSelStateFromBackup( @@ -827,22 +827,22 @@ export async function importBackup( backupTip.exchange_base_url, backupTip.selected_denoms, ); - await tx.tips.put({ + await tx.rewards.put({ acceptedTimestamp: backupTip.timestamp_accepted, createdTimestamp: backupTip.timestamp_created, denomsSel, next_url: backupTip.next_url, exchangeBaseUrl: backupTip.exchange_base_url, merchantBaseUrl: backupTip.exchange_base_url, - merchantTipId: backupTip.merchant_tip_id, + merchantRewardId: backupTip.merchant_tip_id, pickedUpTimestamp: backupTip.timestamp_finished, secretSeed: backupTip.secret_seed, - tipAmountEffective: Amounts.stringify(denomsSel.totalCoinValue), - tipAmountRaw: Amounts.stringify(tipAmountRaw), - tipExpiration: backupTip.timestamp_expiration, - walletTipId: backupTip.wallet_tip_id, + rewardAmountEffective: Amounts.stringify(denomsSel.totalCoinValue), + rewardAmountRaw: Amounts.stringify(tipAmountRaw), + rewardExpiration: backupTip.timestamp_expiration, + walletRewardId: backupTip.wallet_tip_id, denomSelUid: backupTip.selected_denoms_uid, - status: TipRecordStatus.Done, // FIXME! + status: RewardRecordStatus.Done, // FIXME! }); } } @@ -863,8 +863,8 @@ export async function importBackup( } else if (type === TombstoneTag.DeleteRefund) { // Nothing required, will just prevent display // in the transactions list - } else if (type === TombstoneTag.DeleteTip) { - await tx.tips.delete(rest[0]); + } else if (type === TombstoneTag.DeleteReward) { + await tx.rewards.delete(rest[0]); } else if (type === TombstoneTag.DeleteWithdrawalGroup) { await tx.withdrawalGroups.delete(rest[0]); } else { diff --git a/packages/taler-wallet-core/src/operations/common.ts b/packages/taler-wallet-core/src/operations/common.ts index cc16a4704..7a8b78b53 100644 --- a/packages/taler-wallet-core/src/operations/common.ts +++ b/packages/taler-wallet-core/src/operations/common.ts @@ -57,7 +57,7 @@ import { PurchaseRecord, RecoupGroupRecord, RefreshGroupRecord, - TipRecord, + RewardRecord, WithdrawalGroupRecord, } from "../db.js"; import { makeErrorDetail, TalerError } from "@gnu-taler/taler-util"; @@ -293,10 +293,10 @@ function convertTaskToTransactionId( tag: TransactionType.Refresh, refreshGroupId: parsedTaskId.refreshGroupId, }); - case PendingTaskType.TipPickup: + case PendingTaskType.RewardPickup: return constructTransactionIdentifier({ - tag: TransactionType.Tip, - walletTipId: parsedTaskId.walletTipId, + tag: TransactionType.Reward, + walletRewardId: parsedTaskId.walletRewardId, }); case PendingTaskType.PeerPushDebit: return constructTransactionIdentifier({ @@ -515,7 +515,7 @@ export enum TombstoneTag { DeleteWithdrawalGroup = "delete-withdrawal-group", DeleteReserve = "delete-reserve", DeletePayment = "delete-payment", - DeleteTip = "delete-tip", + DeleteReward = "delete-reward", DeleteRefreshGroup = "delete-refresh-group", DeleteDepositGroup = "delete-deposit-group", DeleteRefund = "delete-refund", @@ -601,7 +601,9 @@ export function runLongpollAsync( }; res = await reqFn(cts.token); } catch (e) { - await storePendingTaskError(ws, retryTag, getErrorDetailFromException(e)); + const errDetail = getErrorDetailFromException(e); + logger.warn(`got error during long-polling: ${j2s(errDetail)}`); + await storePendingTaskError(ws, retryTag, errDetail); return; } finally { delete ws.activeLongpoll[retryTag]; @@ -622,7 +624,7 @@ export type ParsedTombstone = | { tag: TombstoneTag.DeleteRefund; refundGroupId: string } | { tag: TombstoneTag.DeleteReserve; reservePub: string } | { tag: TombstoneTag.DeleteRefreshGroup; refreshGroupId: string } - | { tag: TombstoneTag.DeleteTip; walletTipId: string } + | { tag: TombstoneTag.DeleteReward; walletTipId: string } | { tag: TombstoneTag.DeletePayment; proposalId: string }; export function constructTombstone(p: ParsedTombstone): TombstoneIdStr { @@ -637,7 +639,7 @@ export function constructTombstone(p: ParsedTombstone): TombstoneIdStr { return `tmb:${p.tag}:${p.proposalId}` as TombstoneIdStr; case TombstoneTag.DeleteRefreshGroup: return `tmb:${p.tag}:${p.refreshGroupId}` as TombstoneIdStr; - case TombstoneTag.DeleteTip: + case TombstoneTag.DeleteReward: return `tmb:${p.tag}:${p.walletTipId}` as TombstoneIdStr; default: assertUnreachable(p); @@ -810,7 +812,7 @@ export type ParsedTaskIdentifier = | { tag: PendingTaskType.PeerPushDebit; pursePub: string } | { tag: PendingTaskType.Purchase; proposalId: string } | { tag: PendingTaskType.Recoup; recoupGroupId: string } - | { tag: PendingTaskType.TipPickup; walletTipId: string } + | { tag: PendingTaskType.RewardPickup; walletRewardId: string } | { tag: PendingTaskType.Refresh; refreshGroupId: string }; export function parseTaskIdentifier(x: string): ParsedTaskIdentifier { @@ -844,8 +846,8 @@ export function parseTaskIdentifier(x: string): ParsedTaskIdentifier { return { tag: type, recoupGroupId: rest[0] }; case PendingTaskType.Refresh: return { tag: type, refreshGroupId: rest[0] }; - case PendingTaskType.TipPickup: - return { tag: type, walletTipId: rest[0] }; + case PendingTaskType.RewardPickup: + return { tag: type, walletRewardId: rest[0] }; case PendingTaskType.Withdraw: return { tag: type, withdrawalGroupId: rest[0] }; default: @@ -877,8 +879,8 @@ export function constructTaskIdentifier(p: ParsedTaskIdentifier): TaskId { return `${p.tag}:${p.recoupGroupId}` as TaskId; case PendingTaskType.Refresh: return `${p.tag}:${p.refreshGroupId}` as TaskId; - case PendingTaskType.TipPickup: - return `${p.tag}:${p.walletTipId}` as TaskId; + case PendingTaskType.RewardPickup: + return `${p.tag}:${p.walletRewardId}` as TaskId; case PendingTaskType.Withdraw: return `${p.tag}:${p.withdrawalGroupId}` as TaskId; default: @@ -899,8 +901,8 @@ export namespace TaskIdentifiers { export function forExchangeCheckRefresh(exch: ExchangeRecord): TaskId { return `${PendingTaskType.ExchangeCheckRefresh}:${exch.baseUrl}` as TaskId; } - export function forTipPickup(tipRecord: TipRecord): TaskId { - return `${PendingTaskType.TipPickup}:${tipRecord.walletTipId}` as TaskId; + export function forTipPickup(tipRecord: RewardRecord): TaskId { + return `${PendingTaskType.RewardPickup}:${tipRecord.walletRewardId}` as TaskId; } export function forRefresh(refreshGroupRecord: RefreshGroupRecord): TaskId { return `${PendingTaskType.Refresh}:${refreshGroupRecord.refreshGroupId}` as TaskId; diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts index c7e13754f..ac501aae2 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts @@ -436,16 +436,25 @@ async function handlePeerPullCreditCreatePurse( logger.info(`reserve merge response: ${j2s(resp)}`); - await ws.db + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPullCredit, + pursePub: pullIni.pursePub, + }); + + const transitionInfo = await ws.db .mktx((x) => [x.peerPullPaymentInitiations]) .runReadWrite(async (tx) => { const pi2 = await tx.peerPullPaymentInitiations.get(pursePub); if (!pi2) { return; } + const oldTxState = computePeerPullCreditTransactionState(pi2); pi2.status = PeerPullPaymentInitiationStatus.PendingReady; await tx.peerPullPaymentInitiations.put(pi2); + const newTxState = computePeerPullCreditTransactionState(pi2); + return { oldTxState, newTxState }; }); + notifyTransition(ws, transactionId, transitionInfo); return TaskRunResult.finished(); } diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index 870437e2e..cc9217d67 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -32,7 +32,7 @@ import { PeerPushPaymentIncomingStatus, PeerPullPaymentInitiationStatus, WithdrawalGroupStatus, - TipRecordStatus, + RewardRecordStatus, DepositOperationStatus, } from "../db.js"; import { @@ -232,17 +232,17 @@ async function gatherDepositPending( async function gatherTipPending( ws: InternalWalletState, tx: GetReadOnlyAccess<{ - tips: typeof WalletStoresV1.tips; + rewards: typeof WalletStoresV1.rewards; operationRetries: typeof WalletStoresV1.operationRetries; }>, now: AbsoluteTime, resp: PendingOperationsResponse, ): Promise { const range = GlobalIDB.KeyRange.bound( - TipRecordStatus.PendingPickup, - TipRecordStatus.PendingPickup, + RewardRecordStatus.PendingPickup, + RewardRecordStatus.PendingPickup, ); - await tx.tips.indexes.byStatus.iter(range).forEachAsync(async (tip) => { + await tx.rewards.indexes.byStatus.iter(range).forEachAsync(async (tip) => { // FIXME: The tip record needs a proper status field! if (tip.pickedUpTimestamp) { return; @@ -252,13 +252,13 @@ async function gatherTipPending( const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); if (tip.acceptedTimestamp) { resp.pendingOperations.push({ - type: PendingTaskType.TipPickup, + type: PendingTaskType.RewardPickup, ...getPendingCommon(ws, opId, timestampDue), givesLifeness: true, timestampDue: retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(), merchantBaseUrl: tip.merchantBaseUrl, - tipId: tip.walletTipId, - merchantTipId: tip.merchantTipId, + tipId: tip.walletRewardId, + merchantTipId: tip.merchantRewardId, }); } }); @@ -494,7 +494,7 @@ export async function getPendingOperations( x.refreshGroups, x.coins, x.withdrawalGroups, - x.tips, + x.rewards, x.purchases, x.planchets, x.depositGroups, diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index dea2d4b16..abeca1119 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -82,7 +82,7 @@ async function putGroupAsFinished( await tx.recoupGroups.put(recoupGroup); } -async function recoupTipCoin( +async function recoupRewardCoin( ws: InternalWalletState, recoupGroupId: string, coinIdx: number, @@ -482,8 +482,8 @@ async function processRecoup( const cs = coin.coinSource; switch (cs.type) { - case CoinSourceType.Tip: - return recoupTipCoin(ws, recoupGroupId, coinIdx, coin); + case CoinSourceType.Reward: + return recoupRewardCoin(ws, recoupGroupId, coinIdx, coin); case CoinSourceType.Refresh: return recoupRefreshCoin(ws, recoupGroupId, coinIdx, coin, cs); case CoinSourceType.Withdraw: diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/reward.ts similarity index 72% rename from packages/taler-wallet-core/src/operations/tip.ts rename to packages/taler-wallet-core/src/operations/reward.ts index e56fb1e8d..47956f15f 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/reward.ts @@ -31,7 +31,7 @@ import { j2s, Logger, NotificationType, - parseTipUri, + parseRewardUri, PrepareTipResult, TalerErrorCode, TalerPreciseTimestamp, @@ -48,8 +48,8 @@ import { CoinRecord, CoinSourceType, DenominationRecord, - TipRecord, - TipRecordStatus, + RewardRecord, + RewardRecordStatus, } from "../db.js"; import { makeErrorDetail } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../internal-wallet-state.js"; @@ -84,31 +84,31 @@ const logger = new Logger("operations/tip.ts"); /** * Get the (DD37-style) transaction status based on the - * database record of a tip. + * database record of a reward. */ -export function computeTipTransactionStatus( - tipRecord: TipRecord, +export function computeRewardTransactionStatus( + tipRecord: RewardRecord, ): TransactionState { switch (tipRecord.status) { - case TipRecordStatus.Done: + case RewardRecordStatus.Done: return { major: TransactionMajorState.Done, }; - case TipRecordStatus.Aborted: + case RewardRecordStatus.Aborted: return { major: TransactionMajorState.Aborted, }; - case TipRecordStatus.PendingPickup: + case RewardRecordStatus.PendingPickup: return { major: TransactionMajorState.Pending, minor: TransactionMinorState.Pickup, }; - case TipRecordStatus.DialogAccept: + case RewardRecordStatus.DialogAccept: return { major: TransactionMajorState.Dialog, minor: TransactionMinorState.Proposed, }; - case TipRecordStatus.SuspendidPickup: + case RewardRecordStatus.SuspendidPickup: return { major: TransactionMajorState.Pending, minor: TransactionMinorState.Pickup, @@ -119,18 +119,18 @@ export function computeTipTransactionStatus( } export function computeTipTransactionActions( - tipRecord: TipRecord, + tipRecord: RewardRecord, ): TransactionAction[] { switch (tipRecord.status) { - case TipRecordStatus.Done: + case RewardRecordStatus.Done: return [TransactionAction.Delete]; - case TipRecordStatus.Aborted: + case RewardRecordStatus.Aborted: return [TransactionAction.Delete]; - case TipRecordStatus.PendingPickup: + case RewardRecordStatus.PendingPickup: return [TransactionAction.Suspend, TransactionAction.Fail]; - case TipRecordStatus.SuspendidPickup: + case RewardRecordStatus.SuspendidPickup: return [TransactionAction.Resume, TransactionAction.Fail]; - case TipRecordStatus.DialogAccept: + case RewardRecordStatus.DialogAccept: return [TransactionAction.Abort]; default: assertUnreachable(tipRecord.status); @@ -141,15 +141,15 @@ export async function prepareTip( ws: InternalWalletState, talerTipUri: string, ): Promise { - const res = parseTipUri(talerTipUri); + const res = parseRewardUri(talerTipUri); if (!res) { throw Error("invalid taler://tip URI"); } let tipRecord = await ws.db - .mktx((x) => [x.tips]) + .mktx((x) => [x.rewards]) .runReadOnly(async (tx) => { - return tx.tips.indexes.byMerchantTipIdAndBaseUrl.get([ + return tx.rewards.indexes.byMerchantTipIdAndBaseUrl.get([ res.merchantTipId, res.merchantBaseUrl, ]); @@ -194,44 +194,44 @@ export async function prepareTip( const secretSeed = encodeCrock(getRandomBytes(64)); const denomSelUid = encodeCrock(getRandomBytes(32)); - const newTipRecord: TipRecord = { - walletTipId: walletTipId, + const newTipRecord: RewardRecord = { + walletRewardId: walletTipId, acceptedTimestamp: undefined, - status: TipRecordStatus.DialogAccept, - tipAmountRaw: Amounts.stringify(amount), - tipExpiration: tipPickupStatus.expiration, + status: RewardRecordStatus.DialogAccept, + rewardAmountRaw: Amounts.stringify(amount), + rewardExpiration: tipPickupStatus.expiration, exchangeBaseUrl: tipPickupStatus.exchange_url, next_url: tipPickupStatus.next_url, merchantBaseUrl: res.merchantBaseUrl, createdTimestamp: TalerPreciseTimestamp.now(), - merchantTipId: res.merchantTipId, - tipAmountEffective: Amounts.stringify(selectedDenoms.totalCoinValue), + merchantRewardId: res.merchantTipId, + rewardAmountEffective: Amounts.stringify(selectedDenoms.totalCoinValue), denomsSel: selectedDenoms, pickedUpTimestamp: undefined, secretSeed, denomSelUid, }; await ws.db - .mktx((x) => [x.tips]) + .mktx((x) => [x.rewards]) .runReadWrite(async (tx) => { - await tx.tips.put(newTipRecord); + await tx.rewards.put(newTipRecord); }); tipRecord = newTipRecord; } const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Tip, - walletTipId: tipRecord.walletTipId, + tag: TransactionType.Reward, + walletRewardId: tipRecord.walletRewardId, }); const tipStatus: PrepareTipResult = { accepted: !!tipRecord && !!tipRecord.acceptedTimestamp, - tipAmountRaw: Amounts.stringify(tipRecord.tipAmountRaw), + rewardAmountRaw: Amounts.stringify(tipRecord.rewardAmountRaw), exchangeBaseUrl: tipRecord.exchangeBaseUrl, merchantBaseUrl: tipRecord.merchantBaseUrl, - expirationTimestamp: tipRecord.tipExpiration, - tipAmountEffective: Amounts.stringify(tipRecord.tipAmountEffective), - walletTipId: tipRecord.walletTipId, + expirationTimestamp: tipRecord.rewardExpiration, + rewardAmountEffective: Amounts.stringify(tipRecord.rewardAmountEffective), + walletRewardId: tipRecord.walletRewardId, transactionId, }; @@ -243,25 +243,25 @@ export async function processTip( walletTipId: string, ): Promise { const tipRecord = await ws.db - .mktx((x) => [x.tips]) + .mktx((x) => [x.rewards]) .runReadOnly(async (tx) => { - return tx.tips.get(walletTipId); + return tx.rewards.get(walletTipId); }); if (!tipRecord) { return TaskRunResult.finished(); } switch (tipRecord.status) { - case TipRecordStatus.Aborted: - case TipRecordStatus.DialogAccept: - case TipRecordStatus.Done: - case TipRecordStatus.SuspendidPickup: + case RewardRecordStatus.Aborted: + case RewardRecordStatus.DialogAccept: + case RewardRecordStatus.Done: + case RewardRecordStatus.SuspendidPickup: return TaskRunResult.finished(); } const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Tip, - walletTipId, + tag: TransactionType.Reward, + walletRewardId: walletTipId, }); const denomsForWithdraw = tipRecord.denomsSel; @@ -300,7 +300,7 @@ export async function processTip( } const tipStatusUrl = new URL( - `tips/${tipRecord.merchantTipId}/pickup`, + `tips/${tipRecord.merchantRewardId}/pickup`, tipRecord.merchantBaseUrl, ); @@ -384,9 +384,9 @@ export async function processTip( coinPriv: planchet.coinPriv, coinPub: planchet.coinPub, coinSource: { - type: CoinSourceType.Tip, + type: CoinSourceType.Reward, coinIndex: i, - walletTipId: walletTipId, + walletRewardId: walletTipId, }, sourceTransactionId: transactionId, denomPubHash: denom.denomPubHash, @@ -401,20 +401,20 @@ export async function processTip( } const transitionInfo = await ws.db - .mktx((x) => [x.coins, x.coinAvailability, x.denominations, x.tips]) + .mktx((x) => [x.coins, x.coinAvailability, x.denominations, x.rewards]) .runReadWrite(async (tx) => { - const tr = await tx.tips.get(walletTipId); + const tr = await tx.rewards.get(walletTipId); if (!tr) { return; } - if (tr.status !== TipRecordStatus.PendingPickup) { + if (tr.status !== RewardRecordStatus.PendingPickup) { return; } - const oldTxState = computeTipTransactionStatus(tr); + const oldTxState = computeRewardTransactionStatus(tr); tr.pickedUpTimestamp = TalerPreciseTimestamp.now(); - tr.status = TipRecordStatus.Done; - await tx.tips.put(tr); - const newTxState = computeTipTransactionStatus(tr); + tr.status = RewardRecordStatus.Done; + await tx.rewards.put(tr); + const newTxState = computeRewardTransactionStatus(tr); for (const cr of newCoinRecords) { await makeCoinAvailable(ws, tx, cr); } @@ -432,26 +432,26 @@ export async function acceptTip( walletTipId: string, ): Promise { const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Tip, - walletTipId, + tag: TransactionType.Reward, + walletRewardId: walletTipId, }); const dbRes = await ws.db - .mktx((x) => [x.tips]) + .mktx((x) => [x.rewards]) .runReadWrite(async (tx) => { - const tipRecord = await tx.tips.get(walletTipId); + const tipRecord = await tx.rewards.get(walletTipId); if (!tipRecord) { logger.error("tip not found"); return; } - if (tipRecord.status != TipRecordStatus.DialogAccept) { + if (tipRecord.status != RewardRecordStatus.DialogAccept) { logger.warn("Unable to accept tip in the current state"); return { tipRecord }; } - const oldTxState = computeTipTransactionStatus(tipRecord); + const oldTxState = computeRewardTransactionStatus(tipRecord); tipRecord.acceptedTimestamp = TalerPreciseTimestamp.now(); - tipRecord.status = TipRecordStatus.PendingPickup; - await tx.tips.put(tipRecord); - const newTxState = computeTipTransactionStatus(tipRecord); + tipRecord.status = RewardRecordStatus.PendingPickup; + await tx.rewards.put(tipRecord); + const newTxState = computeRewardTransactionStatus(tipRecord); return { tipRecord, transitionInfo: { oldTxState, newTxState } }; }); @@ -465,53 +465,53 @@ export async function acceptTip( return { transactionId: constructTransactionIdentifier({ - tag: TransactionType.Tip, - walletTipId: walletTipId, + tag: TransactionType.Reward, + walletRewardId: walletTipId, }), next_url: tipRecord.next_url, }; } -export async function suspendTipTransaction( +export async function suspendRewardTransaction( ws: InternalWalletState, - walletTipId: string, + walletRewardId: string, ): Promise { const taskId = constructTaskIdentifier({ - tag: PendingTaskType.TipPickup, - walletTipId, + tag: PendingTaskType.RewardPickup, + walletRewardId: walletRewardId, }); const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Tip, - walletTipId, + tag: TransactionType.Reward, + walletRewardId: walletRewardId, }); stopLongpolling(ws, taskId); const transitionInfo = await ws.db - .mktx((x) => [x.tips]) + .mktx((x) => [x.rewards]) .runReadWrite(async (tx) => { - const tipRec = await tx.tips.get(walletTipId); + const tipRec = await tx.rewards.get(walletRewardId); if (!tipRec) { - logger.warn(`transaction tip ${walletTipId} not found`); + logger.warn(`transaction tip ${walletRewardId} not found`); return; } - let newStatus: TipRecordStatus | undefined = undefined; + let newStatus: RewardRecordStatus | undefined = undefined; switch (tipRec.status) { - case TipRecordStatus.Done: - case TipRecordStatus.SuspendidPickup: - case TipRecordStatus.Aborted: - case TipRecordStatus.DialogAccept: + case RewardRecordStatus.Done: + case RewardRecordStatus.SuspendidPickup: + case RewardRecordStatus.Aborted: + case RewardRecordStatus.DialogAccept: break; - case TipRecordStatus.PendingPickup: - newStatus = TipRecordStatus.SuspendidPickup; + case RewardRecordStatus.PendingPickup: + newStatus = RewardRecordStatus.SuspendidPickup; break; default: assertUnreachable(tipRec.status); } if (newStatus != null) { - const oldTxState = computeTipTransactionStatus(tipRec); + const oldTxState = computeRewardTransactionStatus(tipRec); tipRec.status = newStatus; - const newTxState = computeTipTransactionStatus(tipRec); - await tx.tips.put(tipRec); + const newTxState = computeRewardTransactionStatus(tipRec); + await tx.rewards.put(tipRec); return { oldTxState, newTxState, @@ -525,43 +525,43 @@ export async function suspendTipTransaction( export async function resumeTipTransaction( ws: InternalWalletState, - walletTipId: string, + walletRewardId: string, ): Promise { const taskId = constructTaskIdentifier({ - tag: PendingTaskType.TipPickup, - walletTipId, + tag: PendingTaskType.RewardPickup, + walletRewardId: walletRewardId, }); const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Tip, - walletTipId, + tag: TransactionType.Reward, + walletRewardId: walletRewardId, }); stopLongpolling(ws, taskId); const transitionInfo = await ws.db - .mktx((x) => [x.tips]) + .mktx((x) => [x.rewards]) .runReadWrite(async (tx) => { - const tipRec = await tx.tips.get(walletTipId); - if (!tipRec) { - logger.warn(`transaction tip ${walletTipId} not found`); + const rewardRec = await tx.rewards.get(walletRewardId); + if (!rewardRec) { + logger.warn(`transaction reward ${walletRewardId} not found`); return; } - let newStatus: TipRecordStatus | undefined = undefined; - switch (tipRec.status) { - case TipRecordStatus.Done: - case TipRecordStatus.PendingPickup: - case TipRecordStatus.Aborted: - case TipRecordStatus.DialogAccept: + let newStatus: RewardRecordStatus | undefined = undefined; + switch (rewardRec.status) { + case RewardRecordStatus.Done: + case RewardRecordStatus.PendingPickup: + case RewardRecordStatus.Aborted: + case RewardRecordStatus.DialogAccept: break; - case TipRecordStatus.SuspendidPickup: - newStatus = TipRecordStatus.PendingPickup; + case RewardRecordStatus.SuspendidPickup: + newStatus = RewardRecordStatus.PendingPickup; break; default: - assertUnreachable(tipRec.status); + assertUnreachable(rewardRec.status); } if (newStatus != null) { - const oldTxState = computeTipTransactionStatus(tipRec); - tipRec.status = newStatus; - const newTxState = computeTipTransactionStatus(tipRec); - await tx.tips.put(tipRec); + const oldTxState = computeRewardTransactionStatus(rewardRec); + rewardRec.status = newStatus; + const newTxState = computeRewardTransactionStatus(rewardRec); + await tx.rewards.put(rewardRec); return { oldTxState, newTxState, @@ -582,43 +582,43 @@ export async function failTipTransaction( export async function abortTipTransaction( ws: InternalWalletState, - walletTipId: string, + walletRewardId: string, ): Promise { const taskId = constructTaskIdentifier({ - tag: PendingTaskType.TipPickup, - walletTipId, + tag: PendingTaskType.RewardPickup, + walletRewardId: walletRewardId, }); const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Tip, - walletTipId, + tag: TransactionType.Reward, + walletRewardId: walletRewardId, }); stopLongpolling(ws, taskId); const transitionInfo = await ws.db - .mktx((x) => [x.tips]) + .mktx((x) => [x.rewards]) .runReadWrite(async (tx) => { - const tipRec = await tx.tips.get(walletTipId); + const tipRec = await tx.rewards.get(walletRewardId); if (!tipRec) { - logger.warn(`transaction tip ${walletTipId} not found`); + logger.warn(`transaction tip ${walletRewardId} not found`); return; } - let newStatus: TipRecordStatus | undefined = undefined; + let newStatus: RewardRecordStatus | undefined = undefined; switch (tipRec.status) { - case TipRecordStatus.Done: - case TipRecordStatus.Aborted: - case TipRecordStatus.PendingPickup: - case TipRecordStatus.DialogAccept: + case RewardRecordStatus.Done: + case RewardRecordStatus.Aborted: + case RewardRecordStatus.PendingPickup: + case RewardRecordStatus.DialogAccept: break; - case TipRecordStatus.SuspendidPickup: - newStatus = TipRecordStatus.Aborted; + case RewardRecordStatus.SuspendidPickup: + newStatus = RewardRecordStatus.Aborted; break; default: assertUnreachable(tipRec.status); } if (newStatus != null) { - const oldTxState = computeTipTransactionStatus(tipRec); + const oldTxState = computeRewardTransactionStatus(tipRec); tipRec.status = newStatus; - const newTxState = computeTipTransactionStatus(tipRec); - await tx.tips.put(tipRec); + const newTxState = computeRewardTransactionStatus(tipRec); + await tx.rewards.put(tipRec); return { oldTxState, newTxState, diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 868f00de7..a16809b36 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -58,7 +58,7 @@ import { RefreshGroupRecord, RefreshOperationStatus, RefundGroupRecord, - TipRecord, + RewardRecord, WalletContractData, WithdrawalGroupRecord, WithdrawalGroupStatus, @@ -107,11 +107,11 @@ import { import { abortTipTransaction, failTipTransaction, - computeTipTransactionStatus, + computeRewardTransactionStatus, resumeTipTransaction, - suspendTipTransaction, + suspendRewardTransaction, computeTipTransactionActions, -} from "./tip.js"; +} from "./reward.js"; import { abortWithdrawalTransaction, augmentPaytoUrisForWithdrawal, @@ -187,7 +187,7 @@ function shouldSkipSearch( */ const txOrder: { [t in TransactionType]: number } = { [TransactionType.Withdrawal]: 1, - [TransactionType.Tip]: 2, + [TransactionType.Reward]: 2, [TransactionType.Payment]: 3, [TransactionType.PeerPullCredit]: 4, [TransactionType.PeerPullDebit]: 5, @@ -284,12 +284,12 @@ export async function getTransactionById( throw Error(`no tx for refresh`); } - case TransactionType.Tip: { - const tipId = parsedTx.walletTipId; + case TransactionType.Reward: { + const tipId = parsedTx.walletRewardId; return await ws.db - .mktx((x) => [x.tips, x.operationRetries]) + .mktx((x) => [x.rewards, x.operationRetries]) .runReadWrite(async (tx) => { - const tipRecord = await tx.tips.get(tipId); + const tipRecord = await tx.rewards.get(tipId); if (!tipRecord) throw Error("not found"); const retries = await tx.operationRetries.get( @@ -818,21 +818,21 @@ function buildTransactionForDeposit( } function buildTransactionForTip( - tipRecord: TipRecord, + tipRecord: RewardRecord, ort?: OperationRetryRecord, ): Transaction { checkLogicInvariant(!!tipRecord.acceptedTimestamp); return { - type: TransactionType.Tip, - txState: computeTipTransactionStatus(tipRecord), + type: TransactionType.Reward, + txState: computeRewardTransactionStatus(tipRecord), txActions: computeTipTransactionActions(tipRecord), - amountEffective: Amounts.stringify(tipRecord.tipAmountEffective), - amountRaw: Amounts.stringify(tipRecord.tipAmountRaw), + amountEffective: Amounts.stringify(tipRecord.rewardAmountEffective), + amountRaw: Amounts.stringify(tipRecord.rewardAmountRaw), timestamp: tipRecord.acceptedTimestamp, transactionId: constructTransactionIdentifier({ - tag: TransactionType.Tip, - walletTipId: tipRecord.walletTipId, + tag: TransactionType.Reward, + walletRewardId: tipRecord.walletRewardId, }), merchantBaseUrl: tipRecord.merchantBaseUrl, ...(ort?.lastError ? { error: ort.lastError } : {}), @@ -945,7 +945,7 @@ export async function getTransactions( x.purchases, x.contractTerms, x.recoupGroups, - x.tips, + x.rewards, x.tombstones, x.withdrawalGroups, x.refreshGroups, @@ -1200,11 +1200,11 @@ export async function getTransactions( ); }); - tx.tips.iter().forEachAsync(async (tipRecord) => { + tx.rewards.iter().forEachAsync(async (tipRecord) => { if ( shouldSkipCurrency( transactionsRequest, - Amounts.parseOrThrow(tipRecord.tipAmountRaw).currency, + Amounts.parseOrThrow(tipRecord.rewardAmountRaw).currency, ) ) { return; @@ -1267,7 +1267,7 @@ export type ParsedTransactionIdentifier = | { tag: TransactionType.PeerPushDebit; pursePub: string } | { tag: TransactionType.Refresh; refreshGroupId: string } | { tag: TransactionType.Refund; refundGroupId: string } - | { tag: TransactionType.Tip; walletTipId: string } + | { tag: TransactionType.Reward; walletRewardId: string } | { tag: TransactionType.Withdrawal; withdrawalGroupId: string } | { tag: TransactionType.InternalWithdrawal; withdrawalGroupId: string }; @@ -1291,8 +1291,8 @@ export function constructTransactionIdentifier( return `txn:${pTxId.tag}:${pTxId.refreshGroupId}` as TransactionIdStr; case TransactionType.Refund: return `txn:${pTxId.tag}:${pTxId.refundGroupId}` as TransactionIdStr; - case TransactionType.Tip: - return `txn:${pTxId.tag}:${pTxId.walletTipId}` as TransactionIdStr; + case TransactionType.Reward: + return `txn:${pTxId.tag}:${pTxId.walletRewardId}` as TransactionIdStr; case TransactionType.Withdrawal: return `txn:${pTxId.tag}:${pTxId.withdrawalGroupId}` as TransactionIdStr; case TransactionType.InternalWithdrawal: @@ -1346,10 +1346,10 @@ export function parseTransactionIdentifier( tag: TransactionType.Refund, refundGroupId: rest[0], }; - case TransactionType.Tip: + case TransactionType.Reward: return { - tag: TransactionType.Tip, - walletTipId: rest[0], + tag: TransactionType.Reward, + walletRewardId: rest[0], }; case TransactionType.Withdrawal: return { @@ -1427,10 +1427,10 @@ export async function retryTransaction( stopLongpolling(ws, taskId); break; } - case TransactionType.Tip: { + case TransactionType.Reward: { const taskId = constructTaskIdentifier({ - tag: PendingTaskType.TipPickup, - walletTipId: parsedTx.walletTipId, + tag: PendingTaskType.RewardPickup, + walletRewardId: parsedTx.walletRewardId, }); await resetPendingTaskTimeout(ws, taskId); stopLongpolling(ws, taskId); @@ -1522,8 +1522,8 @@ export async function suspendTransaction( break; case TransactionType.Refund: throw Error("refund transactions can't be suspended or resumed"); - case TransactionType.Tip: - await suspendTipTransaction(ws, tx.walletTipId); + case TransactionType.Reward: + await suspendRewardTransaction(ws, tx.walletRewardId); break; default: assertUnreachable(tx); @@ -1551,8 +1551,8 @@ export async function failTransaction( return; case TransactionType.Refund: throw Error("can't do cancel-aborting on refund transaction"); - case TransactionType.Tip: - await failTipTransaction(ws, tx.walletTipId); + case TransactionType.Reward: + await failTipTransaction(ws, tx.walletRewardId); return; case TransactionType.Refresh: await failRefreshGroup(ws, tx.refreshGroupId); @@ -1613,8 +1613,8 @@ export async function resumeTransaction( break; case TransactionType.Refund: throw Error("refund transactions can't be suspended or resumed"); - case TransactionType.Tip: - await resumeTipTransaction(ws, tx.walletTipId); + case TransactionType.Reward: + await resumeTipTransaction(ws, tx.walletRewardId); break; } } @@ -1763,16 +1763,16 @@ export async function deleteTransaction( return; } - case TransactionType.Tip: { - const tipId = parsedTx.walletTipId; + case TransactionType.Reward: { + const tipId = parsedTx.walletRewardId; await ws.db - .mktx((x) => [x.tips, x.tombstones]) + .mktx((x) => [x.rewards, x.tombstones]) .runReadWrite(async (tx) => { - const tipRecord = await tx.tips.get(tipId); + const tipRecord = await tx.rewards.get(tipId); if (tipRecord) { - await tx.tips.delete(tipId); + await tx.rewards.delete(tipId); await tx.tombstones.put({ - id: TombstoneTag.DeleteTip + ":" + tipId, + id: TombstoneTag.DeleteReward + ":" + tipId, }); } }); @@ -1856,8 +1856,8 @@ export async function abortTransaction( case TransactionType.Deposit: await abortDepositGroup(ws, txId.depositGroupId); break; - case TransactionType.Tip: - await abortTipTransaction(ws, txId.walletTipId); + case TransactionType.Reward: + await abortTipTransaction(ws, txId.walletRewardId); break; case TransactionType.Refund: throw Error("can't abort refund transactions"); diff --git a/packages/taler-wallet-core/src/pending-types.ts b/packages/taler-wallet-core/src/pending-types.ts index 3bb6636ee..82eb542a7 100644 --- a/packages/taler-wallet-core/src/pending-types.ts +++ b/packages/taler-wallet-core/src/pending-types.ts @@ -33,7 +33,7 @@ export enum PendingTaskType { Purchase = "purchase", Refresh = "refresh", Recoup = "recoup", - TipPickup = "tip-pickup", + RewardPickup = "reward-pickup", Withdraw = "withdraw", Deposit = "deposit", Backup = "backup", @@ -144,7 +144,7 @@ export interface PendingRefreshTask { * The wallet is picking up a tip that the user has accepted. */ export interface PendingTipPickupTask { - type: PendingTaskType.TipPickup; + type: PendingTaskType.RewardPickup; tipId: string; merchantBaseUrl: string; merchantTipId: string; diff --git a/packages/taler-wallet-core/src/versions.ts b/packages/taler-wallet-core/src/versions.ts index eedaf68f6..f0f747e22 100644 --- a/packages/taler-wallet-core/src/versions.ts +++ b/packages/taler-wallet-core/src/versions.ts @@ -34,3 +34,11 @@ export const WALLET_MERCHANT_PROTOCOL_VERSION = "2:0:1"; * Uses libtool's current:revision:age versioning. */ export const WALLET_BANK_INTEGRATION_PROTOCOL_VERSION = "0:0:0"; + +/** + * Semver of the wallet-core implementation. + * Will be replaced with the value from package.json in a + * post-compilation step (inside lib/). + */ +export const WALLET_CORE_IMPLEMENTATION_VERSION = + "__WALLET_CORE_IMPLEMENTATION_VERSION__"; diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index e395237cf..eaa99a6c3 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -29,7 +29,7 @@ import { AcceptExchangeTosRequest, AcceptManualWithdrawalRequest, AcceptManualWithdrawalResult, - AcceptTipRequest, + AcceptRewardRequest, AcceptTipResponse, AcceptWithdrawalResponse, AddExchangeRequest, @@ -85,8 +85,8 @@ import { PreparePeerPushCreditRequest, PreparePeerPushCreditResponse, PrepareRefundRequest, - PrepareTipRequest, - PrepareTipResult, + PrepareRewardRequest as PrepareRewardRequest, + PrepareTipResult as PrepareRewardResult, RecoveryLoadRequest, RetryTransactionRequest, SetCoinSuspendedRequest, @@ -178,8 +178,8 @@ export enum WalletApiOperation { DumpCoins = "dumpCoins", SetCoinSuspended = "setCoinSuspended", ForceRefresh = "forceRefresh", - PrepareTip = "prepareTip", - AcceptTip = "acceptTip", + PrepareReward = "prepareReward", + AcceptReward = "acceptReward", ExportBackup = "exportBackup", AddBackupProvider = "addBackupProvider", RemoveBackupProvider = "removeBackupProvider", @@ -507,23 +507,23 @@ export type StartRefundQueryOp = { response: EmptyObject; }; -// group: Tipping +// group: Rewards /** - * Query and store information about a tip. + * Query and store information about a reward. */ export type PrepareTipOp = { - op: WalletApiOperation.PrepareTip; - request: PrepareTipRequest; - response: PrepareTipResult; + op: WalletApiOperation.PrepareReward; + request: PrepareRewardRequest; + response: PrepareRewardResult; }; /** - * Accept a tip. + * Accept a reward. */ export type AcceptTipOp = { - op: WalletApiOperation.AcceptTip; - request: AcceptTipRequest; + op: WalletApiOperation.AcceptReward; + request: AcceptRewardRequest; response: AcceptTipResponse; }; @@ -1023,8 +1023,8 @@ export type WalletOperations = { [WalletApiOperation.ForceRefresh]: ForceRefreshOp; [WalletApiOperation.DeleteTransaction]: DeleteTransactionOp; [WalletApiOperation.RetryTransaction]: RetryTransactionOp; - [WalletApiOperation.PrepareTip]: PrepareTipOp; - [WalletApiOperation.AcceptTip]: AcceptTipOp; + [WalletApiOperation.PrepareReward]: PrepareTipOp; + [WalletApiOperation.AcceptReward]: AcceptTipOp; [WalletApiOperation.StartRefundQueryForUri]: StartRefundQueryForUriOp; [WalletApiOperation.StartRefundQuery]: StartRefundQueryOp; [WalletApiOperation.ListCurrencies]: ListCurrenciesOp; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 628a55e92..aab414e94 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -93,7 +93,7 @@ import { codecForPreparePeerPullPaymentRequest, codecForPreparePeerPushCreditRequest, codecForPrepareRefundRequest, - codecForPrepareTipRequest, + codecForPrepareRewardRequest, codecForResumeTransaction, codecForRetryTransactionRequest, codecForSetCoinSuspendedRequest, @@ -249,10 +249,10 @@ import { } from "./operations/testing.js"; import { acceptTip, - computeTipTransactionStatus, + computeRewardTransactionStatus, prepareTip, processTip, -} from "./operations/tip.js"; +} from "./operations/reward.js"; import { abortTransaction, deleteTransaction, @@ -300,6 +300,7 @@ import { import { TimerAPI, TimerGroup } from "./util/timer.js"; import { WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, + WALLET_CORE_IMPLEMENTATION_VERSION, WALLET_EXCHANGE_PROTOCOL_VERSION, WALLET_MERCHANT_PROTOCOL_VERSION, } from "./versions.js"; @@ -328,7 +329,7 @@ async function callOperationHandler( return await processRefreshGroup(ws, pending.refreshGroupId); case PendingTaskType.Withdraw: return await processWithdrawalGroup(ws, pending.withdrawalGroupId); - case PendingTaskType.TipPickup: + case PendingTaskType.RewardPickup: return await processTip(ws, pending.tipId); case PendingTaskType.Purchase: return await processPurchase(ws, pending.proposalId); @@ -1016,12 +1017,6 @@ export async function getClientFromWalletState( return client; } -declare const __VERSION__: string; -declare const __GIT_HASH__: string; - -const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : "dev"; -const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; - /** * Implementation of the "wallet-core" API. */ @@ -1355,9 +1350,9 @@ async function dispatchRequestInternal( refreshGroupId, }; } - case WalletApiOperation.PrepareTip: { - const req = codecForPrepareTipRequest().decode(payload); - return await prepareTip(ws, req.talerTipUri); + case WalletApiOperation.PrepareReward: { + const req = codecForPrepareRewardRequest().decode(payload); + return await prepareTip(ws, req.talerRewardUri); } case WalletApiOperation.StartRefundQueryForUri: { const req = codecForPrepareRefundRequest().decode(payload); @@ -1375,9 +1370,9 @@ async function dispatchRequestInternal( await startQueryRefund(ws, txIdParsed.proposalId); return {}; } - case WalletApiOperation.AcceptTip: { + case WalletApiOperation.AcceptReward: { const req = codecForAcceptTipRequest().decode(payload); - return await acceptTip(ws, req.walletTipId); + return await acceptTip(ws, req.walletRewardId); } case WalletApiOperation.ExportBackupPlain: { return exportBackup(ws); @@ -1590,15 +1585,15 @@ async function dispatchRequestInternal( } export function getVersion(ws: InternalWalletState): WalletCoreVersion { - const version: WalletCoreVersion = { - hash: GIT_HASH, - version: VERSION, + const result: WalletCoreVersion = { + hash: undefined, + version: WALLET_CORE_IMPLEMENTATION_VERSION, exchange: WALLET_EXCHANGE_PROTOCOL_VERSION, merchant: WALLET_MERCHANT_PROTOCOL_VERSION, bank: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, devMode: false, }; - return version; + return result; } /** @@ -1889,12 +1884,12 @@ class InternalWalletStateImpl implements InternalWalletState { } return computeRefreshTransactionState(rec); } - case TransactionType.Tip: { - const rec = await tx.tips.get(parsedTxId.walletTipId); + case TransactionType.Reward: { + const rec = await tx.rewards.get(parsedTxId.walletRewardId); if (!rec) { return undefined; } - return computeTipTransactionStatus(rec); + return computeRewardTransactionStatus(rec); } default: assertUnreachable(parsedTxId); diff --git a/packages/taler-wallet-core/tsconfig.json b/packages/taler-wallet-core/tsconfig.json index 5b74121a2..8de28b3d8 100644 --- a/packages/taler-wallet-core/tsconfig.json +++ b/packages/taler-wallet-core/tsconfig.json @@ -7,8 +7,10 @@ "target": "ES2017", "module": "ESNext", "moduleResolution": "Node16", + "resolveJsonModule": true, "sourceMap": true, "lib": ["es6"], + "resolvePackageJsonImports": true, "types": ["node"], "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, @@ -31,5 +33,8 @@ "path": "../taler-util/" } ], - "include": ["src/**/*"] + "include": [ + "src/**/*", + "src/*.json" + ] } diff --git a/packages/taler-wallet-webextension/src/NavigationBar.tsx b/packages/taler-wallet-webextension/src/NavigationBar.tsx index 231418861..167f1797c 100644 --- a/packages/taler-wallet-webextension/src/NavigationBar.tsx +++ b/packages/taler-wallet-webextension/src/NavigationBar.tsx @@ -146,7 +146,7 @@ const talerUriActionToPageName: { } = { [TalerUriAction.Withdraw]: "ctaWithdraw", [TalerUriAction.Pay]: "ctaPay", - [TalerUriAction.Tip]: "ctaTips", + [TalerUriAction.Reward]: "ctaTips", [TalerUriAction.Refund]: "ctaRefund", [TalerUriAction.PayPull]: "ctaInvoicePay", [TalerUriAction.PayPush]: "ctaTransferPickup", diff --git a/packages/taler-wallet-webextension/src/components/HistoryItem.tsx b/packages/taler-wallet-webextension/src/components/HistoryItem.tsx index a0ce04460..e072d2581 100644 --- a/packages/taler-wallet-webextension/src/components/HistoryItem.tsx +++ b/packages/taler-wallet-webextension/src/components/HistoryItem.tsx @@ -134,7 +134,7 @@ export function HistoryItem(props: { tx: Transaction }): VNode { } /> ); - case TransactionType.Tip: + case TransactionType.Reward: return ( { - if (!talerTipUri) throw Error("ERROR_NO-URI-FOR-TIP"); - const tip = await api.wallet.call(WalletApiOperation.PrepareTip, { - talerTipUri, + if (!talerRewardUri) throw Error("ERROR_NO-URI-FOR-TIP"); + const tip = await api.wallet.call(WalletApiOperation.PrepareReward, { + talerRewardUri, }); return { tip }; }); @@ -63,8 +63,8 @@ export function useComponentState({ const { tip } = tipInfo.response; const doAccept = async (): Promise => { - const res = await api.wallet.call(WalletApiOperation.AcceptTip, { - walletTipId: tip.walletTipId, + const res = await api.wallet.call(WalletApiOperation.AcceptReward, { + walletRewardId: tip.walletRewardId, }); //FIX: this may not be seen since we are moving to the success also @@ -75,7 +75,7 @@ export function useComponentState({ const baseInfo = { merchantBaseUrl: tip.merchantBaseUrl, exchangeBaseUrl: tip.exchangeBaseUrl, - amount: Amounts.parseOrThrow(tip.tipAmountEffective), + amount: Amounts.parseOrThrow(tip.rewardAmountEffective), error: undefined, cancel: { onClick: pushAlertOnError(onCancel), diff --git a/packages/taler-wallet-webextension/src/cta/Tip/stories.tsx b/packages/taler-wallet-webextension/src/cta/Reward/stories.tsx similarity index 100% rename from packages/taler-wallet-webextension/src/cta/Tip/stories.tsx rename to packages/taler-wallet-webextension/src/cta/Reward/stories.tsx diff --git a/packages/taler-wallet-webextension/src/cta/Tip/test.ts b/packages/taler-wallet-webextension/src/cta/Reward/test.ts similarity index 90% rename from packages/taler-wallet-webextension/src/cta/Tip/test.ts rename to packages/taler-wallet-webextension/src/cta/Reward/test.ts index e0b6210a2..6d7bad0c8 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Reward/test.ts @@ -62,17 +62,17 @@ describe("Tip CTA states", () => { it("should be ready for accepting the tip", async () => { const { handler, TestingContext } = createWalletApiMock(); - handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { + handler.addWalletCallResponse(WalletApiOperation.PrepareReward, undefined, { accepted: false, exchangeBaseUrl: "exchange url", merchantBaseUrl: "merchant url", - tipAmountEffective: "EUR:1", - walletTipId: "tip_id", + rewardAmountEffective: "EUR:1", + walletRewardId: "tip_id", transactionId: "txn:tip:ABC1234", expirationTimestamp: { t_s: 1, }, - tipAmountRaw: "", + rewardAmountRaw: "", }); const props: Props = { @@ -100,23 +100,23 @@ describe("Tip CTA states", () => { expect(state.exchangeBaseUrl).eq("exchange url"); if (state.accept.onClick === undefined) expect.fail(); - handler.addWalletCallResponse(WalletApiOperation.AcceptTip); + handler.addWalletCallResponse(WalletApiOperation.AcceptReward); state.accept.onClick(); handler.addWalletCallResponse( - WalletApiOperation.PrepareTip, + WalletApiOperation.PrepareReward, undefined, { accepted: true, exchangeBaseUrl: "exchange url", merchantBaseUrl: "merchant url", - tipAmountEffective: "EUR:1", - walletTipId: "tip_id", + rewardAmountEffective: "EUR:1", + walletRewardId: "tip_id", transactionId: "txn:tip:ABC1234", expirationTimestamp: { t_s: 1, }, - tipAmountRaw: "", + rewardAmountRaw: "", }, ); }, @@ -137,17 +137,17 @@ describe("Tip CTA states", () => { it.skip("should be ignored after clicking the ignore button", async () => { const { handler, TestingContext } = createWalletApiMock(); - handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { + handler.addWalletCallResponse(WalletApiOperation.PrepareReward, undefined, { exchangeBaseUrl: "exchange url", merchantBaseUrl: "merchant url", - tipAmountEffective: "EUR:1", - walletTipId: "tip_id", + rewardAmountEffective: "EUR:1", + walletRewardId: "tip_id", transactionId: "txn:tip:ABC1234", accepted: false, expirationTimestamp: { t_s: 1, }, - tipAmountRaw: "", + rewardAmountRaw: "", }); const props: Props = { @@ -184,17 +184,17 @@ describe("Tip CTA states", () => { it("should render accepted if the tip has been used previously", async () => { const { handler, TestingContext } = createWalletApiMock(); - handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { + handler.addWalletCallResponse(WalletApiOperation.PrepareReward, undefined, { accepted: true, exchangeBaseUrl: "exchange url", merchantBaseUrl: "merchant url", - tipAmountEffective: "EUR:1", - walletTipId: "tip_id", + rewardAmountEffective: "EUR:1", + walletRewardId: "tip_id", transactionId: "txn:tip:ABC1234", expirationTimestamp: { t_s: 1, }, - tipAmountRaw: "", + rewardAmountRaw: "", }); const props: Props = { diff --git a/packages/taler-wallet-webextension/src/cta/Tip/views.tsx b/packages/taler-wallet-webextension/src/cta/Reward/views.tsx similarity index 100% rename from packages/taler-wallet-webextension/src/cta/Tip/views.tsx rename to packages/taler-wallet-webextension/src/cta/Reward/views.tsx diff --git a/packages/taler-wallet-webextension/src/cta/index.stories.ts b/packages/taler-wallet-webextension/src/cta/index.stories.ts index 84863f84f..06b11ef6d 100644 --- a/packages/taler-wallet-webextension/src/cta/index.stories.ts +++ b/packages/taler-wallet-webextension/src/cta/index.stories.ts @@ -22,7 +22,7 @@ export * as a1 from "./Deposit/stories.jsx"; export * as a3 from "./Payment/stories.jsx"; export * as a4 from "./Refund/stories.jsx"; -export * as a5 from "./Tip/stories.jsx"; +export * as a5 from "./Reward/stories.js"; export * as a6 from "./Withdraw/stories.jsx"; export * as a8 from "./InvoiceCreate/stories.js"; export * as a9 from "./InvoicePay/stories.js"; diff --git a/packages/taler-wallet-webextension/src/platform/chrome.ts b/packages/taler-wallet-webextension/src/platform/chrome.ts index 1295fa514..a50f225dc 100644 --- a/packages/taler-wallet-webextension/src/platform/chrome.ts +++ b/packages/taler-wallet-webextension/src/platform/chrome.ts @@ -279,7 +279,7 @@ function openWalletURIFromPopup(uri: TalerUri): void { `static/wallet.html#/cta/pay?talerUri=${encodeURIComponent(talerUri)}`, ); break; - case TalerUriAction.Tip: + case TalerUriAction.Reward: url = chrome.runtime.getURL( `static/wallet.html#/cta/tip?talerUri=${encodeURIComponent(talerUri)}`, ); diff --git a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx index 6a0585907..e120334e8 100644 --- a/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx +++ b/packages/taler-wallet-webextension/src/popup/TalerActionFound.tsx @@ -65,7 +65,7 @@ function ContentByUriType({
); - case TalerUriAction.Tip: + case TalerUriAction.Reward: return (

diff --git a/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx index fd9815401..fc3a0916c 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx +++ b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx @@ -66,7 +66,7 @@ export function AddNewActionView({ onCancel }: Props): VNode { return Open pay page; case TalerUriAction.Refund: return Open refund page; - case TalerUriAction.Tip: + case TalerUriAction.Reward: return Open tip page; case TalerUriAction.Withdraw: return Open withdraw page; diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx index d8cb22bf0..98515aac0 100644 --- a/packages/taler-wallet-webextension/src/wallet/Application.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -58,7 +58,7 @@ import { PaymentPage } from "../cta/Payment/index.js"; import { PaymentTemplatePage } from "../cta/PaymentTemplate/index.js"; import { RecoveryPage } from "../cta/Recovery/index.js"; import { RefundPage } from "../cta/Refund/index.js"; -import { TipPage } from "../cta/Tip/index.js"; +import { TipPage } from "../cta/Reward/index.js"; import { TransferCreatePage } from "../cta/TransferCreate/index.js"; import { TransferPickupPage } from "../cta/TransferPickup/index.js"; import { diff --git a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx index 149c8c1f4..1ddb24b02 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx @@ -34,7 +34,7 @@ import { TransactionPeerPushDebit, TransactionRefresh, TransactionRefund, - TransactionTip, + TransactionReward, TransactionType, TransactionWithdrawal, WithdrawalType, @@ -113,9 +113,9 @@ const exampleData = { } as TransactionRefresh, tip: { ...commonTransaction(), - type: TransactionType.Tip, + type: TransactionType.Reward, merchantBaseUrl: "http://ads.merchant.taler.net/", - } as TransactionTip, + } as TransactionReward, refund: { ...commonTransaction(), type: TransactionType.Refund, diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx index f2e3982f6..3ba3ac591 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx @@ -37,7 +37,7 @@ import { TransactionPeerPushDebit, TransactionRefresh, TransactionRefund, - TransactionTip, + TransactionReward, TransactionType, TransactionWithdrawal, WithdrawalDetails, @@ -138,7 +138,7 @@ const exampleData = { } as TransactionRefresh, tip: { ...commonTransaction, - type: TransactionType.Tip, + type: TransactionType.Reward, // merchant: { // name: "the merchant", // logo: merchantIcon, @@ -146,7 +146,7 @@ const exampleData = { // email: "contact@merchant.taler", // }, merchantBaseUrl: "http://merchant.taler", - } as TransactionTip, + } as TransactionReward, refund: { ...commonTransaction, type: TransactionType.Refund, diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index 8d564a275..e54137016 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -745,7 +745,7 @@ export function TransactionView({ ); } - if (transaction.type === TransactionType.Tip) { + if (transaction.type === TransactionType.Reward) { return ( { try { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d9d1a2fa3..f7fd2f134 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true