wallet-core: implement and test stored backups
This commit is contained in:
parent
79973a63dd
commit
64e78d03a1
@ -1882,7 +1882,7 @@ export class SqliteBackend implements Backend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearObjectStore(
|
async clearObjectStore(
|
||||||
btx: DatabaseTransaction,
|
btx: DatabaseTransaction,
|
||||||
objectStoreName: string,
|
objectStoreName: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@ -1906,7 +1906,21 @@ export class SqliteBackend implements Backend {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error("Method not implemented.");
|
this._prep(sqlClearObjectStore).run({
|
||||||
|
object_store_id: scopeInfo.objectStoreId,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const index of scopeInfo.indexMap.values()) {
|
||||||
|
let stmt: Sqlite3Statement;
|
||||||
|
if (index.unique) {
|
||||||
|
stmt = this._prep(sqlClearUniqueIndexData);
|
||||||
|
} else {
|
||||||
|
stmt = this._prep(sqlClearIndexData);
|
||||||
|
}
|
||||||
|
stmt.run({
|
||||||
|
index_id: index.indexId,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1963,6 +1977,15 @@ CREATE TABLE IF NOT EXISTS unique_index_data
|
|||||||
);
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const sqlClearObjectStore = `
|
||||||
|
DELETE FROM object_data WHERE object_store_id=$object_store_id`;
|
||||||
|
|
||||||
|
const sqlClearIndexData = `
|
||||||
|
DELETE FROM index_data WHERE index_id=$index_id`;
|
||||||
|
|
||||||
|
const sqlClearUniqueIndexData = `
|
||||||
|
DELETE FROM unique_index_data WHERE index_id=$index_id`;
|
||||||
|
|
||||||
const sqlListDatabases = `
|
const sqlListDatabases = `
|
||||||
SELECT name, version FROM databases;
|
SELECT name, version FROM databases;
|
||||||
`;
|
`;
|
||||||
|
@ -735,7 +735,9 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this._closePending) {
|
if (this._closePending) {
|
||||||
throw new InvalidStateError();
|
throw new InvalidStateError(
|
||||||
|
`tried to start transaction on ${this._name}, but a close is pending`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(storeNames)) {
|
if (!Array.isArray(storeNames)) {
|
||||||
@ -930,6 +932,9 @@ export class BridgeIDBFactory {
|
|||||||
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-running-a-versionchange-transaction
|
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-running-a-versionchange-transaction
|
||||||
|
|
||||||
for (const otherConn of this.connections) {
|
for (const otherConn of this.connections) {
|
||||||
|
if (otherConn._name != db._name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (otherConn._closePending) {
|
if (otherConn._closePending) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2023 Taler Systems S.A.
|
||||||
|
|
||||||
|
GNU Taler is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports.
|
||||||
|
*/
|
||||||
|
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||||
|
import { GlobalTestState } from "../harness/harness.js";
|
||||||
|
import {
|
||||||
|
withdrawViaBankV2,
|
||||||
|
makeTestPaymentV2,
|
||||||
|
useSharedTestkudosEnvironment,
|
||||||
|
} from "../harness/helpers.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test stored backup wallet-core API.
|
||||||
|
*/
|
||||||
|
export async function runStoredBackupsTest(t: GlobalTestState) {
|
||||||
|
// Set up test environment
|
||||||
|
|
||||||
|
const { walletClient, bank, exchange, merchant } =
|
||||||
|
await useSharedTestkudosEnvironment(t);
|
||||||
|
|
||||||
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
|
await withdrawViaBankV2(t, {
|
||||||
|
walletClient,
|
||||||
|
bank,
|
||||||
|
exchange,
|
||||||
|
amount: "TESTKUDOS:20",
|
||||||
|
});
|
||||||
|
|
||||||
|
await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
|
||||||
|
|
||||||
|
const sb1Resp = await walletClient.call(
|
||||||
|
WalletApiOperation.CreateStoredBackup,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const sbList = await walletClient.call(
|
||||||
|
WalletApiOperation.ListStoredBackups,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
t.assertTrue(sbList.storedBackups.length === 1);
|
||||||
|
t.assertTrue(sbList.storedBackups[0].name === sb1Resp.name);
|
||||||
|
|
||||||
|
const order = {
|
||||||
|
summary: "Buy me!",
|
||||||
|
amount: "TESTKUDOS:5",
|
||||||
|
fulfillment_url: "taler://fulfillment-success/thx",
|
||||||
|
};
|
||||||
|
|
||||||
|
await makeTestPaymentV2(t, { walletClient, merchant, order });
|
||||||
|
await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
|
||||||
|
|
||||||
|
const txn1 = await walletClient.call(WalletApiOperation.GetTransactions, {});
|
||||||
|
t.assertDeepEqual(txn1.transactions.length, 2);
|
||||||
|
|
||||||
|
// Recover from the stored backup now.
|
||||||
|
|
||||||
|
const sb2Resp = await walletClient.call(
|
||||||
|
WalletApiOperation.CreateStoredBackup,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("recovering backup");
|
||||||
|
|
||||||
|
await walletClient.call(WalletApiOperation.RecoverStoredBackup, {
|
||||||
|
name: sb1Resp.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("first recovery done");
|
||||||
|
|
||||||
|
// Recovery went well, now we can delete the backup
|
||||||
|
// of the old database we stored before importing.
|
||||||
|
{
|
||||||
|
const sbl1 = await walletClient.call(
|
||||||
|
WalletApiOperation.ListStoredBackups,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
t.assertTrue(sbl1.storedBackups.length === 2);
|
||||||
|
|
||||||
|
await walletClient.call(WalletApiOperation.DeleteStoredBackup, {
|
||||||
|
name: sb1Resp.name,
|
||||||
|
});
|
||||||
|
const sbl2 = await walletClient.call(
|
||||||
|
WalletApiOperation.ListStoredBackups,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
t.assertTrue(sbl2.storedBackups.length === 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const txn2 = await walletClient.call(WalletApiOperation.GetTransactions, {});
|
||||||
|
// We only have the withdrawal after restoring
|
||||||
|
t.assertDeepEqual(txn2.transactions.length, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
runStoredBackupsTest.suites = ["wallet"];
|
@ -114,6 +114,7 @@ import { runSimplePaymentTest } from "./test-simple-payment.js";
|
|||||||
import { runTermOfServiceFormatTest } from "./test-tos-format.js";
|
import { runTermOfServiceFormatTest } from "./test-tos-format.js";
|
||||||
import { runExchangePurseTest } from "./test-exchange-purse.js";
|
import { runExchangePurseTest } from "./test-exchange-purse.js";
|
||||||
import { getSharedTestDir } from "../harness/helpers.js";
|
import { getSharedTestDir } from "../harness/helpers.js";
|
||||||
|
import { runStoredBackupsTest } from "./test-stored-backups.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test runner.
|
* Test runner.
|
||||||
@ -212,6 +213,7 @@ const allTests: TestMainFunction[] = [
|
|||||||
runWithdrawalFeesTest,
|
runWithdrawalFeesTest,
|
||||||
runWithdrawalHugeTest,
|
runWithdrawalHugeTest,
|
||||||
runTermOfServiceFormatTest,
|
runTermOfServiceFormatTest,
|
||||||
|
runStoredBackupsTest,
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface TestRunSpec {
|
export interface TestRunSpec {
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { AmountString } from "./taler-types.js";
|
||||||
|
|
||||||
export interface BackupRecovery {
|
export interface BackupRecovery {
|
||||||
walletRootPriv: string;
|
walletRootPriv: string;
|
||||||
providers: {
|
providers: {
|
||||||
@ -21,3 +23,20 @@ export interface BackupRecovery {
|
|||||||
url: string;
|
url: string;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class BackupBackupProviderTerms {
|
||||||
|
/**
|
||||||
|
* Last known supported protocol version.
|
||||||
|
*/
|
||||||
|
supported_protocol_version: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last known annual fee.
|
||||||
|
*/
|
||||||
|
annual_fee: AmountString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last known storage limit.
|
||||||
|
*/
|
||||||
|
storage_limit_in_megabytes: number;
|
||||||
|
}
|
||||||
|
@ -2673,3 +2673,15 @@ export interface RecoverStoredBackupRequest {
|
|||||||
export interface DeleteStoredBackupRequest {
|
export interface DeleteStoredBackupRequest {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const codecForDeleteStoredBackupRequest =
|
||||||
|
(): Codec<DeleteStoredBackupRequest> =>
|
||||||
|
buildCodecForObject<DeleteStoredBackupRequest>()
|
||||||
|
.property("name", codecForString())
|
||||||
|
.build("DeleteStoredBackupRequest");
|
||||||
|
|
||||||
|
export const codecForRecoverStoredBackupRequest =
|
||||||
|
(): Codec<RecoverStoredBackupRequest> =>
|
||||||
|
buildCodecForObject<RecoverStoredBackupRequest>()
|
||||||
|
.property("name", codecForString())
|
||||||
|
.build("RecoverStoredBackupRequest");
|
||||||
|
@ -883,7 +883,7 @@ backupCli.subcommand("exportDb", "export-db").action(async (args) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
backupCli.subcommand("storeBackup", "store-backup").action(async (args) => {
|
backupCli.subcommand("storeBackup", "store").action(async (args) => {
|
||||||
await withWallet(args, async (wallet) => {
|
await withWallet(args, async (wallet) => {
|
||||||
const resp = await wallet.client.call(
|
const resp = await wallet.client.call(
|
||||||
WalletApiOperation.CreateStoredBackup,
|
WalletApiOperation.CreateStoredBackup,
|
||||||
@ -893,6 +893,46 @@ backupCli.subcommand("storeBackup", "store-backup").action(async (args) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
backupCli.subcommand("storeBackup", "list-stored").action(async (args) => {
|
||||||
|
await withWallet(args, async (wallet) => {
|
||||||
|
const resp = await wallet.client.call(
|
||||||
|
WalletApiOperation.ListStoredBackups,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
console.log(JSON.stringify(resp, undefined, 2));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
backupCli
|
||||||
|
.subcommand("storeBackup", "delete-stored")
|
||||||
|
.requiredArgument("name", clk.STRING)
|
||||||
|
.action(async (args) => {
|
||||||
|
await withWallet(args, async (wallet) => {
|
||||||
|
const resp = await wallet.client.call(
|
||||||
|
WalletApiOperation.DeleteStoredBackup,
|
||||||
|
{
|
||||||
|
name: args.storeBackup.name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
console.log(JSON.stringify(resp, undefined, 2));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
backupCli
|
||||||
|
.subcommand("recoverBackup", "recover-stored")
|
||||||
|
.requiredArgument("name", clk.STRING)
|
||||||
|
.action(async (args) => {
|
||||||
|
await withWallet(args, async (wallet) => {
|
||||||
|
const resp = await wallet.client.call(
|
||||||
|
WalletApiOperation.RecoverStoredBackup,
|
||||||
|
{
|
||||||
|
name: args.recoverBackup.name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
console.log(JSON.stringify(resp, undefined, 2));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
backupCli.subcommand("importDb", "import-db").action(async (args) => {
|
backupCli.subcommand("importDb", "import-db").action(async (args) => {
|
||||||
await withWallet(args, async (wallet) => {
|
await withWallet(args, async (wallet) => {
|
||||||
const dumpRaw = await read(process.stdin);
|
const dumpRaw = await read(process.stdin);
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
IDBDatabase,
|
IDBDatabase,
|
||||||
IDBFactory,
|
IDBFactory,
|
||||||
IDBObjectStore,
|
IDBObjectStore,
|
||||||
|
IDBRequest,
|
||||||
IDBTransaction,
|
IDBTransaction,
|
||||||
structuredEncapsulate,
|
structuredEncapsulate,
|
||||||
} from "@gnu-taler/idb-bridge";
|
} from "@gnu-taler/idb-bridge";
|
||||||
@ -59,6 +60,7 @@ import {
|
|||||||
Logger,
|
Logger,
|
||||||
CoinPublicKeyString,
|
CoinPublicKeyString,
|
||||||
TalerPreciseTimestamp,
|
TalerPreciseTimestamp,
|
||||||
|
j2s,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
DbAccess,
|
DbAccess,
|
||||||
@ -117,7 +119,8 @@ export const TALER_WALLET_META_DB_NAME = "taler-wallet-meta";
|
|||||||
/**
|
/**
|
||||||
* Stored backups, mainly created when manually importing a backup.
|
* 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";
|
export const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
|
||||||
|
|
||||||
@ -2833,11 +2836,10 @@ export async function exportSingleDb(
|
|||||||
dbName,
|
dbName,
|
||||||
undefined,
|
undefined,
|
||||||
() => {
|
() => {
|
||||||
// May not happen, since we're not requesting a specific version
|
logger.info(`unexpected onversionchange in exportSingleDb of ${dbName}`);
|
||||||
throw Error("unexpected version change");
|
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const tx = myDb.transaction(Array.from(myDb.objectStoreNames));
|
const tx = myDb.transaction(Array.from(myDb.objectStoreNames));
|
||||||
tx.addEventListener("complete", () => {
|
tx.addEventListener("complete", () => {
|
||||||
myDb.close();
|
//myDb.close();
|
||||||
resolve(singleDbDump);
|
resolve(singleDbDump);
|
||||||
});
|
});
|
||||||
// tslint:disable-next-line:prefer-for-of
|
// tslint:disable-next-line:prefer-for-of
|
||||||
@ -2885,6 +2887,7 @@ export async function exportSingleDb(
|
|||||||
if (store.keyPath == null) {
|
if (store.keyPath == null) {
|
||||||
rec.key = structuredEncapsulate(cursor.key);
|
rec.key = structuredEncapsulate(cursor.key);
|
||||||
}
|
}
|
||||||
|
storeDump.records.push(rec);
|
||||||
cursor.continue();
|
cursor.continue();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -2913,21 +2916,22 @@ async function recoverFromDump(
|
|||||||
db: IDBDatabase,
|
db: IDBDatabase,
|
||||||
dbDump: DbDumpDatabase,
|
dbDump: DbDumpDatabase,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
|
const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
|
||||||
tx.addEventListener("complete", () => {
|
const txProm = promiseFromTransaction(tx);
|
||||||
resolve();
|
const storeNames = db.objectStoreNames;
|
||||||
});
|
for (let i = 0; i < storeNames.length; i++) {
|
||||||
for (let i = 0; i < db.objectStoreNames.length; i++) {
|
|
||||||
const name = db.objectStoreNames[i];
|
const name = db.objectStoreNames[i];
|
||||||
const storeDump = dbDump.stores[name];
|
const storeDump = dbDump.stores[name];
|
||||||
if (!storeDump) continue;
|
if (!storeDump) continue;
|
||||||
|
await promiseFromRequest(tx.objectStore(name).clear());
|
||||||
|
logger.info(`importing ${storeDump.records.length} records into ${name}`);
|
||||||
for (let rec of storeDump.records) {
|
for (let rec of storeDump.records) {
|
||||||
tx.objectStore(name).put(rec.value, rec.key);
|
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 {
|
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.
|
* Purge all data in the given database.
|
||||||
*/
|
*/
|
||||||
|
@ -52,7 +52,6 @@ interface MakeDbResult {
|
|||||||
async function makeFileDb(
|
async function makeFileDb(
|
||||||
args: DefaultNodeWalletArgs = {},
|
args: DefaultNodeWalletArgs = {},
|
||||||
): Promise<MakeDbResult> {
|
): Promise<MakeDbResult> {
|
||||||
BridgeIDBFactory.enableTracing = false;
|
|
||||||
const myBackend = new MemoryBackend();
|
const myBackend = new MemoryBackend();
|
||||||
myBackend.enableTracing = false;
|
myBackend.enableTracing = false;
|
||||||
const storagePath = args.persistentStoragePath;
|
const storagePath = args.persistentStoragePath;
|
||||||
@ -141,7 +140,10 @@ export async function createNativeWalletHost2(
|
|||||||
|
|
||||||
let dbResp: MakeDbResult;
|
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");
|
logger.info("using legacy file-based DB backend");
|
||||||
dbResp = await makeFileDb(args);
|
dbResp = await makeFileDb(args);
|
||||||
} else {
|
} else {
|
||||||
|
@ -121,6 +121,11 @@ import {
|
|||||||
GetCurrencyInfoResponse,
|
GetCurrencyInfoResponse,
|
||||||
codecForGetCurrencyInfoRequest,
|
codecForGetCurrencyInfoRequest,
|
||||||
CreateStoredBackupResponse,
|
CreateStoredBackupResponse,
|
||||||
|
StoredBackupList,
|
||||||
|
codecForDeleteStoredBackupRequest,
|
||||||
|
DeleteStoredBackupRequest,
|
||||||
|
RecoverStoredBackupRequest,
|
||||||
|
codecForRecoverStoredBackupRequest,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
HttpRequestLibrary,
|
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.
|
* Implementation of the "wallet-core" API.
|
||||||
*/
|
*/
|
||||||
@ -1059,12 +1115,18 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
|||||||
switch (operation) {
|
switch (operation) {
|
||||||
case WalletApiOperation.CreateStoredBackup:
|
case WalletApiOperation.CreateStoredBackup:
|
||||||
return createStoredBackup(ws);
|
return createStoredBackup(ws);
|
||||||
case WalletApiOperation.DeleteStoredBackup:
|
case WalletApiOperation.DeleteStoredBackup: {
|
||||||
|
const req = codecForDeleteStoredBackupRequest().decode(payload);
|
||||||
|
await deleteStoredBackup(ws, req);
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
case WalletApiOperation.ListStoredBackups:
|
case WalletApiOperation.ListStoredBackups:
|
||||||
|
return listStoredBackups(ws);
|
||||||
|
case WalletApiOperation.RecoverStoredBackup: {
|
||||||
|
const req = codecForRecoverStoredBackupRequest().decode(payload);
|
||||||
|
await recoverStoredBackup(ws, req);
|
||||||
return {};
|
return {};
|
||||||
case WalletApiOperation.RecoverStoredBackup:
|
}
|
||||||
return {};
|
|
||||||
case WalletApiOperation.InitWallet: {
|
case WalletApiOperation.InitWallet: {
|
||||||
logger.trace("initializing wallet");
|
logger.trace("initializing wallet");
|
||||||
ws.initCalled = true;
|
ws.initCalled = true;
|
||||||
|
Loading…
Reference in New Issue
Block a user