wallet-core,wallet-cli: properly serialize manual DB export
This commit is contained in:
parent
557213f9c4
commit
88f7338d7c
@ -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",
|
||||
});
|
||||
|
@ -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 },
|
||||
version: db.version,
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user