wallet-core: open DB inside wallet handle, back up meta DB as well
This commit is contained in:
parent
53613a137d
commit
aba173d8a9
@ -104,7 +104,7 @@ import { RetryInfo, TaskIdentifiers } from "./operations/common.js";
|
||||
* for all previous versions must be written, which should be
|
||||
* avoided.
|
||||
*/
|
||||
export const TALER_DB_NAME = "taler-wallet-main-v9";
|
||||
export const TALER_WALLET_MAIN_DB_NAME = "taler-wallet-main-v9";
|
||||
|
||||
/**
|
||||
* Name of the metadata database. This database is used
|
||||
@ -112,7 +112,7 @@ export const TALER_DB_NAME = "taler-wallet-main-v9";
|
||||
*
|
||||
* (Minor migrations are handled via upgrade transactions.)
|
||||
*/
|
||||
export const TALER_META_DB_NAME = "taler-wallet-meta";
|
||||
export const TALER_WALLET_META_DB_NAME = "taler-wallet-meta";
|
||||
|
||||
export const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
|
||||
|
||||
@ -2806,25 +2806,36 @@ export interface DbDump {
|
||||
};
|
||||
}
|
||||
|
||||
export function exportDb(db: IDBDatabase): Promise<DbDump> {
|
||||
const dbDump: DbDump = {
|
||||
databases: {},
|
||||
};
|
||||
export async function exportSingleDb(
|
||||
idb: IDBFactory,
|
||||
dbName: string,
|
||||
): Promise<DbDumpDatabase> {
|
||||
const myDb = await openDatabase(
|
||||
idb,
|
||||
dbName,
|
||||
undefined,
|
||||
() => {
|
||||
// May not happen, since we're not requesting a specific version
|
||||
throw Error("unexpected version change");
|
||||
},
|
||||
() => {
|
||||
logger.info("unexpected onupgradeneeded");
|
||||
},
|
||||
);
|
||||
|
||||
const walletDb: DbDumpDatabase = {
|
||||
version: db.version,
|
||||
const singleDbDump: DbDumpDatabase = {
|
||||
version: myDb.version,
|
||||
stores: {},
|
||||
};
|
||||
dbDump.databases[db.name] = walletDb;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = db.transaction(Array.from(db.objectStoreNames));
|
||||
const tx = myDb.transaction(Array.from(myDb.objectStoreNames));
|
||||
tx.addEventListener("complete", () => {
|
||||
resolve(dbDump);
|
||||
resolve(singleDbDump);
|
||||
});
|
||||
// tslint:disable-next-line:prefer-for-of
|
||||
for (let i = 0; i < db.objectStoreNames.length; i++) {
|
||||
const name = db.objectStoreNames[i];
|
||||
for (let i = 0; i < myDb.objectStoreNames.length; i++) {
|
||||
const name = myDb.objectStoreNames[i];
|
||||
const store = tx.objectStore(name);
|
||||
const storeDump: DbStoreDump = {
|
||||
autoIncrement: store.autoIncrement,
|
||||
@ -2842,7 +2853,7 @@ export function exportDb(db: IDBDatabase): Promise<DbDump> {
|
||||
unique: index.unique,
|
||||
};
|
||||
}
|
||||
walletDb.stores[name] = storeDump;
|
||||
singleDbDump.stores[name] = storeDump;
|
||||
store.openCursor().addEventListener("success", (e: Event) => {
|
||||
const cursor = (e.target as any).result;
|
||||
if (cursor) {
|
||||
@ -2862,6 +2873,23 @@ export function exportDb(db: IDBDatabase): Promise<DbDump> {
|
||||
});
|
||||
}
|
||||
|
||||
export async function exportDb(idb: IDBFactory): Promise<DbDump> {
|
||||
const dbDump: DbDump = {
|
||||
databases: {},
|
||||
};
|
||||
|
||||
dbDump.databases[TALER_WALLET_META_DB_NAME] = await exportSingleDb(
|
||||
idb,
|
||||
TALER_WALLET_META_DB_NAME,
|
||||
);
|
||||
dbDump.databases[TALER_WALLET_MAIN_DB_NAME] = await exportSingleDb(
|
||||
idb,
|
||||
TALER_WALLET_MAIN_DB_NAME,
|
||||
);
|
||||
|
||||
return dbDump;
|
||||
}
|
||||
|
||||
export interface DatabaseDump {
|
||||
name: string;
|
||||
stores: { [s: string]: any };
|
||||
@ -2902,13 +2930,13 @@ export async function importDb(db: IDBDatabase, object: any): Promise<void> {
|
||||
// looks like a IDBDatabase
|
||||
const someDatabase = object.databases;
|
||||
|
||||
if (TALER_META_DB_NAME in someDatabase) {
|
||||
if (TALER_WALLET_META_DB_NAME in someDatabase) {
|
||||
//looks like a taler database
|
||||
const currentMainDbValue =
|
||||
someDatabase[TALER_META_DB_NAME].objectStores.metaConfig.records[0]
|
||||
.value.value;
|
||||
someDatabase[TALER_WALLET_META_DB_NAME].objectStores.metaConfig
|
||||
.records[0].value.value;
|
||||
|
||||
if (currentMainDbValue !== TALER_DB_NAME) {
|
||||
if (currentMainDbValue !== TALER_WALLET_MAIN_DB_NAME) {
|
||||
console.log("not the current database version");
|
||||
}
|
||||
|
||||
@ -3236,7 +3264,7 @@ export async function openTalerDatabase(
|
||||
): Promise<DbAccess<typeof WalletStoresV1>> {
|
||||
const metaDbHandle = await openDatabase(
|
||||
idbFactory,
|
||||
TALER_META_DB_NAME,
|
||||
TALER_WALLET_META_DB_NAME,
|
||||
1,
|
||||
() => {},
|
||||
onMetaDbUpgradeNeeded,
|
||||
@ -3249,17 +3277,17 @@ export async function openTalerDatabase(
|
||||
.runReadWrite(async (tx) => {
|
||||
const dbVersionRecord = await tx.metaConfig.get(CURRENT_DB_CONFIG_KEY);
|
||||
if (!dbVersionRecord) {
|
||||
currentMainVersion = TALER_DB_NAME;
|
||||
currentMainVersion = TALER_WALLET_MAIN_DB_NAME;
|
||||
await tx.metaConfig.put({
|
||||
key: CURRENT_DB_CONFIG_KEY,
|
||||
value: TALER_DB_NAME,
|
||||
value: TALER_WALLET_MAIN_DB_NAME,
|
||||
});
|
||||
} else {
|
||||
currentMainVersion = dbVersionRecord.value;
|
||||
}
|
||||
});
|
||||
|
||||
if (currentMainVersion !== TALER_DB_NAME) {
|
||||
if (currentMainVersion !== TALER_WALLET_MAIN_DB_NAME) {
|
||||
switch (currentMainVersion) {
|
||||
case "taler-wallet-main-v2":
|
||||
case "taler-wallet-main-v3":
|
||||
@ -3275,7 +3303,7 @@ export async function openTalerDatabase(
|
||||
.runReadWrite(async (tx) => {
|
||||
await tx.metaConfig.put({
|
||||
key: CURRENT_DB_CONFIG_KEY,
|
||||
value: TALER_DB_NAME,
|
||||
value: TALER_WALLET_MAIN_DB_NAME,
|
||||
});
|
||||
});
|
||||
break;
|
||||
@ -3288,7 +3316,7 @@ export async function openTalerDatabase(
|
||||
|
||||
const mainDbHandle = await openDatabase(
|
||||
idbFactory,
|
||||
TALER_DB_NAME,
|
||||
TALER_WALLET_MAIN_DB_NAME,
|
||||
WALLET_DB_MINOR_VERSION,
|
||||
onVersionChange,
|
||||
onTalerDbUpgradeNeeded,
|
||||
@ -3305,7 +3333,7 @@ export async function deleteTalerDatabase(
|
||||
idbFactory: IDBFactory,
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = idbFactory.deleteDatabase(TALER_DB_NAME);
|
||||
const req = idbFactory.deleteDatabase(TALER_WALLET_MAIN_DB_NAME);
|
||||
req.onerror = () => reject(req.error);
|
||||
req.onsuccess = () => resolve();
|
||||
});
|
||||
|
@ -139,13 +139,6 @@ export async function createNativeWalletHost2(
|
||||
});
|
||||
}
|
||||
|
||||
const myVersionChange = (): Promise<void> => {
|
||||
logger.error("version change requested, should not happen");
|
||||
throw Error(
|
||||
"BUG: wallet DB version change event can't happen with memory IDB",
|
||||
);
|
||||
};
|
||||
|
||||
let dbResp: MakeDbResult;
|
||||
|
||||
if (args.persistentStoragePath &&args.persistentStoragePath.endsWith(".json")) {
|
||||
@ -160,8 +153,6 @@ export async function createNativeWalletHost2(
|
||||
|
||||
shimIndexedDB(dbResp.idbFactory);
|
||||
|
||||
const myDb = await openTalerDatabase(myIdbFactory, myVersionChange);
|
||||
|
||||
let workerFactory;
|
||||
const cryptoWorkerType = args.cryptoWorkerType ?? "node-worker-thread";
|
||||
if (cryptoWorkerType === "sync") {
|
||||
@ -189,7 +180,7 @@ export async function createNativeWalletHost2(
|
||||
const timer = new SetTimeoutTimerAPI();
|
||||
|
||||
const w = await Wallet.create(
|
||||
myDb,
|
||||
myIdbFactory,
|
||||
myHttpLib,
|
||||
timer,
|
||||
workerFactory,
|
||||
|
@ -110,7 +110,7 @@ async function makeSqliteDb(
|
||||
return {
|
||||
...myBackend.accessStats,
|
||||
primitiveStatements: numStmt,
|
||||
}
|
||||
};
|
||||
},
|
||||
idbFactory: myBridgeIdbFactory,
|
||||
};
|
||||
@ -167,12 +167,15 @@ export async function createNativeWalletHost2(
|
||||
|
||||
let dbResp: MakeDbResult;
|
||||
|
||||
if (args.persistentStoragePath && args.persistentStoragePath.endsWith(".json")) {
|
||||
if (
|
||||
args.persistentStoragePath &&
|
||||
args.persistentStoragePath.endsWith(".json")
|
||||
) {
|
||||
logger.info("using JSON file DB backend (slow!)");
|
||||
dbResp = await makeFileDb(args);
|
||||
} else {
|
||||
logger.info("using sqlite3 DB backend (experimental!)");
|
||||
dbResp = await makeSqliteDb(args)
|
||||
dbResp = await makeSqliteDb(args);
|
||||
}
|
||||
|
||||
const myIdbFactory: IDBFactory = dbResp.idbFactory as any as IDBFactory;
|
||||
@ -189,22 +192,13 @@ export async function createNativeWalletHost2(
|
||||
});
|
||||
}
|
||||
|
||||
const myVersionChange = (): Promise<void> => {
|
||||
logger.error("version change requested, should not happen");
|
||||
throw Error(
|
||||
"BUG: wallet DB version change event can't happen with memory IDB",
|
||||
);
|
||||
};
|
||||
|
||||
const myDb = await openTalerDatabase(myIdbFactory, myVersionChange);
|
||||
|
||||
let workerFactory;
|
||||
workerFactory = new SynchronousCryptoWorkerFactoryPlain();
|
||||
|
||||
const timer = new SetTimeoutTimerAPI();
|
||||
|
||||
const w = await Wallet.create(
|
||||
myDb,
|
||||
myIdbFactory,
|
||||
myHttpLib,
|
||||
timer,
|
||||
workerFactory,
|
||||
|
@ -54,6 +54,7 @@ import {
|
||||
} from "./util/query.js";
|
||||
import { TimerGroup } from "./util/timer.js";
|
||||
import { WalletConfig } from "./wallet-api-types.js";
|
||||
import { IDBFactory } from "@gnu-taler/idb-bridge";
|
||||
|
||||
export const EXCHANGE_COINS_LOCK = "exchange-coins-lock";
|
||||
export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock";
|
||||
@ -203,6 +204,9 @@ export interface InternalWalletState {
|
||||
denomPubHash: string,
|
||||
): Promise<DenominationInfo | undefined>;
|
||||
|
||||
ensureWalletDbOpen(): Promise<void>;
|
||||
|
||||
idb: IDBFactory;
|
||||
db: DbAccess<typeof WalletStoresV1>;
|
||||
http: HttpRequestLibrary;
|
||||
|
||||
|
@ -239,7 +239,7 @@ class ResultStream<T> {
|
||||
export function openDatabase(
|
||||
idbFactory: IDBFactory,
|
||||
databaseName: string,
|
||||
databaseVersion: number,
|
||||
databaseVersion: number | undefined,
|
||||
onVersionChange: () => void,
|
||||
onUpgradeNeeded: (
|
||||
db: IDBDatabase,
|
||||
|
@ -139,6 +139,7 @@ import {
|
||||
clearDatabase,
|
||||
exportDb,
|
||||
importDb,
|
||||
openTalerDatabase,
|
||||
} from "./db.js";
|
||||
import { DevExperimentHttpLib, applyDevExperiment } from "./dev-experiments.js";
|
||||
import {
|
||||
@ -315,6 +316,7 @@ import {
|
||||
getMaxPeerPushAmount,
|
||||
convertWithdrawalAmount,
|
||||
} from "./util/instructedAmountConversion.js";
|
||||
import { IDBFactory } from "@gnu-taler/idb-bridge";
|
||||
|
||||
const logger = new Logger("wallet.ts");
|
||||
|
||||
@ -1539,7 +1541,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
||||
return {};
|
||||
}
|
||||
case WalletApiOperation.ExportDb: {
|
||||
const dbDump = await exportDb(ws.db.idbHandle());
|
||||
const dbDump = await exportDb(ws.idb);
|
||||
return dbDump;
|
||||
}
|
||||
case WalletApiOperation.ImportDb: {
|
||||
@ -1654,14 +1656,14 @@ export class Wallet {
|
||||
private _client: WalletCoreApiClient | undefined;
|
||||
|
||||
private constructor(
|
||||
db: DbAccess<typeof WalletStoresV1>,
|
||||
idb: IDBFactory,
|
||||
http: HttpRequestLibrary,
|
||||
timer: TimerAPI,
|
||||
cryptoWorkerFactory: CryptoWorkerFactory,
|
||||
config?: WalletConfigParameter,
|
||||
) {
|
||||
this.ws = new InternalWalletStateImpl(
|
||||
db,
|
||||
idb,
|
||||
http,
|
||||
timer,
|
||||
cryptoWorkerFactory,
|
||||
@ -1677,13 +1679,13 @@ export class Wallet {
|
||||
}
|
||||
|
||||
static async create(
|
||||
db: DbAccess<typeof WalletStoresV1>,
|
||||
idb: IDBFactory,
|
||||
http: HttpRequestLibrary,
|
||||
timer: TimerAPI,
|
||||
cryptoWorkerFactory: CryptoWorkerFactory,
|
||||
config?: WalletConfigParameter,
|
||||
): Promise<Wallet> {
|
||||
const w = new Wallet(db, http, timer, cryptoWorkerFactory, config);
|
||||
const w = new Wallet(idb, http, timer, cryptoWorkerFactory, config);
|
||||
w._client = await getClientFromWalletState(w.ws);
|
||||
return w;
|
||||
}
|
||||
@ -1725,19 +1727,22 @@ export class Wallet {
|
||||
this.ws.stop();
|
||||
}
|
||||
|
||||
runPending(): Promise<void> {
|
||||
async runPending(): Promise<void> {
|
||||
await this.ws.ensureWalletDbOpen();
|
||||
return runPending(this.ws);
|
||||
}
|
||||
|
||||
runTaskLoop(opts?: RetryLoopOpts): Promise<TaskLoopResult> {
|
||||
async runTaskLoop(opts?: RetryLoopOpts): Promise<TaskLoopResult> {
|
||||
await this.ws.ensureWalletDbOpen();
|
||||
return runTaskLoop(this.ws, opts);
|
||||
}
|
||||
|
||||
handleCoreApiRequest(
|
||||
async handleCoreApiRequest(
|
||||
operation: string,
|
||||
id: string,
|
||||
payload: unknown,
|
||||
): Promise<CoreApiResponse> {
|
||||
await this.ws.ensureWalletDbOpen();
|
||||
return handleCoreApiRequest(this.ws, operation, id, payload);
|
||||
}
|
||||
}
|
||||
@ -1801,12 +1806,17 @@ class InternalWalletStateImpl implements InternalWalletState {
|
||||
|
||||
config: Readonly<WalletConfig>;
|
||||
|
||||
private _db: DbAccess<typeof WalletStoresV1> | undefined = undefined;
|
||||
|
||||
get db(): DbAccess<typeof WalletStoresV1> {
|
||||
if (!this._db) {
|
||||
throw Error("db not initialized");
|
||||
}
|
||||
return this._db;
|
||||
}
|
||||
|
||||
constructor(
|
||||
// FIXME: Make this a getter and make
|
||||
// the actual value nullable.
|
||||
// Check if we are in a DB migration / garbage collection
|
||||
// and throw an error in that case.
|
||||
public db: DbAccess<typeof WalletStoresV1>,
|
||||
public idb: IDBFactory,
|
||||
public http: HttpRequestLibrary,
|
||||
public timer: TimerAPI,
|
||||
cryptoWorkerFactory: CryptoWorkerFactory,
|
||||
@ -1821,6 +1831,20 @@ class InternalWalletStateImpl implements InternalWalletState {
|
||||
}
|
||||
}
|
||||
|
||||
async ensureWalletDbOpen(): Promise<void> {
|
||||
if (this._db) {
|
||||
return;
|
||||
}
|
||||
const myVersionChange = (): Promise<void> => {
|
||||
logger.error("version change requested, should not happen");
|
||||
throw Error(
|
||||
"BUG: wallet DB version change event can't happen with memory IDB",
|
||||
);
|
||||
};
|
||||
const myDb = await openTalerDatabase(this.idb, myVersionChange);
|
||||
this._db = myDb;
|
||||
}
|
||||
|
||||
async getTransactionState(
|
||||
ws: InternalWalletState,
|
||||
tx: GetReadOnlyAccess<typeof WalletStoresV1>,
|
||||
|
@ -298,13 +298,6 @@ async function reinitWallet(): Promise<void> {
|
||||
}
|
||||
currentDatabase = undefined;
|
||||
// setBadgeText({ text: "" });
|
||||
try {
|
||||
currentDatabase = await openTalerDatabase(indexedDB as any, reinitWallet);
|
||||
} catch (e) {
|
||||
logger.error("could not open database", e);
|
||||
walletInit.reject(e);
|
||||
return;
|
||||
}
|
||||
let httpLib;
|
||||
let cryptoWorker;
|
||||
let timer;
|
||||
@ -325,7 +318,7 @@ async function reinitWallet(): Promise<void> {
|
||||
const settings = await platform.getSettingsFromStorage();
|
||||
logger.info("Setting up wallet");
|
||||
const wallet = await Wallet.create(
|
||||
currentDatabase,
|
||||
indexedDB as any,
|
||||
httpLib,
|
||||
timer,
|
||||
cryptoWorker,
|
||||
|
Loading…
Reference in New Issue
Block a user