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,
|
cryptoWorkerType: walletCliArgs.wallet.cryptoWorker as any,
|
||||||
config: {
|
config: {
|
||||||
features: {
|
features: {},
|
||||||
},
|
|
||||||
testing: {
|
testing: {
|
||||||
devModeActive: checkEnvFlag("TALER_WALLET_DEV_MODE"),
|
devModeActive: checkEnvFlag("TALER_WALLET_DEV_MODE"),
|
||||||
denomselAllowLate: checkEnvFlag(
|
denomselAllowLate: checkEnvFlag(
|
||||||
@ -651,9 +650,12 @@ walletCli
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case TalerUriAction.Reward: {
|
case TalerUriAction.Reward: {
|
||||||
const res = await wallet.client.call(WalletApiOperation.PrepareReward, {
|
const res = await wallet.client.call(
|
||||||
|
WalletApiOperation.PrepareReward,
|
||||||
|
{
|
||||||
talerRewardUri: uri,
|
talerRewardUri: uri,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
console.log("tip status", res);
|
console.log("tip status", res);
|
||||||
await wallet.client.call(WalletApiOperation.AcceptReward, {
|
await wallet.client.call(WalletApiOperation.AcceptReward, {
|
||||||
walletRewardId: res.walletRewardId,
|
walletRewardId: res.walletRewardId,
|
||||||
@ -874,96 +876,13 @@ const backupCli = walletCli.subcommand("backupArgs", "backup", {
|
|||||||
help: "Subcommands for backups",
|
help: "Subcommands for backups",
|
||||||
});
|
});
|
||||||
|
|
||||||
backupCli
|
backupCli.subcommand("exportDb", "export-db").action(async (args) => {
|
||||||
.subcommand("setDeviceId", "set-device-id")
|
|
||||||
.requiredArgument("deviceId", clk.STRING, {
|
|
||||||
help: "new device ID",
|
|
||||||
})
|
|
||||||
.action(async (args) => {
|
|
||||||
await withWallet(args, async (wallet) => {
|
await withWallet(args, async (wallet) => {
|
||||||
await wallet.client.call(WalletApiOperation.SetWalletDeviceId, {
|
const backup = await wallet.client.call(WalletApiOperation.ExportDb, {});
|
||||||
walletDeviceId: args.setDeviceId.deviceId,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
backupCli.subcommand("exportPlain", "export-plain").action(async (args) => {
|
|
||||||
await withWallet(args, async (wallet) => {
|
|
||||||
const backup = await wallet.client.call(
|
|
||||||
WalletApiOperation.ExportBackupPlain,
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
console.log(JSON.stringify(backup, undefined, 2));
|
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", {
|
const depositCli = walletCli.subcommand("depositArgs", "deposit", {
|
||||||
help: "Subcommands for depositing money to payto:// accounts",
|
help: "Subcommands for depositing money to payto:// accounts",
|
||||||
});
|
});
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
IDBFactory,
|
IDBFactory,
|
||||||
IDBObjectStore,
|
IDBObjectStore,
|
||||||
IDBTransaction,
|
IDBTransaction,
|
||||||
|
structuredEncapsulate,
|
||||||
} from "@gnu-taler/idb-bridge";
|
} from "@gnu-taler/idb-bridge";
|
||||||
import {
|
import {
|
||||||
AgeCommitmentProof,
|
AgeCommitmentProof,
|
||||||
@ -566,10 +567,31 @@ export interface ExchangeDetailsPointer {
|
|||||||
updateClock: TalerPreciseTimestamp;
|
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.
|
* Exchange record as stored in the wallet's database.
|
||||||
*/
|
*/
|
||||||
export interface ExchangeRecord {
|
export interface ExchangeEntryRecord {
|
||||||
/**
|
/**
|
||||||
* Base url of the exchange.
|
* Base url of the exchange.
|
||||||
*/
|
*/
|
||||||
@ -594,13 +616,12 @@ export interface ExchangeRecord {
|
|||||||
*/
|
*/
|
||||||
detailsPointer: ExchangeDetailsPointer | undefined;
|
detailsPointer: ExchangeDetailsPointer | undefined;
|
||||||
|
|
||||||
/**
|
entryStatus: ExchangeEntryDbRecordStatus;
|
||||||
* Is this a permanent or temporary exchange record?
|
|
||||||
*/
|
updateStatus: ExchangeEntryDbUpdateStatus;
|
||||||
permanent: boolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last time when the exchange was updated (both /keys and /wire).
|
* Last time when the exchange /keys info was updated.
|
||||||
*/
|
*/
|
||||||
lastUpdate: TalerPreciseTimestamp | undefined;
|
lastUpdate: TalerPreciseTimestamp | undefined;
|
||||||
|
|
||||||
@ -608,20 +629,21 @@ export interface ExchangeRecord {
|
|||||||
* Next scheduled update for the exchange.
|
* Next scheduled update for the exchange.
|
||||||
*
|
*
|
||||||
* (This field must always be present, so we can index on the timestamp.)
|
* (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;
|
lastKeysEtag: string | undefined;
|
||||||
|
|
||||||
lastWireEtag: string | undefined;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Next time that we should check if coins need to be refreshed.
|
* Next time that we should check if coins need to be refreshed.
|
||||||
*
|
*
|
||||||
* Updated whenever the exchange's denominations are updated or when
|
* Updated whenever the exchange's denominations are updated or when
|
||||||
* the refresh check has been done.
|
* the refresh check has been done.
|
||||||
*/
|
*/
|
||||||
nextRefreshCheck: TalerPreciseTimestamp;
|
nextRefreshCheckStampMs: DbIndexableTimestampMs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public key of the reserve that we're currently using for
|
* Public key of the reserve that we're currently using for
|
||||||
@ -2424,7 +2446,7 @@ export const WalletStoresV1 = {
|
|||||||
),
|
),
|
||||||
exchanges: describeStore(
|
exchanges: describeStore(
|
||||||
"exchanges",
|
"exchanges",
|
||||||
describeContents<ExchangeRecord>({
|
describeContents<ExchangeEntryRecord>({
|
||||||
keyPath: "baseUrl",
|
keyPath: "baseUrl",
|
||||||
}),
|
}),
|
||||||
{},
|
{},
|
||||||
@ -2713,11 +2735,10 @@ export type WalletDbReadOnlyTransaction<
|
|||||||
Stores extends StoreNames<typeof WalletStoresV1> & string,
|
Stores extends StoreNames<typeof WalletStoresV1> & string,
|
||||||
> = DbReadOnlyTransaction<typeof WalletStoresV1, Stores>;
|
> = DbReadOnlyTransaction<typeof WalletStoresV1, Stores>;
|
||||||
|
|
||||||
export type WalletReadWriteTransaction<
|
export type WalletDbReadWriteTransaction<
|
||||||
Stores extends StoreNames<typeof WalletStoresV1> & string,
|
Stores extends StoreNames<typeof WalletStoresV1> & string,
|
||||||
> = DbReadWriteTransaction<typeof WalletStoresV1, Stores>;
|
> = DbReadWriteTransaction<typeof WalletStoresV1, Stores>;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An applied migration.
|
* An applied migration.
|
||||||
*/
|
*/
|
||||||
@ -2748,29 +2769,85 @@ export const walletMetadataStore = {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export function exportDb(db: IDBDatabase): Promise<any> {
|
export interface DbDumpRecord {
|
||||||
const dump = {
|
/**
|
||||||
name: db.name,
|
* Key, serialized with structuredEncapsulated.
|
||||||
stores: {} as { [s: string]: any },
|
*/
|
||||||
version: db.version,
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const tx = db.transaction(Array.from(db.objectStoreNames));
|
const tx = db.transaction(Array.from(db.objectStoreNames));
|
||||||
tx.addEventListener("complete", () => {
|
tx.addEventListener("complete", () => {
|
||||||
resolve(dump);
|
resolve(dbDump);
|
||||||
});
|
});
|
||||||
// tslint:disable-next-line:prefer-for-of
|
// tslint:disable-next-line:prefer-for-of
|
||||||
for (let i = 0; i < db.objectStoreNames.length; i++) {
|
for (let i = 0; i < db.objectStoreNames.length; i++) {
|
||||||
const name = db.objectStoreNames[i];
|
const name = db.objectStoreNames[i];
|
||||||
const storeDump = {} as { [s: string]: any };
|
const store = tx.objectStore(name);
|
||||||
dump.stores[name] = storeDump;
|
const storeDump: DbStoreDump = {
|
||||||
tx.objectStore(name)
|
autoIncrement: store.autoIncrement,
|
||||||
.openCursor()
|
keyPath: store.keyPath,
|
||||||
.addEventListener("success", (e: Event) => {
|
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;
|
const cursor = (e.target as any).result;
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
storeDump[cursor.key] = cursor.value;
|
storeDump.records.push({
|
||||||
|
key: structuredEncapsulate(cursor.key),
|
||||||
|
value: structuredEncapsulate(cursor.value),
|
||||||
|
});
|
||||||
cursor.continue();
|
cursor.continue();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user