aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-08-30 15:54:44 +0200
committerFlorian Dold <florian@dold.me>2023-08-30 15:54:44 +0200
commit88f7338d7c84ac2a774b483ccff25faf6ceeb879 (patch)
tree9be612a3ce6d6edbded48ae51a7028f5c14cb645
parent557213f9c4fd834fadb189799073dc64cdb00a07 (diff)
wallet-core,wallet-cli: properly serialize manual DB export
-rw-r--r--packages/taler-wallet-cli/src/index.ts99
-rw-r--r--packages/taler-wallet-core/src/db.ts135
2 files changed, 115 insertions, 119 deletions
diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts
index 9d840e5bb..d7966a9ca 100644
--- a/packages/taler-wallet-cli/src/index.ts
+++ b/packages/taler-wallet-cli/src/index.ts
@@ -257,8 +257,7 @@ async function createLocalWallet(
},
cryptoWorkerType: walletCliArgs.wallet.cryptoWorker as any,
config: {
- features: {
- },
+ features: {},
testing: {
devModeActive: checkEnvFlag("TALER_WALLET_DEV_MODE"),
denomselAllowLate: checkEnvFlag(
@@ -651,9 +650,12 @@ walletCli
});
break;
case TalerUriAction.Reward: {
- const res = await wallet.client.call(WalletApiOperation.PrepareReward, {
- talerRewardUri: uri,
- });
+ const res = await wallet.client.call(
+ WalletApiOperation.PrepareReward,
+ {
+ talerRewardUri: uri,
+ },
+ );
console.log("tip status", res);
await wallet.client.call(WalletApiOperation.AcceptReward, {
walletRewardId: res.walletRewardId,
@@ -874,96 +876,13 @@ const backupCli = walletCli.subcommand("backupArgs", "backup", {
help: "Subcommands for backups",
});
-backupCli
- .subcommand("setDeviceId", "set-device-id")
- .requiredArgument("deviceId", clk.STRING, {
- help: "new device ID",
- })
- .action(async (args) => {
- await withWallet(args, async (wallet) => {
- await wallet.client.call(WalletApiOperation.SetWalletDeviceId, {
- walletDeviceId: args.setDeviceId.deviceId,
- });
- });
- });
-
-backupCli.subcommand("exportPlain", "export-plain").action(async (args) => {
+backupCli.subcommand("exportDb", "export-db").action(async (args) => {
await withWallet(args, async (wallet) => {
- const backup = await wallet.client.call(
- WalletApiOperation.ExportBackupPlain,
- {},
- );
+ const backup = await wallet.client.call(WalletApiOperation.ExportDb, {});
console.log(JSON.stringify(backup, undefined, 2));
});
});
-backupCli.subcommand("recoverySave", "save-recovery").action(async (args) => {
- await withWallet(args, async (wallet) => {
- const recoveryJson = await wallet.client.call(
- WalletApiOperation.ExportBackupRecovery,
- {},
- );
- console.log(JSON.stringify(recoveryJson, undefined, 2));
- });
-});
-
-backupCli.subcommand("run", "run").action(async (args) => {
- await withWallet(args, async (wallet) => {
- await wallet.client.call(WalletApiOperation.RunBackupCycle, {});
- });
-});
-
-backupCli.subcommand("status", "status").action(async (args) => {
- await withWallet(args, async (wallet) => {
- const status = await wallet.client.call(
- WalletApiOperation.GetBackupInfo,
- {},
- );
- console.log(JSON.stringify(status, undefined, 2));
- });
-});
-
-backupCli
- .subcommand("recoveryLoad", "load-recovery")
- .maybeOption("strategy", ["--strategy"], clk.STRING, {
- help: "Strategy for resolving a conflict with the existing wallet key ('theirs' or 'ours')",
- })
- .action(async (args) => {
- await withWallet(args, async (wallet) => {
- const data = JSON.parse(await read(process.stdin));
- let strategy: RecoveryMergeStrategy | undefined;
- const stratStr = args.recoveryLoad.strategy;
- if (stratStr) {
- if (stratStr === "theirs") {
- strategy = RecoveryMergeStrategy.Theirs;
- } else if (stratStr === "ours") {
- strategy = RecoveryMergeStrategy.Theirs;
- } else {
- throw Error("invalid recovery strategy");
- }
- }
- await wallet.client.call(WalletApiOperation.ImportBackupRecovery, {
- recovery: data,
- strategy,
- });
- });
- });
-
-backupCli
- .subcommand("addProvider", "add-provider")
- .requiredArgument("url", clk.STRING)
- .maybeArgument("name", clk.STRING)
- .flag("activate", ["--activate"])
- .action(async (args) => {
- await withWallet(args, async (wallet) => {
- await wallet.client.call(WalletApiOperation.AddBackupProvider, {
- backupProviderBaseUrl: args.addProvider.url,
- activate: args.addProvider.activate,
- name: args.addProvider.name || args.addProvider.url,
- });
- });
- });
-
const depositCli = walletCli.subcommand("depositArgs", "deposit", {
help: "Subcommands for depositing money to payto:// accounts",
});
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index c550ab675..2dbf5dade 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -23,6 +23,7 @@ import {
IDBFactory,
IDBObjectStore,
IDBTransaction,
+ structuredEncapsulate,
} from "@gnu-taler/idb-bridge";
import {
AgeCommitmentProof,
@@ -566,10 +567,31 @@ export interface ExchangeDetailsPointer {
updateClock: TalerPreciseTimestamp;
}
+export enum ExchangeEntryDbRecordStatus {
+ Preset = 1,
+ Ephemeral = 2,
+ Used = 3,
+}
+
+export enum ExchangeEntryDbUpdateStatus {
+ Initial = 1,
+ InitialUpdate = 2,
+ Suspended = 3,
+ Failed = 4,
+ OutdatedUpdate = 5,
+ Ready = 6,
+ ReadyUpdate = 7,
+}
+
+/**
+ * Timestamp stored as a IEEE 754 double, in milliseconds.
+ */
+export type DbIndexableTimestampMs = number;
+
/**
* Exchange record as stored in the wallet's database.
*/
-export interface ExchangeRecord {
+export interface ExchangeEntryRecord {
/**
* Base url of the exchange.
*/
@@ -594,13 +616,12 @@ export interface ExchangeRecord {
*/
detailsPointer: ExchangeDetailsPointer | undefined;
- /**
- * Is this a permanent or temporary exchange record?
- */
- permanent: boolean;
+ entryStatus: ExchangeEntryDbRecordStatus;
+
+ updateStatus: ExchangeEntryDbUpdateStatus;
/**
- * Last time when the exchange was updated (both /keys and /wire).
+ * Last time when the exchange /keys info was updated.
*/
lastUpdate: TalerPreciseTimestamp | undefined;
@@ -608,20 +629,21 @@ export interface ExchangeRecord {
* Next scheduled update for the exchange.
*
* (This field must always be present, so we can index on the timestamp.)
+ *
+ * FIXME: To index on the timestamp, this needs to be a number of
+ * binary timestamp!
*/
- nextUpdate: TalerPreciseTimestamp;
+ nextUpdateStampMs: DbIndexableTimestampMs;
lastKeysEtag: string | undefined;
- lastWireEtag: string | undefined;
-
/**
* Next time that we should check if coins need to be refreshed.
*
* Updated whenever the exchange's denominations are updated or when
* the refresh check has been done.
*/
- nextRefreshCheck: TalerPreciseTimestamp;
+ nextRefreshCheckStampMs: DbIndexableTimestampMs;
/**
* Public key of the reserve that we're currently using for
@@ -2424,7 +2446,7 @@ export const WalletStoresV1 = {
),
exchanges: describeStore(
"exchanges",
- describeContents<ExchangeRecord>({
+ describeContents<ExchangeEntryRecord>({
keyPath: "baseUrl",
}),
{},
@@ -2713,11 +2735,10 @@ export type WalletDbReadOnlyTransaction<
Stores extends StoreNames<typeof WalletStoresV1> & string,
> = DbReadOnlyTransaction<typeof WalletStoresV1, Stores>;
-export type WalletReadWriteTransaction<
+export type WalletDbReadWriteTransaction<
Stores extends StoreNames<typeof WalletStoresV1> & string,
> = DbReadWriteTransaction<typeof WalletStoresV1, Stores>;
-
/**
* An applied migration.
*/
@@ -2748,32 +2769,88 @@ export const walletMetadataStore = {
),
};
-export function exportDb(db: IDBDatabase): Promise<any> {
- const dump = {
- name: db.name,
- stores: {} as { [s: string]: any },
+export interface DbDumpRecord {
+ /**
+ * Key, serialized with structuredEncapsulated.
+ */
+ key: any;
+ /**
+ * Value, serialized with structuredEncapsulated.
+ */
+ value: any;
+}
+
+export interface DbIndexDump {
+ keyPath: string | string[];
+ multiEntry: boolean;
+ unique: boolean;
+}
+
+export interface DbStoreDump {
+ keyPath?: string | string[];
+ autoIncrement: boolean;
+ indexes: { [indexName: string]: DbIndexDump };
+ records: DbDumpRecord[];
+}
+
+export interface DbDumpDatabase {
+ version: number;
+ stores: { [storeName: string]: DbStoreDump };
+}
+
+export interface DbDump {
+ databases: {
+ [name: string]: DbDumpDatabase;
+ };
+}
+
+export function exportDb(db: IDBDatabase): Promise<DbDump> {
+ const dbDump: DbDump = {
+ databases: {},
+ };
+
+ const walletDb: DbDumpDatabase = {
version: db.version,
+ stores: {},
};
+ dbDump.databases[db.name] = walletDb;
return new Promise((resolve, reject) => {
const tx = db.transaction(Array.from(db.objectStoreNames));
tx.addEventListener("complete", () => {
- resolve(dump);
+ resolve(dbDump);
});
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < db.objectStoreNames.length; i++) {
const name = db.objectStoreNames[i];
- const storeDump = {} as { [s: string]: any };
- dump.stores[name] = storeDump;
- tx.objectStore(name)
- .openCursor()
- .addEventListener("success", (e: Event) => {
- const cursor = (e.target as any).result;
- if (cursor) {
- storeDump[cursor.key] = cursor.value;
- cursor.continue();
- }
- });
+ const store = tx.objectStore(name);
+ const storeDump: DbStoreDump = {
+ autoIncrement: store.autoIncrement,
+ keyPath: store.keyPath,
+ indexes: {},
+ records: [],
+ };
+ const indexNames = store.indexNames;
+ for (let j = 0; j < indexNames.length; j++) {
+ const idxName = indexNames[j];
+ const index = store.index(idxName);
+ storeDump.indexes[idxName] = {
+ keyPath: index.keyPath,
+ multiEntry: index.multiEntry,
+ unique: index.unique,
+ };
+ }
+ walletDb.stores[name] = storeDump;
+ store.openCursor().addEventListener("success", (e: Event) => {
+ const cursor = (e.target as any).result;
+ if (cursor) {
+ storeDump.records.push({
+ key: structuredEncapsulate(cursor.key),
+ value: structuredEncapsulate(cursor.value),
+ });
+ cursor.continue();
+ }
+ });
}
});
}