UNPKG

@atomiqlabs/sdk-lib

Version:

Basic SDK functionality library for atomiq

264 lines (263 loc) 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.IndexedDBUnifiedStorage = void 0; const Utils_1 = require("../utils/Utils"); function toCompositeIndex(values) { if (values.length === 0) return []; if (values.length === 1) { return values[0]; } else { const compositeArray = []; const firstValues = values.shift(); const restValues = toCompositeIndex(values); for (let value of firstValues) { for (let restValue of restValues) { compositeArray.push([value].concat(restValue)); } } return compositeArray; } } function matches(conditions, obj) { for (let condition of conditions) { let value = obj[condition.key]; if (!condition.values.has(value)) return false; } return true; } function toSetConditions(input) { return input.map(val => { return { key: val.key, values: Array.isArray(val.value) ? new Set(val.value) : new Set([val.value]) }; }); } const indexes = { "escrowHash": { key: "escrowHash", unique: true }, "type": { key: "type", unique: false }, "initiator": { key: "initiator", unique: false }, "initiator, id": { key: ["initiator", "id"], unique: false }, "type, state": { key: ["type", "state"], unique: false }, "type, paymentHash": { key: ["type", "paymentHash"], unique: false }, "type, initiator, state": { key: ["type", "initiator", "state"], unique: false } }; class IndexedDBUnifiedStorage { constructor(storageKey) { this.storageKey = storageKey; this.logger = (0, Utils_1.getLogger)("IndexedDBUnifiedStorage(" + this.storageKey + "): "); } //Reviver also needs to update the swap to the latest version async tryMigrateLocalStorage(storageKey, swapType, reviver) { const txt = window.localStorage.getItem(storageKey); if (txt == null) return false; let data; try { data = JSON.parse(txt); } catch (e) { this.logger.warn("tryMigrate(" + storageKey + "): Tried to migrate the database, but cannot parse old local storage!"); return false; } let swaps = Object.keys(data).map(id => { let swapData = data[id]; swapData.type = swapType; return reviver(swapData); }); await this.saveAll(swaps.map(swap => swap.serialize())); window.localStorage.removeItem(storageKey); this.logger.info("tryMigrate(" + storageKey + "): Database successfully migrated from localStorage to unifiedIndexedDB!"); return true; } //Reviver also needs to update the swap to the latest version async tryMigrateOldIndexedDB(storageKey, swapType, reviver) { const databases = await window.indexedDB.databases(); if (databases.find(val => val.name === storageKey) == null) { this.logger.info("tryMigrateOldIndexedDB(" + storageKey + "): Old database not found!"); return false; } this.logger.debug("tryMigrateOldIndexedDB(" + storageKey + "): Old database found!"); let db; try { db = await new Promise((resolve, reject) => { const request = window.indexedDB.open(storageKey, 1); request.onerror = (e) => reject(e); request.onsuccess = (e) => resolve(e.target.result); }); } catch (e) { this.logger.warn("tryMigrateOldIndexedDB(" + storageKey + "): Error opening old IndexedDB!", e); return false; } this.logger.debug("tryMigrateOldIndexedDB(" + storageKey + "): Connection opened!"); try { const data = await new Promise((resolve, reject) => { const tx = db.transaction("swaps", "readonly", { durability: "strict" }); const store = tx.objectStore("swaps"); const req = store.getAll(); req.onsuccess = (event) => resolve(event.target.result); req.onerror = (event) => reject(event); }); this.logger.debug("tryMigrateOldIndexedDB(" + storageKey + "): Data retrieved!"); let swaps = data.map(({ id, data }) => { data.type = swapType; return reviver(data); }); this.logger.debug("tryMigrateOldIndexedDB(" + storageKey + "): Data revived!"); await this.saveAll(swaps.map(swap => swap.serialize())); this.logger.debug("tryMigrateOldIndexedDB(" + storageKey + "): Data saved!"); //Remove the old database db.close(); this.logger.debug("tryMigrateOldIndexedDB(" + storageKey + "): DB connection closed!"); await new Promise((resolve, reject) => { const res = window.indexedDB.deleteDatabase(storageKey); res.onsuccess = () => resolve(); res.onerror = (e) => reject(e); }); this.logger.info("tryMigrateOldIndexedDB(" + storageKey + "): Database successfully migrated from oldIndexedDB to unifiedIndexedDB!"); return true; } catch (e) { this.logger.warn("tryMigrateOldIndexedDB(" + storageKey + "): Tried to migrate the database, but cannot parse oldIndexedDB!", e); return false; } } //NOTE: Reviver also needs to update the swap to the latest version async tryMigrate(storageKeys, reviver) { let someMigrated = false; for (let storageKey of storageKeys) { this.logger.info("tryMigrate(): Trying to migrate...", storageKey); if (await this.tryMigrateLocalStorage(storageKey[0], storageKey[1], reviver)) someMigrated = true; if (await this.tryMigrateOldIndexedDB(storageKey[0], storageKey[1], reviver)) someMigrated = true; } return someMigrated; } executeTransaction(cbk, readonly) { return new Promise((resolve, reject) => { const tx = this.db.transaction("swaps", readonly ? "readonly" : "readwrite", { durability: "strict" }); const req = cbk(tx.objectStore("swaps")); req.onsuccess = (event) => resolve(event.target.result); req.onerror = (event) => reject(event); }); } executeTransactionArr(cbk, readonly) { const tx = this.db.transaction("swaps", readonly ? "readonly" : "readwrite", { durability: "strict" }); const reqs = cbk(tx.objectStore("swaps")); return Promise.all(reqs.map(req => new Promise((resolve, reject) => { req.onsuccess = (event) => resolve(event.target.result); req.onerror = (event) => reject(event); }))); } executeTransactionWithCursor(cbk, valueCbk) { return new Promise((resolve, reject) => { const tx = this.db.transaction("swaps", "readonly", { durability: "strict" }); const cursorRequests = cbk(tx.objectStore("swaps")); const resultObjects = []; for (let cursorRequest of cursorRequests) { cursorRequest.onsuccess = (event) => { const cursor = event.target.result; if (cursor != null) { const value = cursor.value; if (valueCbk(value)) resultObjects.push(value); cursor.continue(); } else { resolve(resultObjects); } }; cursorRequest.onerror = (event) => reject(event); } }); } async init() { if (this.db == null) { this.db = await new Promise((resolve, reject) => { const request = window.indexedDB.open(this.storageKey, 1); request.onupgradeneeded = (event) => { const db = event.target.result; const objectStore = db.createObjectStore("swaps", { keyPath: "id" }); Object.keys(indexes).forEach(name => { const index = indexes[name]; objectStore.createIndex(name, index.key, { unique: index.unique }); }); }; request.onerror = (e) => reject(e); request.onsuccess = (e) => resolve(e.target.result); }); } } /** * Params are specified in the following way: * - [[condition1, condition2]] - returns all rows where condition1 AND condition2 is met * - [[condition1], [condition2]] - returns all rows where condition1 OR condition2 is met * - [[condition1, condition2], [condition3]] - returns all rows where (condition1 AND condition2) OR condition3 is met * @param params */ async query(params) { if (params.length === 0) return await this.querySingle([]); const results = await Promise.all(params.map(singleParam => this.querySingle(singleParam))); const resultSet = new Set(results.flat()); //Deduplicate return Array.from(resultSet); } async querySingle(params) { if (params.length === 0) { return await this.executeTransaction((objectStore) => objectStore.getAll(), true); } const queryKeys = params.map(param => param.key); const requiredIndex = queryKeys.join(", "); if (requiredIndex === "id") { //ID is the index const values = Array.isArray(params[0].value) ? params[0].value : [params[0].value]; const res = await this.executeTransactionArr((objectStore) => { return values.map(val => objectStore.getAll(val)); }, true); return res.flat(); } else if (indexes[requiredIndex] != null) { //Index exists const values = params.map(param => Array.isArray(param.value) ? param.value : [param.value]); const compositeIndexQueries = toCompositeIndex(values); const resp = await this.executeTransactionArr(objectStore => { const index = objectStore.index(requiredIndex); return compositeIndexQueries.map(indexQuery => index.getAll(indexQuery)); }, true); return resp.flat(); } else { //Need to go over all values this.logger.warn("query(): Index cannot be used for query, required index: " + requiredIndex + " query params: ", params); const setConditions = toSetConditions(params); return await this.executeTransactionWithCursor(objectStore => [objectStore.openCursor()], (val) => matches(setConditions, val)); } } async remove(object) { await this.executeTransaction(store => store.delete(object.id), false) .catch(() => null); } async removeAll(arr) { if (arr.length === 0) return; await this.executeTransactionArr(store => arr.map(object => { return store.delete(object.id); }), false); } async save(object) { await this.executeTransaction(store => store.put(object), false); } async saveAll(arr) { if (arr.length === 0) return; await this.executeTransactionArr(store => arr.map(object => { return store.put(object); }), false); } } exports.IndexedDBUnifiedStorage = IndexedDBUnifiedStorage;