diff --git a/src/js/AssetProvider.js b/src/js/AssetProvider.js index 9d638fa..77ce27b 100644 --- a/src/js/AssetProvider.js +++ b/src/js/AssetProvider.js @@ -84,7 +84,6 @@ export default class AssetProvider { try { this.starting = true; // restore tracked assets - this.ready = true; const trackedAssets = await this.store.get("trackedAssets"); if (trackedAssets) { @@ -102,6 +101,7 @@ export default class AssetProvider { this.staticIcons = Icons; this.specialSymbols = SpecialSymbols; + this.ready = true; } catch (e) { console.error(e); } finally { @@ -198,45 +198,57 @@ export default class AssetProvider { async track(assetHash, noInit = false) { if (!noInit) await this._init(); if (assetHash === this.baseAssetId) return; - if (this._isFiat(assetHash)) { - if (this.trackedFiatAssets.indexOf(assetHash) < 0) { - this.trackedFiatAssets.push(assetHash); - await this.store.set("trackedFiatAssets", this.trackedFiatAssets); + await this.store.lock("trackedAssets"); + try { + console.log("Track", assetHash); + + if (this._isFiat(assetHash)) { + if (this.trackedFiatAssets.indexOf(assetHash) < 0) { + this.trackedFiatAssets.push(assetHash); + await this.store.set("trackedFiatAssets", this.trackedFiatAssets); + } + return; } - return; - } - if (this.trackedAssets.indexOf(assetHash) >= 0) return; + if (this.trackedAssets.indexOf(assetHash) >= 0) return; - this.trackedAssets.push(assetHash); - await this.store.set("trackedAssets", this.trackedAssets); + this.trackedAssets.push(assetHash); + await this.store.set("trackedAssets", this.trackedAssets); - let first = true; - return new Promise((res, rej) => { - const trackerCallback = async (price, baseAssetId) => { - await this.cache.set("p:" + assetHash, price); - if (first) { - res(price); - first = false; - } - }; - if (!this.trackerCallbacks) this.trackerCallbacks = {}; - this.trackerCallbacks[assetHash] = trackerCallback; - this.sideSwap.subscribeToAssetPriceUpdate(assetHash, trackerCallback); - }); + let first = true; + return new Promise((res, rej) => { + const trackerCallback = async (price, baseAssetId) => { + await this.cache.set("p:" + assetHash, price); + if (first) { + res(price); + first = false; + } + }; + if (!this.trackerCallbacks) this.trackerCallbacks = {}; + this.trackerCallbacks[assetHash] = trackerCallback; + this.sideSwap.subscribeToAssetPriceUpdate(assetHash, trackerCallback); + }); + } finally { + await this.store.unlock("trackedAssets"); + } } async untrack(assetHash) { if (assetHash === this.baseAssetId) return; - if (this._isFiat(assetHash)) return; - const index = this.trackedAssets.indexOf(assetHash); - if (index < 0) return; - this.trackedAssets.splice(index, 1); - await this.store.set("trackedAssets", this.trackedAssets); - const trackerCallback = this.trackerCallbacks[assetHash]; - if (trackerCallback) { - this.sideSwap.unsubscribeFromAssetPriceUpdate(assetHash, trackerCallback); - delete this.trackerCallbacks[assetHash]; + await this.store.lock("trackedAssets"); + try { + if (this._isFiat(assetHash)) return; + const index = this.trackedAssets.indexOf(assetHash); + if (index < 0) return; + this.trackedAssets.splice(index, 1); + await this.store.set("trackedAssets", this.trackedAssets); + const trackerCallback = this.trackerCallbacks[assetHash]; + if (trackerCallback) { + this.sideSwap.unsubscribeFromAssetPriceUpdate(assetHash, trackerCallback); + delete this.trackerCallbacks[assetHash]; + } + } finally { + await this.store.unlock("trackedAssets"); } } diff --git a/src/js/Constants.js b/src/js/Constants.js index e5637cb..d2ee33d 100644 --- a/src/js/Constants.js +++ b/src/js/Constants.js @@ -7,7 +7,7 @@ export default { HARDCODED_FEE: 0.27, // used when the fee api fails DEBOUNCE_CALLBACK_TIME: 500, // lower this value to make the app more responsive, but more resource intensive APP_ID: "lq", // The app id - APP_VERSION: 1, // bumping this invalidates all cache and storage + APP_VERSION: 10, // bumping this invalidates all cache and storage STORAGE_METHODS: ["LocalStore", "IDBStore", "MemStore"], // order matters, first is preferred STORAGE_METHODS_BY_SPEED: ["LocalStore", "IDBStore", "MemStore"], // order matters, first is fastest diff --git a/src/js/LiquidWallet.js b/src/js/LiquidWallet.js index 4c5a29c..66fe5e4 100644 --- a/src/js/LiquidWallet.js +++ b/src/js/LiquidWallet.js @@ -1409,7 +1409,7 @@ export default class LiquidWallet { */ async pinAsset(assetHash) { await this.check(); - this.assetProvider.track(assetHash); + return this.assetProvider.track(assetHash); } /** @@ -1418,7 +1418,7 @@ export default class LiquidWallet { */ async unpinAsset(assetHash) { await this.check(); - this.assetProvider.untrack(assetHash); + return this.assetProvider.untrack(assetHash); } /** diff --git a/src/js/storage/AbstractBrowserStore.js b/src/js/storage/AbstractBrowserStore.js index 32579ea..7d8b4a1 100644 --- a/src/js/storage/AbstractBrowserStore.js +++ b/src/js/storage/AbstractBrowserStore.js @@ -9,6 +9,21 @@ export default class AbstractBrowserStore { constructor(prefix, limit) { this.limit = limit; this.prefix = prefix; + this.locks = {}; + } + + async lock(key) { + let t = 1; + while (this.locks[key]) { + await new Promise((resolve) => setTimeout(resolve, t)); + t *= 2; + if (t > 100) t = 100; + } + this.locks[key] = true; + } + + unlock(key) { + delete this.locks[key]; } async _init() { @@ -21,9 +36,9 @@ export default class AbstractBrowserStore { this.starting = true; this.accessTable = await this._retrieve("s:accessTable"); - this.expirationTable = await this._retrieve("s:expirationTable"); this.sizeTable = await this._retrieve("s:sizeTable"); + if (!this.accessTable) { this.accessTable = new Map(); } @@ -79,9 +94,9 @@ export default class AbstractBrowserStore { if (!value) { await this._delete(key); - this.accessTable.delete(key); - this.expirationTable.delete(key); - this.sizeTable.delete(key); + await this._setAccessTime(key, undefined); + await this._setExpiration(key, undefined); + await this._setSize(key, undefined); } else { const entrySize = await this._calcSize(key, value); if (this.limit) { @@ -90,26 +105,51 @@ export default class AbstractBrowserStore { } } await this._store(key, value); - // localStorage.setItem(key, JSON.stringify(value)); - this.accessTable.set(key, Date.now()); - if (expiration) this.expirationTable.set(key, Date.now() + expiration); + await this._setAccessTime(key, Date.now()); + await this._setExpiration(key, expiration ? Date.now() + expiration : undefined); + await this._setSize(key, entrySize); + } + } + + async _setAccessTime(key, time) { + await this._init(); + await this.lock("s:"); + try { + if (time) this.accessTable.set(key, time); + else this.accessTable.delete(key); + await this._store("s:accessTable", this.accessTable); + } finally { + this.unlock("s:"); + } + } + + async _setExpiration(key, time) { + await this._init(); + await this.lock("s:"); + try { + if (time) this.expirationTable.set(key, time); else this.expirationTable.delete(key); - this.sizeTable.set(key, entrySize); + await this._store("s:expirationTable", this.expirationTable); + } finally { + this.unlock("s:"); } - this._store("s:accessTable", this.accessTable); - this._store("s:expirationTable", this.expirationTable); - this._store("s:sizeTable", this.sizeTable); - // localStorage.setItem('accessTable', JSON.stringify(Array.from(this.accessTable.entries()))); - // localStorage.setItem('expirationTable', JSON.stringify(Array.from(this.expirationTable.entries()))); - // localStorage.setItem('sizeTable', JSON.stringify(Array.from(this.sizeTable.entries()))); } - async clear() { + async _setSize(key, size) { await this._init(); + await this.lock("s:"); + try { + if (size) this.sizeTable.set(key, size); + else this.sizeTable.delete(key); + await this._store("s:sizeTable", this.sizeTable); + } finally { + this.unlock("s:"); + } + } + async clear() { + await this._init(); const keys = this.accessTable.keys(); - console.log(keys); - for (const key of keys) { if (key.startsWith("s:")) continue; console.log("Clearing " + key); @@ -123,8 +163,10 @@ export default class AbstractBrowserStore { if (key.startsWith("s:")) throw new Error("Key cannot start with s:"); let value; - let exists = this.accessTable.has(key) && this.sizeTable.has(key); // corrupted data + let exists = this.accessTable.has(key) && this.sizeTable.has(key); if (!exists) { + // corrupted data + console.log("Corrupted data for " + key); await this.set(key, undefined); value = undefined; } else { @@ -132,12 +174,12 @@ export default class AbstractBrowserStore { } if (value) { - this.accessTable.set(key, Date.now()); - await this._store("s:accessTable", this.accessTable); + await this._setAccessTime(key, Date.now()); } const expire = this.expirationTable.get(key); if (!value || (expire && expire < Date.now())) { + console.log("Refreshing " + key); if (refreshCallback) { let refreshed = Promise.resolve(refreshCallback()); refreshed = refreshed.then(async (data) => { @@ -170,7 +212,6 @@ export default class AbstractBrowserStore { for (let [key, access] of this.accessTable) { if (key.startsWith("s:")) continue; if (access < oldestAccess) { - alert("Deleting " + key); oldestAccess = access; oldestKey = key; }