(function () { 'use strict'; var DB_NAME = 'soondesign_local'; var DB_VERSION = 1; var MAX_BLOBS = 20; var THUMB_MAX_BYTES = 102400; var dbPromise = null; var idbAvailable = typeof indexedDB !== 'undefined'; function soonExtractThumbFromJson(json) { if (!json || !json.frontDisplayPic || typeof json.frontDisplayPic !== 'string') return ''; var candidate = json.frontDisplayPic.trim(); if (candidate.indexOf('data:image/') !== 0) return ''; if (candidate.length > THUMB_MAX_BYTES) return ''; return candidate; } function txDone(tx) { return new Promise(function (resolve, reject) { tx.oncomplete = function () { resolve(); }; tx.onerror = function () { reject(tx.error || new Error('idb_tx_error')); }; tx.onabort = function () { reject(tx.error || new Error('idb_tx_abort')); }; }); } function reqPromise(req) { return new Promise(function (resolve, reject) { req.onsuccess = function () { resolve(req.result); }; req.onerror = function () { reject(req.error || new Error('idb_req_error')); }; }); } function soonLocalOpen() { if (!idbAvailable) return Promise.resolve(null); if (dbPromise) return dbPromise; dbPromise = new Promise(function (resolve, reject) { var req = indexedDB.open(DB_NAME, DB_VERSION); req.onupgradeneeded = function (e) { var db = e.target.result; if (!db.objectStoreNames.contains('blobs')) { db.createObjectStore('blobs', { keyPath: 'cacheKey' }); } if (!db.objectStoreNames.contains('thumbs')) { db.createObjectStore('thumbs', { keyPath: 'cacheKey' }); } }; req.onsuccess = function () { resolve(req.result); }; req.onerror = function () { idbAvailable = false; dbPromise = null; reject(req.error || new Error('idb_open_failed')); }; }).catch(function () { idbAvailable = false; dbPromise = null; return null; }); return dbPromise; } function soonLocalGet(cacheKey) { if (!cacheKey || !idbAvailable) return Promise.resolve(null); return soonLocalOpen().then(function (db) { if (!db) return null; var tx = db.transaction('blobs', 'readonly'); return reqPromise(tx.objectStore('blobs').get(cacheKey)).then(function (row) { if (!row || row.json == null) return null; return { json: row.json, meta: { name: row.name, type: row.type, source: row.source, updatedAt: row.updatedAt, savedAt: row.savedAt, bytes: row.bytes } }; }); }).catch(function () { return null; }); } function soonLocalEvictLRU(max) { max = max || MAX_BLOBS; return soonLocalOpen().then(function (db) { if (!db) return; var tx = db.transaction(['blobs', 'thumbs'], 'readwrite'); var blobStore = tx.objectStore('blobs'); return reqPromise(blobStore.getAll()).then(function (rows) { if (!rows || rows.length <= max) return txDone(tx); rows.sort(function (a, b) { return (a.savedAt || 0) - (b.savedAt || 0); }); var toRemove = rows.length - max; for (var i = 0; i < toRemove; i++) { var k = rows[i].cacheKey; blobStore.delete(k); tx.objectStore('thumbs').delete(k); } return txDone(tx); }); }).catch(function () { /* ignore */ }); } function soonLocalPut(cacheKey, json, meta) { if (!cacheKey || json == null || !idbAvailable) return Promise.resolve(false); meta = meta || {}; var jsonObj = typeof json === 'string' ? (function () { try { return JSON.parse(json); } catch (e) { return null; } })() : json; if (!jsonObj) return Promise.resolve(false); var bytes = 0; try { bytes = JSON.stringify(jsonObj).length; } catch (e) { bytes = 0; } var row = { cacheKey: cacheKey, json: jsonObj, name: meta.name || '', type: meta.type != null ? meta.type : (jsonObj.soonType || 1), source: meta.source || '', updatedAt: meta.updatedAt || '', savedAt: Date.now(), bytes: bytes }; return soonLocalOpen().then(function (db) { if (!db) return false; var tx = db.transaction('blobs', 'readwrite'); tx.objectStore('blobs').put(row); return txDone(tx).then(function () { return soonLocalEvictLRU(MAX_BLOBS).then(function () { return true; }); }); }).catch(function () { return false; }); } function soonLocalPutThumb(cacheKey, dataUrl) { if (!cacheKey || !dataUrl || !idbAvailable) return Promise.resolve(false); if (String(dataUrl).length > THUMB_MAX_BYTES) return Promise.resolve(false); return soonLocalOpen().then(function (db) { if (!db) return false; var tx = db.transaction('thumbs', 'readwrite'); tx.objectStore('thumbs').put({ cacheKey: cacheKey, dataUrl: dataUrl, savedAt: Date.now() }); return txDone(tx).then(function () { return true; }); }).catch(function () { return false; }); } function soonLocalGetThumb(cacheKey) { if (!cacheKey || !idbAvailable) return Promise.resolve(''); return soonLocalOpen().then(function (db) { if (!db) return ''; var tx = db.transaction('thumbs', 'readonly'); return reqPromise(tx.objectStore('thumbs').get(cacheKey)).then(function (row) { return (row && row.dataUrl) ? row.dataUrl : ''; }); }).catch(function () { return ''; }); } function soonLocalRemove(cacheKey) { if (!cacheKey || !idbAvailable) return Promise.resolve(); return soonLocalOpen().then(function (db) { if (!db) return; var tx = db.transaction(['blobs', 'thumbs'], 'readwrite'); tx.objectStore('blobs').delete(cacheKey); tx.objectStore('thumbs').delete(cacheKey); return txDone(tx); }).catch(function () { /* ignore */ }); } function soonLocalRenameKey(oldKey, newKey, meta) { if (!oldKey || !newKey || oldKey === newKey) return Promise.resolve(false); return soonLocalGet(oldKey).then(function (hit) { if (!hit) return false; return soonLocalPut(newKey, hit.json, Object.assign({}, hit.meta, meta || {})).then(function (ok) { if (!ok) return false; return soonLocalGetThumb(oldKey).then(function (thumb) { var chain = Promise.resolve(); if (thumb) chain = soonLocalPutThumb(newKey, thumb); return chain.then(function () { return soonLocalRemove(oldKey); }).then(function () { return true; }); }); }); }).catch(function () { return false; }); } function soonLocalCacheAndThumb(cacheKey, json, meta) { var thumb = soonExtractThumbFromJson(json); return soonLocalPut(cacheKey, json, meta).then(function (ok) { if (!ok) return false; if (thumb) return soonLocalPutThumb(cacheKey, thumb).then(function () { return true; }); return true; }); } function soonLocalIsTemplateStale(cacheKey, blobMeta) { if (!cacheKey || cacheKey.indexOf('soondesign_template:') !== 0) return false; var tplMeta = window._soonTemplateMeta; if (!tplMeta || !tplMeta.updated_at) return false; if (!blobMeta || !blobMeta.updatedAt) return false; return String(tplMeta.updated_at) !== String(blobMeta.updatedAt); } window.soonLocalOpen = soonLocalOpen; window.soonLocalGet = soonLocalGet; window.soonLocalPut = soonLocalPut; window.soonLocalPutThumb = soonLocalPutThumb; window.soonLocalGetThumb = soonLocalGetThumb; window.soonLocalRemove = soonLocalRemove; window.soonLocalRenameKey = soonLocalRenameKey; window.soonLocalEvictLRU = soonLocalEvictLRU; window.soonLocalCacheAndThumb = soonLocalCacheAndThumb; window.soonLocalIsTemplateStale = soonLocalIsTemplateStale; window.soonExtractThumbFromJson = soonExtractThumbFromJson; var LS_DROP = ['soondesign_recent', 'soondesign_history', 'soondesign_recent_migrated']; var SS_DROP = ['soondesign_open_file', 'soondesign_open_type', 'soondesign_open_meta', 'soondesign_pending_import']; function dropStorageKeys(storage, prefix) { if (!storage) return; try { var rm = []; for (var i = 0; i < storage.length; i++) { var k = storage.key(i); if (k && k.indexOf(prefix) === 0) rm.push(k); } rm.forEach(function (k) { storage.removeItem(k); }); } catch (e) { /* ignore */ } } function soonClearLocalDesignCache() { try { LS_DROP.forEach(function (k) { localStorage.removeItem(k); }); dropStorageKeys(localStorage, 'soondesign_session:'); SS_DROP.forEach(function (k) { sessionStorage.removeItem(k); }); dropStorageKeys(sessionStorage, 'soondesign_session:'); } catch (e) { /* ignore */ } dbPromise = null; if (typeof indexedDB === 'undefined') return Promise.resolve(); return new Promise(function (resolve) { var done = function () { resolve(); }; try { var req = indexedDB.deleteDatabase(DB_NAME); req.onsuccess = req.onerror = req.onblocked = done; } catch (e2) { done(); } }); } window.soonClearLocalDesignCache = soonClearLocalDesignCache; })();