commit 43889193dcfa8058a647b2c876a63d4c0d5d64cc
parent 59071e0e4a2ba260643e5efe924c266c00972273
Author: Florian Dold <florian@dold.me>
Date: Wed, 15 Jul 2026 22:48:44 +0200
wallet-core: start with DAL
Diffstat:
7 files changed, 177 insertions(+), 70 deletions(-)
diff --git a/packages/taler-wallet-core/src/contacts.ts b/packages/taler-wallet-core/src/contacts.ts
@@ -30,15 +30,15 @@ import {
Logger,
NotificationType,
} from "@gnu-taler/taler-util";
-import { ContactRecord, WalletIndexedDbTransaction } from "./db-indexeddb.js";
+import { WalletDbTransaction } from "./dbtx.js";
import { WalletExecutionContext } from "./wallet.js";
const logger = new Logger("contacts.ts");
async function makeContactListItem(
wex: WalletExecutionContext,
- tx: WalletIndexedDbTransaction,
- r: ContactRecord,
+ tx: WalletDbTransaction,
+ r: ContactEntry,
//lastError: TalerErrorDetail | undefined,
): Promise<ContactEntry> {
const listItem: ContactEntry = {
@@ -60,7 +60,7 @@ export async function addContact(
req: AddContactRequest,
): Promise<EmptyObject> {
await wex.runLegacyWalletDbTx(async (tx) => {
- tx.contacts.put(req.contact);
+ await tx.wtx.addContact(req.contact);
tx.notify({
type: NotificationType.ContactAdded,
contact: req.contact,
@@ -77,7 +77,7 @@ export async function deleteContact(
req: DeleteContactRequest,
): Promise<EmptyObject> {
await wex.runLegacyWalletDbTx(async (tx) => {
- await tx.contacts.delete([req.contact.alias, req.contact.aliasType]);
+ await tx.wtx.deleteContact(req.contact.alias, req.contact.aliasType);
tx.notify({
type: NotificationType.ContactDeleted,
contact: req.contact,
@@ -95,9 +95,9 @@ export async function listContacts(
): Promise<ContactListResponse> {
const contacts: ContactEntry[] = [];
await wex.runLegacyWalletDbTx(async (tx) => {
- const contactsRecords = await tx.contacts.iter().toArray();
+ const contactsRecords = await tx.wtx.listContacts();
for (const contactRec of contactsRecords) {
- const li = await makeContactListItem(wex, tx, contactRec);
+ const li = await makeContactListItem(wex, tx.wtx, contactRec);
contacts.push(li);
}
});
diff --git a/packages/taler-wallet-core/src/db-common.ts b/packages/taler-wallet-core/src/db-common.ts
@@ -454,6 +454,52 @@ export enum ConfigRecordKey {
DonauConfig = "donauConfig",
}
+export interface DonauConfig {
+ donauBaseUrl: string;
+ donauTaxId: string;
+ /** Tax ID hash, salted with donauSalt */
+ donauTaxIdHash: string;
+ /** 32 byte salt, base32crockford encoded */
+ donauSalt: string;
+}
+
+export interface WalletBackupConfState {
+ deviceId: string;
+ walletRootPub: string;
+ walletRootPriv: string;
+
+ /**
+ * Last hash of the canonicalized plain-text backup.
+ */
+ lastBackupPlainHash?: string;
+
+ /**
+ * Timestamp stored in the last backup.
+ */
+ lastBackupTimestamp?: DbPreciseTimestamp;
+
+ /**
+ * Last time we tried to do a backup.
+ */
+ lastBackupCheckTimestamp?: DbPreciseTimestamp;
+ lastBackupNonce?: string;
+}
+
+/**
+ * Configuration key/value entries to configure
+ * the wallet.
+ */
+export type ConfigRecord =
+ | {
+ key: ConfigRecordKey.WalletBackupState;
+ value: WalletBackupConfState;
+ }
+ | { key: ConfigRecordKey.CurrencyDefaultsApplied; value: boolean | number }
+ | { key: ConfigRecordKey.TestLoopTx; value: number }
+ | { key: ConfigRecordKey.LastInitInfo; value: DbProtocolTimestamp }
+ | { key: ConfigRecordKey.MaterializedTransactionsVersion; value: number }
+ | { key: ConfigRecordKey.DonauConfig; value: DonauConfig };
+
export enum RecoupOperationStatus {
Pending = 0x0100_0000,
Suspended = 0x0110_0000,
diff --git a/packages/taler-wallet-core/src/db-indexeddb.ts b/packages/taler-wallet-core/src/db-indexeddb.ts
@@ -84,7 +84,7 @@ import {
} from "@gnu-taler/taler-util";
import { DbRetryInfo, TaskIdentifiers } from "./common.js";
import {
- ConfigRecordKey,
+ ConfigRecord,
DbPreciseTimestamp,
DbProtocolTimestamp,
DenomLossStatus,
@@ -92,6 +92,7 @@ import {
DepositElementStatus,
DepositOperationStatus,
DonationReceiptStatus,
+ DonauConfig,
ExchangeEntryDbRecordStatus,
ExchangeEntryDbUpdateStatus,
PeerPullDebitRecordStatus,
@@ -106,6 +107,7 @@ import {
RefundGroupStatus,
RefundItemStatus,
ReserveRecordStatus,
+ WalletBackupConfState,
WithdrawalGroupStatus,
timestampProtocolFromDb,
} from "./db-common.js";
@@ -123,6 +125,7 @@ import {
describeStoreV2,
openDatabase,
} from "./query.js";
+export { ConfigRecord, DonauConfig, WalletBackupConfState };
/**
* This file contains the database schema of the Taler wallet together
@@ -1315,52 +1318,6 @@ export interface PurchaseRecord {
refundAmountAwaiting: AmountString | undefined;
}
-export interface DonauConfig {
- donauBaseUrl: string;
- donauTaxId: string;
- /** Tax ID hash, salted with donauSalt */
- donauTaxIdHash: string;
- /** 32 byte salt, base32crockford encoded */
- donauSalt: string;
-}
-
-/**
- * Configuration key/value entries to configure
- * the wallet.
- */
-export type ConfigRecord =
- | {
- key: ConfigRecordKey.WalletBackupState;
- value: WalletBackupConfState;
- }
- | { key: ConfigRecordKey.CurrencyDefaultsApplied; value: boolean | number }
- | { key: ConfigRecordKey.TestLoopTx; value: number }
- | { key: ConfigRecordKey.LastInitInfo; value: DbProtocolTimestamp }
- | { key: ConfigRecordKey.MaterializedTransactionsVersion; value: number }
- | { key: ConfigRecordKey.DonauConfig; value: DonauConfig };
-
-export interface WalletBackupConfState {
- deviceId: string;
- walletRootPub: string;
- walletRootPriv: string;
-
- /**
- * Last hash of the canonicalized plain-text backup.
- */
- lastBackupPlainHash?: string;
-
- /**
- * Timestamp stored in the last backup.
- */
- lastBackupTimestamp?: DbPreciseTimestamp;
-
- /**
- * Last time we tried to do a backup.
- */
- lastBackupCheckTimestamp?: DbPreciseTimestamp;
- lastBackupNonce?: string;
-}
-
// FIXME: Should these be numeric codes?
export const enum WithdrawalRecordType {
BankManual = "bank-manual",
@@ -2292,9 +2249,6 @@ export interface DonationSummaryRecord {
amountReceiptsSubmitted: AmountString;
}
-/**
- * Record for contacts
- */
export interface ContactRecord {
/**
* The mailbox URI of this contact
diff --git a/packages/taler-wallet-core/src/dbtx.ts b/packages/taler-wallet-core/src/dbtx.ts
@@ -15,11 +15,15 @@
*/
import {
+ ContactEntry,
CurrencySpecification,
+ MailboxConfiguration,
+ MailboxMessageRecord,
ScopeInfo,
stringifyScopeInfo,
} from "@gnu-taler/taler-util";
-import { ConfigRecord, WalletIndexedDbTransaction } from "./db-indexeddb.js";
+import { ConfigRecord } from "./db-common.js";
+import { PurchaseRecord, WalletIndexedDbTransaction } from "./db-indexeddb.js";
export interface GetCurrencyInfoDbResult {
/**
@@ -48,6 +52,8 @@ export interface WalletDbTransaction {
key: T,
): Promise<Extract<ConfigRecord, { key: T }> | undefined>;
+ upsertConfig(record: ConfigRecord): Promise<void>;
+
/**
* Store currency info for a scope.
*
@@ -58,6 +64,29 @@ export interface WalletDbTransaction {
insertCurrencyInfoUnlessExists(
req: StoreCurrencyInfoDbRequest,
): Promise<void>;
+
+ addContact(contact: ContactEntry): Promise<void>;
+
+ deleteContact(alias: string, aliasType: string): Promise<void>;
+
+ listContacts(): Promise<ContactEntry[]>;
+
+ upsertMailboxMessage(message: MailboxMessageRecord): Promise<void>;
+
+ deleteMailboxMessage(
+ originMailboxBaseUrl: string,
+ talerUri: string,
+ ): Promise<void>;
+
+ listMailboxMessages(): Promise<MailboxMessageRecord[]>;
+
+ getMailboxConfiguration(
+ mailboxBaseUrl: string,
+ ): Promise<MailboxConfiguration | undefined>;
+
+ upsertMailboxConfiguration(mailboxConf: MailboxConfiguration): Promise<void>;
+
+ getPurchase(proposalId: string): Promise<PurchaseRecord | undefined>;
}
export class IdbWalletTransaction implements WalletDbTransaction {
@@ -87,6 +116,11 @@ export class IdbWalletTransaction implements WalletDbTransaction {
return (await tx.config.get(key)) as any;
}
+ async upsertConfig(record: ConfigRecord): Promise<void> {
+ const tx = this.tx;
+ await tx.config.put(record);
+ }
+
async upsertCurrencyInfo(req: StoreCurrencyInfoDbRequest): Promise<void> {
const tx = this.tx;
await tx.currencyInfo.put({
@@ -111,4 +145,71 @@ export class IdbWalletTransaction implements WalletDbTransaction {
source: req.source,
});
}
+
+ async addContact(contact: ContactEntry): Promise<void> {
+ const tx = this.tx;
+ await tx.contacts.put({
+ alias: contact.alias,
+ aliasType: contact.aliasType,
+ mailboxBaseUri: contact.mailboxBaseUri,
+ mailboxAddress: contact.mailboxAddress,
+ source: contact.source,
+ petname: contact.petname,
+ });
+ }
+
+ async deleteContact(alias: string, aliasType: string): Promise<void> {
+ const tx = this.tx;
+ await tx.contacts.delete([alias, aliasType]);
+ }
+
+ async listContacts(): Promise<ContactEntry[]> {
+ const tx = this.tx;
+ const records = await tx.contacts.iter().toArray();
+ return records.map((r) => ({
+ alias: r.alias,
+ aliasType: r.aliasType,
+ mailboxBaseUri: r.mailboxBaseUri,
+ mailboxAddress: r.mailboxAddress,
+ source: r.source,
+ petname: r.petname,
+ }));
+ }
+
+ async upsertMailboxMessage(message: MailboxMessageRecord): Promise<void> {
+ const tx = this.tx;
+ await tx.mailboxMessages.put(message);
+ }
+
+ async deleteMailboxMessage(
+ originMailboxBaseUrl: string,
+ talerUri: string,
+ ): Promise<void> {
+ const tx = this.tx;
+ await tx.mailboxMessages.delete([originMailboxBaseUrl, talerUri]);
+ }
+
+ async listMailboxMessages(): Promise<MailboxMessageRecord[]> {
+ const tx = this.tx;
+ return await tx.mailboxMessages.getAll();
+ }
+
+ async getMailboxConfiguration(
+ mailboxBaseUrl: string,
+ ): Promise<MailboxConfiguration | undefined> {
+ const tx = this.tx;
+ return await tx.mailboxConfigurations.get(mailboxBaseUrl);
+ }
+
+ async upsertMailboxConfiguration(
+ mailboxConf: MailboxConfiguration,
+ ): Promise<void> {
+ const tx = this.tx;
+ await tx.mailboxConfigurations.put(mailboxConf);
+ }
+
+ async getPurchase(proposalId: string): Promise<PurchaseRecord | undefined> {
+ const tx = this.tx;
+ return await tx.purchases.get(proposalId);
+ }
}
diff --git a/packages/taler-wallet-core/src/mailbox.ts b/packages/taler-wallet-core/src/mailbox.ts
@@ -68,7 +68,7 @@ export async function addMailboxMessage(
req: AddMailboxMessageRequest,
): Promise<EmptyObject> {
await wex.runLegacyWalletDbTx(async (tx) => {
- tx.mailboxMessages.put(req.message);
+ await tx.wtx.upsertMailboxMessage(req.message);
tx.notify({
type: NotificationType.MailboxMessageAdded,
message: req.message,
@@ -85,10 +85,10 @@ export async function deleteMailboxMessage(
req: DeleteMailboxMessageRequest,
): Promise<EmptyObject> {
await wex.runLegacyWalletDbTx(async (tx) => {
- tx.mailboxMessages.delete([
+ await tx.wtx.deleteMailboxMessage(
req.message.originMailboxBaseUrl,
req.message.talerUri,
- ]);
+ );
tx.notify({
type: NotificationType.MailboxMessageDeleted,
message: req.message,
@@ -105,7 +105,7 @@ export async function listMailboxMessages(
req: EmptyObject,
): Promise<MailboxMessagesResponse> {
const messages = await wex.runLegacyWalletDbTx(async (tx) => {
- return await tx.mailboxMessages.getAll();
+ return await tx.wtx.listMailboxMessages();
});
return { messages: messages };
}
@@ -181,8 +181,10 @@ export async function getMailbox(
): Promise<GetMailboxResponse> {
return await wex.runLegacyWalletDbTx(async (tx) => {
return {
- mailboxConfiguration: await tx.mailboxConfigurations.get(mailboxBaseUrl.mailboxBaseUrl),
- }
+ mailboxConfiguration: await tx.wtx.getMailboxConfiguration(
+ mailboxBaseUrl.mailboxBaseUrl,
+ ),
+ };
});
}
@@ -198,7 +200,7 @@ export async function createNewMailbox(
const hpkeKey = encodeCrock(hpkeCreateSecretKey());
const privKey = encodeCrock(keys.eddsaPriv);
const hAddress = encodeCrock(sha512(keys.eddsaPub));
- const nowInAYear = AbsoluteTime.addDuration(
+ const nowInAYear = AbsoluteTime.addDuration(
AbsoluteTime.now(),
Duration.fromSpec({
years: 1,
@@ -225,7 +227,7 @@ export async function createNewMailbox(
);
}
await wex.runLegacyWalletDbTx(async (tx) => {
- return await tx.mailboxConfigurations.put(mailboxConf);
+ return await tx.wtx.upsertMailboxConfiguration(mailboxConf);
});
return mailboxConf;
}
@@ -274,7 +276,9 @@ export async function refreshMailbox(
default:
throw Error("unable to get mailbox service config");
}
- const res = await mailboxClient.getMessages({ hMailbox: mailboxConf.hAddress });
+ const res = await mailboxClient.getMessages({
+ hMailbox: mailboxConf.hAddress,
+ });
switch (res.case) {
case "ok":
const hpkeSk: Uint8Array = decodeCrock(mailboxConf.privateEncryptionKey);
diff --git a/packages/taler-wallet-core/src/preset-exchanges.ts b/packages/taler-wallet-core/src/preset-exchanges.ts
@@ -73,7 +73,9 @@ const builtinExchanges: BuiltinExchange[] = [
*/
export async function fillDefaults(wex: WalletExecutionContext): Promise<void> {
await wex.runLegacyWalletDbTx(async (tx) => {
- let appliedRec = await tx.config.get("currencyDefaultsApplied");
+ let appliedRec = await tx.wtx.getConfig(
+ ConfigRecordKey.CurrencyDefaultsApplied,
+ );
let appliedVersion = appliedRec ? !!appliedRec.value : 0;
if (appliedRec != null) {
if (appliedRec.value === true) {
@@ -101,7 +103,7 @@ export async function fillDefaults(wex: WalletExecutionContext): Promise<void> {
exch.currencySpec,
);
}
- await tx.config.put({
+ await tx.wtx.upsertConfig({
key: ConfigRecordKey.CurrencyDefaultsApplied,
value: currentDefaultsVersion,
});
diff --git a/packages/taler-wallet-core/src/testing.ts b/packages/taler-wallet-core/src/testing.ts
@@ -976,7 +976,7 @@ export async function testPay(
const purchase = await wex.runLegacyWalletDbTx(async (tx) => {
const parsedTx = parseTransactionIdentifier(r.transactionId);
checkLogicInvariant(parsedTx?.tag === TransactionType.Payment);
- return tx.purchases.get(parsedTx.proposalId);
+ return tx.wtx.getPurchase(parsedTx.proposalId);
});
checkLogicInvariant(!!purchase);
return {