aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-09-01 10:52:15 +0200
committerFlorian Dold <florian@dold.me>2023-09-01 10:52:15 +0200
commit64e78d03a117fffeb18e18154d9028a2532285a5 (patch)
tree116d1c79b9419f114b6b5f42ce0c8eeb6e88c928 /packages/taler-wallet-core
parent79973a63dd31c0d84b677a2a1511b1dffc6218b8 (diff)
downloadwallet-core-64e78d03a117fffeb18e18154d9028a2532285a5.tar.xz
wallet-core: implement and test stored backups
Diffstat (limited to 'packages/taler-wallet-core')
-rw-r--r--packages/taler-wallet-core/src/db.ts53
-rw-r--r--packages/taler-wallet-core/src/host-impl.node.ts6
-rw-r--r--packages/taler-wallet-core/src/wallet.ts68
3 files changed, 103 insertions, 24 deletions
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index a642c0203..b9d86eb25 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -22,6 +22,7 @@ import {
IDBDatabase,
IDBFactory,
IDBObjectStore,
+ IDBRequest,
IDBTransaction,
structuredEncapsulate,
} from "@gnu-taler/idb-bridge";
@@ -59,6 +60,7 @@ import {
Logger,
CoinPublicKeyString,
TalerPreciseTimestamp,
+ j2s,
} from "@gnu-taler/taler-util";
import {
DbAccess,
@@ -117,7 +119,8 @@ export const TALER_WALLET_META_DB_NAME = "taler-wallet-meta";
/**
* Stored backups, mainly created when manually importing a backup.
*/
-export const TALER_WALLET_STORED_BACKUPS_DB_NAME = "taler-wallet-stored-backups";
+export const TALER_WALLET_STORED_BACKUPS_DB_NAME =
+ "taler-wallet-stored-backups";
export const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
@@ -2833,11 +2836,10 @@ export async function exportSingleDb(
dbName,
undefined,
() => {
- // May not happen, since we're not requesting a specific version
- throw Error("unexpected version change");
+ logger.info(`unexpected onversionchange in exportSingleDb of ${dbName}`);
},
() => {
- logger.info("unexpected onupgradeneeded");
+ logger.info(`unexpected onupgradeneeded in exportSingleDb of ${dbName}`);
},
);
@@ -2849,7 +2851,7 @@ export async function exportSingleDb(
return new Promise((resolve, reject) => {
const tx = myDb.transaction(Array.from(myDb.objectStoreNames));
tx.addEventListener("complete", () => {
- myDb.close();
+ //myDb.close();
resolve(singleDbDump);
});
// tslint:disable-next-line:prefer-for-of
@@ -2885,6 +2887,7 @@ export async function exportSingleDb(
if (store.keyPath == null) {
rec.key = structuredEncapsulate(cursor.key);
}
+ storeDump.records.push(rec);
cursor.continue();
}
});
@@ -2913,21 +2916,22 @@ async function recoverFromDump(
db: IDBDatabase,
dbDump: DbDumpDatabase,
): Promise<void> {
- return new Promise((resolve, reject) => {
- const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
- tx.addEventListener("complete", () => {
- resolve();
- });
- for (let i = 0; i < db.objectStoreNames.length; i++) {
- const name = db.objectStoreNames[i];
- const storeDump = dbDump.stores[name];
- if (!storeDump) continue;
- for (let rec of storeDump.records) {
- tx.objectStore(name).put(rec.value, rec.key);
- }
+ const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
+ const txProm = promiseFromTransaction(tx);
+ const storeNames = db.objectStoreNames;
+ for (let i = 0; i < storeNames.length; i++) {
+ const name = db.objectStoreNames[i];
+ const storeDump = dbDump.stores[name];
+ if (!storeDump) continue;
+ await promiseFromRequest(tx.objectStore(name).clear());
+ logger.info(`importing ${storeDump.records.length} records into ${name}`);
+ for (let rec of storeDump.records) {
+ await promiseFromRequest(tx.objectStore(name).put(rec.value, rec.key));
+ logger.info("importing record done");
}
- tx.commit();
- });
+ }
+ tx.commit();
+ return await txProm;
}
function checkDbDump(x: any): x is DbDump {
@@ -3184,6 +3188,17 @@ function promiseFromTransaction(transaction: IDBTransaction): Promise<void> {
});
}
+export function promiseFromRequest(request: IDBRequest): Promise<any> {
+ return new Promise((resolve, reject) => {
+ request.onsuccess = () => {
+ resolve(request.result);
+ };
+ request.onerror = () => {
+ reject(request.error);
+ };
+ });
+}
+
/**
* Purge all data in the given database.
*/
diff --git a/packages/taler-wallet-core/src/host-impl.node.ts b/packages/taler-wallet-core/src/host-impl.node.ts
index 0b6539306..0626b9254 100644
--- a/packages/taler-wallet-core/src/host-impl.node.ts
+++ b/packages/taler-wallet-core/src/host-impl.node.ts
@@ -52,7 +52,6 @@ interface MakeDbResult {
async function makeFileDb(
args: DefaultNodeWalletArgs = {},
): Promise<MakeDbResult> {
- BridgeIDBFactory.enableTracing = false;
const myBackend = new MemoryBackend();
myBackend.enableTracing = false;
const storagePath = args.persistentStoragePath;
@@ -141,7 +140,10 @@ export async function createNativeWalletHost2(
let dbResp: MakeDbResult;
- if (args.persistentStoragePath &&args.persistentStoragePath.endsWith(".json")) {
+ if (
+ args.persistentStoragePath &&
+ args.persistentStoragePath.endsWith(".json")
+ ) {
logger.info("using legacy file-based DB backend");
dbResp = await makeFileDb(args);
} else {
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index 626409dd6..5666d67e0 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -121,6 +121,11 @@ import {
GetCurrencyInfoResponse,
codecForGetCurrencyInfoRequest,
CreateStoredBackupResponse,
+ StoredBackupList,
+ codecForDeleteStoredBackupRequest,
+ DeleteStoredBackupRequest,
+ RecoverStoredBackupRequest,
+ codecForRecoverStoredBackupRequest,
} from "@gnu-taler/taler-util";
import {
HttpRequestLibrary,
@@ -1041,6 +1046,57 @@ async function createStoredBackup(
};
}
+async function listStoredBackups(
+ ws: InternalWalletState,
+): Promise<StoredBackupList> {
+ const storedBackups: StoredBackupList = {
+ storedBackups: [],
+ };
+ const backupsDb = await openStoredBackupsDatabase(ws.idb);
+ await backupsDb.mktxAll().runReadWrite(async (tx) => {
+ await tx.backupMeta.iter().forEach((x) => {
+ storedBackups.storedBackups.push({
+ name: x.name,
+ });
+ });
+ });
+ return storedBackups;
+}
+
+async function deleteStoredBackup(
+ ws: InternalWalletState,
+ req: DeleteStoredBackupRequest,
+): Promise<void> {
+ const backupsDb = await openStoredBackupsDatabase(ws.idb);
+ await backupsDb.mktxAll().runReadWrite(async (tx) => {
+ await tx.backupData.delete(req.name);
+ await tx.backupMeta.delete(req.name);
+ });
+}
+
+async function recoverStoredBackup(
+ ws: InternalWalletState,
+ req: RecoverStoredBackupRequest,
+): Promise<void> {
+ logger.info(`Recovering stored backup ${req.name}`);
+ const { name } = req;
+ const backupsDb = await openStoredBackupsDatabase(ws.idb);
+ const bd = await backupsDb.mktxAll().runReadWrite(async (tx) => {
+ const backupMeta = tx.backupMeta.get(name);
+ if (!backupMeta) {
+ throw Error("backup not found");
+ }
+ const backupData = await tx.backupData.get(name);
+ if (!backupData) {
+ throw Error("no backup data (DB corrupt)");
+ }
+ return backupData;
+ });
+ logger.info(`backup found, now importing`);
+ await importDb(ws.db.idbHandle(), bd);
+ logger.info(`import done`);
+}
+
/**
* Implementation of the "wallet-core" API.
*/
@@ -1059,12 +1115,18 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
switch (operation) {
case WalletApiOperation.CreateStoredBackup:
return createStoredBackup(ws);
- case WalletApiOperation.DeleteStoredBackup:
+ case WalletApiOperation.DeleteStoredBackup: {
+ const req = codecForDeleteStoredBackupRequest().decode(payload);
+ await deleteStoredBackup(ws, req);
return {};
+ }
case WalletApiOperation.ListStoredBackups:
+ return listStoredBackups(ws);
+ case WalletApiOperation.RecoverStoredBackup: {
+ const req = codecForRecoverStoredBackupRequest().decode(payload);
+ await recoverStoredBackup(ws, req);
return {};
- case WalletApiOperation.RecoverStoredBackup:
- return {};
+ }
case WalletApiOperation.InitWallet: {
logger.trace("initializing wallet");
ws.initCalled = true;