diff options
Diffstat (limited to 'modules/core')
| -rw-r--r-- | modules/core/core-bundle.js | 57 | ||||
| -rw-r--r-- | modules/core/module-loader.js | 2 | ||||
| -rw-r--r-- | modules/core/virtual-fs.js | 57 |
3 files changed, 111 insertions, 5 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index af59d06..2e57be6 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -374,6 +374,20 @@ class VirtualFileSystem { return this._loadLegacy(); } } + // P0: Check IndexedDB fallback if localStorage is empty + try { + const idbData = await this._loadFromIndexedDB(); + if (idbData) { + const text = await this._decompress(idbData); + const data = JSON.parse(text); + this.root = data.root || this.root; + this.currentPath = data.currentPath || []; + this._usingIndexedDB = true; + this._purgeTrash(); + console.info('FS loaded from IndexedDB fallback'); + return true; + } + } catch (e) { /* IndexedDB not available or empty */ } return this._loadLegacy(); } catch (e) { console.error('FS load error:', e); return false; } } @@ -413,14 +427,50 @@ class VirtualFileSystem { const json = JSON.stringify({ root: this.root, currentPath: this.currentPath }); const compressed = await this._compress(json); if (compressed) { - localStorage.setItem('webx11_filesystem_v2', compressed); - localStorage.removeItem('webx11_filesystem'); + try { + localStorage.setItem('webx11_filesystem_v2', compressed); + localStorage.removeItem('webx11_filesystem'); + } catch (quotaErr) { + // P0: IndexedDB fallback when localStorage quota exceeded + console.warn('localStorage quota exceeded, falling back to IndexedDB:', quotaErr.message); + await this._saveToIndexedDB(compressed); + this._usingIndexedDB = true; + } } else { localStorage.setItem('webx11_filesystem', json); } } catch (e) { console.error('FS save error:', e); } }, 2000); } + // P0: IndexedDB fallback storage for large VFS data + _openIDB() { + return new Promise((resolve, reject) => { + const req = indexedDB.open('webx11_vfs', 1); + req.onupgradeneeded = () => { + req.result.createObjectStore('vfs'); + }; + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); + } + async _saveToIndexedDB(data) { + const db = await this._openIDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction('vfs', 'readwrite'); + tx.objectStore('vfs').put(data, 'filesystem_v2'); + tx.oncomplete = () => { db.close(); resolve(true); }; + tx.onerror = () => { db.close(); reject(tx.error); }; + }); + } + async _loadFromIndexedDB() { + const db = await this._openIDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction('vfs', 'readonly'); + const req = tx.objectStore('vfs').get('filesystem_v2'); + req.onsuccess = () => { db.close(); resolve(req.result || null); }; + req.onerror = () => { db.close(); reject(req.error); }; + }); + } getCurrentDir() { let dir = this.root; for (let part of this.currentPath) { @@ -919,6 +969,9 @@ class VirtualFileSystem { this.currentPath = []; // Wipe all localStorage keys ['webx11_filesystem', 'webx11_filesystem_v2'].forEach(k => localStorage.removeItem(k)); + // P0: Also clear IndexedDB fallback + try { indexedDB.deleteDatabase('webx11_vfs'); } catch (e) {} + this._usingIndexedDB = false; return true; } } diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index f1608ed..6927743 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -4,7 +4,7 @@ // Core module bundle loaded synchronously in main.html. // This loader only handles version cache-busting, splash, terminal init, and optional ext modules. - const LATEST_VER = '2026-06-18-v4.5.74'; + const LATEST_VER = '2026-06-18-v4.5.75'; const CACHE = '?v=' + LATEST_VER; const stored = localStorage.getItem('webx11_cache_ver'); if (stored !== LATEST_VER) { diff --git a/modules/core/virtual-fs.js b/modules/core/virtual-fs.js index 827c446..7f6b441 100644 --- a/modules/core/virtual-fs.js +++ b/modules/core/virtual-fs.js @@ -372,6 +372,20 @@ class VirtualFileSystem { return this._loadLegacy(); } } + // P0: Check IndexedDB fallback if localStorage is empty + try { + const idbData = await this._loadFromIndexedDB(); + if (idbData) { + const text = await this._decompress(idbData); + const data = JSON.parse(text); + this.root = data.root || this.root; + this.currentPath = data.currentPath || []; + this._usingIndexedDB = true; + this._purgeTrash(); + console.info('FS loaded from IndexedDB fallback'); + return true; + } + } catch (e) { /* IndexedDB not available or empty */ } return this._loadLegacy(); } catch (e) { console.error('FS load error:', e); return false; } } @@ -411,14 +425,50 @@ class VirtualFileSystem { const json = JSON.stringify({ root: this.root, currentPath: this.currentPath }); const compressed = await this._compress(json); if (compressed) { - localStorage.setItem('webx11_filesystem_v2', compressed); - localStorage.removeItem('webx11_filesystem'); + try { + localStorage.setItem('webx11_filesystem_v2', compressed); + localStorage.removeItem('webx11_filesystem'); + } catch (quotaErr) { + // P0: IndexedDB fallback when localStorage quota exceeded + console.warn('localStorage quota exceeded, falling back to IndexedDB:', quotaErr.message); + await this._saveToIndexedDB(compressed); + this._usingIndexedDB = true; + } } else { localStorage.setItem('webx11_filesystem', json); } } catch (e) { console.error('FS save error:', e); } }, 2000); } + // P0: IndexedDB fallback storage for large VFS data + _openIDB() { + return new Promise((resolve, reject) => { + const req = indexedDB.open('webx11_vfs', 1); + req.onupgradeneeded = () => { + req.result.createObjectStore('vfs'); + }; + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); + } + async _saveToIndexedDB(data) { + const db = await this._openIDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction('vfs', 'readwrite'); + tx.objectStore('vfs').put(data, 'filesystem_v2'); + tx.oncomplete = () => { db.close(); resolve(true); }; + tx.onerror = () => { db.close(); reject(tx.error); }; + }); + } + async _loadFromIndexedDB() { + const db = await this._openIDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction('vfs', 'readonly'); + const req = tx.objectStore('vfs').get('filesystem_v2'); + req.onsuccess = () => { db.close(); resolve(req.result || null); }; + req.onerror = () => { db.close(); reject(req.error); }; + }); + } getCurrentDir() { let dir = this.root; for (let part of this.currentPath) { @@ -917,6 +967,9 @@ class VirtualFileSystem { this.currentPath = []; // Wipe all localStorage keys ['webx11_filesystem', 'webx11_filesystem_v2'].forEach(k => localStorage.removeItem(k)); + // P0: Also clear IndexedDB fallback + try { indexedDB.deleteDatabase('webx11_vfs'); } catch (e) {} + this._usingIndexedDB = false; return true; } } |