Fix : Properly clear App Cache and IndexedDB on Resetfix reset btn to clear cache #1136#1413
Fix : Properly clear App Cache and IndexedDB on Resetfix reset btn to clear cache #1136#1413infinityanant wants to merge 3 commits intokiwix:mainfrom
Conversation
|
Hi @Jaifroid, could you please review this PR whenever you have a moment? I'd be happy to implement any changes or improvements you suggest. Thanks! |
There was a problem hiding this comment.
Pull request overview
This PR fixes the “Reset App” behavior so that CacheStorage and IndexedDB are actually cleared before the app reloads, even when no Service Worker controller is available.
Changes:
- Switch cache deletion to use
caches.keys()and delete caches directly via the Cache API. - Convert IndexedDB and Cache API deletions into Promises and wait for completion with
Promise.all()before reloading. - Minor formatting adjustments in
settingsStore.js(function spacing/indentation).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| cachePromise = caches.keys().then(function (cacheNames) { | ||
| if (cacheNames && cacheNames.length > 0) { | ||
| var deletePromises = cacheNames.map(function (cacheName) { | ||
| console.debug('Deleting cache: ' + cacheName); | ||
| return caches.delete(cacheName); | ||
| }); | ||
| return Promise.all(deletePromises); |
There was a problem hiding this comment.
caches.keys() returns all CacheStorage entries for the origin, which can include caches unrelated to this app (if multiple apps share the same domain). Consider filtering cacheNames to only delete Kiwix caches (e.g. by known names from the SW or a kiwix prefix) to avoid wiping other apps' caches on the same origin.
| indexedDBPromise = indexedDB.databases().then(function (dbs) { | ||
| var deletePromises = dbs.map(function (db) { | ||
| console.debug('Deleting ' + db.name + '...'); | ||
| return new Promise(function (resolve) { | ||
| var req = indexedDB.deleteDatabase(db.name); | ||
| req.onsuccess = resolve; |
There was a problem hiding this comment.
In the indexedDB.databases() branch, db.name can be null/undefined (per IDBDatabaseInfo). Calling indexedDB.deleteDatabase(db.name) will coerce it to a string like 'null'/'undefined'. Please guard/skip entries without a valid name before attempting deletion.
| return new Promise(function (resolve) { | ||
| var req = indexedDB.deleteDatabase(db.name); | ||
| req.onsuccess = resolve; | ||
| req.onerror = resolve; // Resolve even on error | ||
| }); |
There was a problem hiding this comment.
The IndexedDB delete promise resolves on onsuccess/onerror but does not handle onblocked. If any DB connection is still open, deleteDatabase may fire onblocked and never reach success/error, causing the reset to hang and never reload. Please handle onblocked (and consider logging req.error on onerror) so the overall reset can complete deterministically.
| deleteRequest.onerror = function (ev) { | ||
| console.error('Error deleting ' + dbName + '...', ev); | ||
| resolve(); // Resolve even on error | ||
| }; |
There was a problem hiding this comment.
Same as above for the Firefox branch: the deleteDatabase request can become blocked by open connections and never fire onsuccess/onerror, which would prevent Promise.all(...) from resolving and stop the reload. Please attach an onblocked handler (and optionally surface/log blocked state) to ensure reset completes.
| }; | |
| }; | |
| deleteRequest.onblocked = function (ev) { | |
| console.warn('Deletion of ' + dbName + ' is blocked, proceeding with reset...', ev); | |
| resolve(); // Resolve even when blocked to avoid hanging the reset | |
| }; |
|
|
||
|
|
There was a problem hiding this comment.
There are multiple extra blank lines before getCacheNames that were introduced in this change. Please remove the redundant empty lines to keep formatting consistent.
| // function reset(object) { | ||
| // // 1. Clear any cookie entries | ||
| // if (!object || object === 'cookie') { | ||
| // var regexpCookieKeys = /(?:^|;)\s*([^=]+)=([^;]*)/ig; | ||
| // var currentCookie = document.cookie; | ||
| // var foundCrumb = false; | ||
| // var cookieCrumb = regexpCookieKeys.exec(currentCookie); | ||
| // while (cookieCrumb !== null) { | ||
| // // DEV: Note that we don't use the keyPrefix in legacy cookie support | ||
| // foundCrumb = true; | ||
| // // This expiry date will cause the browser to delete the cookie crumb on next page refresh | ||
| // document.cookie = cookieCrumb[1] + '=;expires=Thu, 21 Sep 1979 00:00:01 UTC;'; | ||
| // cookieCrumb = regexpCookieKeys.exec(currentCookie); | ||
| // } | ||
| // if (foundCrumb) console.debug('All cookie keys were expired...'); | ||
| // } | ||
|
|
||
| // // 2. Clear any localStorage settings | ||
| // if (!object || object === 'localStorage') { | ||
| // if (params.storeType === 'local_storage') { | ||
| // localStorage.clear(); | ||
| // console.debug('All Local Storage settings were deleted...'); | ||
| // } | ||
| // } | ||
|
|
There was a problem hiding this comment.
The old reset implementation is left in the file as a large commented-out block. This adds noise and makes future maintenance harder; git history already preserves the previous version. Please remove the commented-out function block rather than keeping it inline.
| // function reset(object) { | |
| // // 1. Clear any cookie entries | |
| // if (!object || object === 'cookie') { | |
| // var regexpCookieKeys = /(?:^|;)\s*([^=]+)=([^;]*)/ig; | |
| // var currentCookie = document.cookie; | |
| // var foundCrumb = false; | |
| // var cookieCrumb = regexpCookieKeys.exec(currentCookie); | |
| // while (cookieCrumb !== null) { | |
| // // DEV: Note that we don't use the keyPrefix in legacy cookie support | |
| // foundCrumb = true; | |
| // // This expiry date will cause the browser to delete the cookie crumb on next page refresh | |
| // document.cookie = cookieCrumb[1] + '=;expires=Thu, 21 Sep 1979 00:00:01 UTC;'; | |
| // cookieCrumb = regexpCookieKeys.exec(currentCookie); | |
| // } | |
| // if (foundCrumb) console.debug('All cookie keys were expired...'); | |
| // } | |
| // // 2. Clear any localStorage settings | |
| // if (!object || object === 'localStorage') { | |
| // if (params.storeType === 'local_storage') { | |
| // localStorage.clear(); | |
| // console.debug('All Local Storage settings were deleted...'); | |
| // } | |
| // } |
Problem
The reset function was not properly clearing the app cache and IndexedDB because:
Cache deletion failed silently: The code relied on
getCacheNames()which queries the Service Worker. When no Service Worker controller was available, it returnednulland skipped cache deletion entirely.Race condition: The reset function triggered app reload before async cleanup operations (IndexedDB and Cache API deletions) completed, leaving data behind.
Solution
caches.keys()directly instead of querying the Service Worker, ensuring caches are always accessiblePromise.all()to wait for all async cleanup operations to complete before reloading the appkiwixjs-appCacheandkiwixjs-assetsCacheare properly deleted, along with IndexedDB databasesTesting
Verified that after clicking "Reset App":
collDB) are deletedFixes #1136