refactor(idaa): migrate clear-caches page to core_func.clear_all_storage
Fixes a bug where indexedDB.deleteDatabase() was not awaited, meaning the page could report success while databases were still being deleted. Also adds the known-names IDB fallback for older Safari (pre-2021), which previously caused the page to silently skip all IDB clearing on those browsers. Removes ~70 lines of duplicate inline logic in favour of core_func.clear_all_storage() with an on_step callback that maps BrowserResetLogFn messages to the existing step-by-step progress UI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { TriangleAlert, CircleCheck, Loader } from '@lucide/svelte';
|
import { TriangleAlert, CircleCheck, Loader } from '@lucide/svelte';
|
||||||
|
import { core_func } from '$lib/ae_core/ae_core_functions';
|
||||||
|
import type { BrowserResetLogFn } from '$lib/ae_core/ae_core_functions';
|
||||||
|
|
||||||
type StepStatus = 'pending' | 'running' | 'done' | 'error';
|
type StepStatus = 'pending' | 'running' | 'done' | 'error';
|
||||||
|
|
||||||
@@ -12,101 +14,105 @@ interface ClearStep {
|
|||||||
|
|
||||||
let steps: ClearStep[] = $state([
|
let steps: ClearStep[] = $state([
|
||||||
{ label: 'Service workers', status: 'pending', detail: '' },
|
{ label: 'Service workers', status: 'pending', detail: '' },
|
||||||
{ label: 'Service worker caches', status: 'pending', detail: '' },
|
{ label: 'App file cache', status: 'pending', detail: '' },
|
||||||
{ label: 'IndexedDB databases', status: 'pending', detail: '' },
|
{ label: 'Saved app data', status: 'pending', detail: '' },
|
||||||
{ label: 'Local storage', status: 'pending', detail: '' },
|
{ label: 'Local settings', status: 'pending', detail: '' },
|
||||||
{ label: 'Session storage', status: 'pending', detail: '' }
|
{ label: 'Session data', status: 'pending', detail: '' }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let overall_done = $state(false);
|
let overall_done = $state(false);
|
||||||
let had_error = $state(false);
|
let had_error = $state(false);
|
||||||
|
|
||||||
|
// Tracks item counts per step — used to build "N databases cleared" summary labels.
|
||||||
|
const ok_counts: number[] = [0, 0, 0, 0, 0];
|
||||||
|
// Tracks which step last received a message so the first message for a new step
|
||||||
|
// transitions it to 'running' before reporting the result.
|
||||||
|
let current_step_idx = -1;
|
||||||
|
|
||||||
|
// Maps a core__browser_reset log message to one of the 5 step indices.
|
||||||
|
function step_index_for(msg: string): number {
|
||||||
|
const m = msg.toLowerCase();
|
||||||
|
if (m.includes('service worker') || (m.includes(' sw') && !m.includes('cache'))) return 0;
|
||||||
|
if (m.includes('cache storage') || m.includes('cache:')) return 1;
|
||||||
|
if (m.includes('idb') || m.includes('indexeddb') || m.includes('database')) return 2;
|
||||||
|
if (m.includes('localstorage')) return 3;
|
||||||
|
if (m.includes('sessionstorage')) return 4;
|
||||||
|
return -1; // general info (e.g. "Starting full storage reset...") — not tied to a step
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_ok_detail(idx: number, msg: string): string {
|
||||||
|
const n = ok_counts[idx];
|
||||||
|
if (idx === 0) return `${n} unregistered`;
|
||||||
|
if (idx === 1) return `${n} cache${n !== 1 ? 's' : ''} cleared`;
|
||||||
|
if (idx === 2) return `${n} database${n !== 1 ? 's' : ''} cleared`;
|
||||||
|
// localStorage / sessionStorage report "Cleared X (N item(s))." — extract the count.
|
||||||
|
const count_match = msg.match(/\((\d+)\s+item/);
|
||||||
|
return count_match ? `${count_match[1]} items cleared` : 'Cleared';
|
||||||
|
}
|
||||||
|
|
||||||
|
const on_step: BrowserResetLogFn = (msg, level = 'info') => {
|
||||||
|
const idx = step_index_for(msg);
|
||||||
|
if (idx < 0) return;
|
||||||
|
|
||||||
|
// First message for this step: advance to it and mark it running.
|
||||||
|
if (idx > current_step_idx) {
|
||||||
|
current_step_idx = idx;
|
||||||
|
steps[idx].status = 'running';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level === 'error') {
|
||||||
|
steps[idx].status = 'error';
|
||||||
|
steps[idx].detail = msg.replace(/^ERROR\s+/i, '');
|
||||||
|
had_error = true;
|
||||||
|
} else if (level === 'ok') {
|
||||||
|
ok_counts[idx]++;
|
||||||
|
steps[idx].status = 'done';
|
||||||
|
steps[idx].detail = make_ok_detail(idx, msg);
|
||||||
|
} else if (level === 'warn') {
|
||||||
|
// "Nothing found" and "not supported" warnings close the step.
|
||||||
|
// Fallback (indexedDB.databases() unavailable) and "blocked" warnings do NOT
|
||||||
|
// close the step — further 'ok' or 'error' messages will follow.
|
||||||
|
const lower = msg.toLowerCase();
|
||||||
|
const is_nothing_found =
|
||||||
|
lower.includes('no active') ||
|
||||||
|
lower.includes('not supported') ||
|
||||||
|
lower.includes('no cache storage') ||
|
||||||
|
lower.includes('no indexeddb') ||
|
||||||
|
lower.includes('no idb');
|
||||||
|
if (is_nothing_found && steps[idx].status === 'running') {
|
||||||
|
steps[idx].status = 'done';
|
||||||
|
steps[idx].detail = lower.includes('not supported') ? 'Not supported' : 'Nothing to clear';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 'info' messages only trigger the 'running' transition above; no further action.
|
||||||
|
};
|
||||||
|
|
||||||
async function clear_all_caches() {
|
async function clear_all_caches() {
|
||||||
// Service workers
|
// core_func.clear_all_storage handles:
|
||||||
steps[0].status = 'running';
|
// SW unregister → Cache Storage → IDB (with known-names fallback) → localStorage → sessionStorage
|
||||||
try {
|
// All IDB deletes are properly awaited (the original inline implementation did not await them).
|
||||||
if ('serviceWorker' in navigator) {
|
await core_func.clear_all_storage(on_step);
|
||||||
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
||||||
for (const reg of registrations) await reg.unregister();
|
// Any steps that received no matching messages (edge case) are marked done.
|
||||||
steps[0].detail = `${registrations.length} unregistered`;
|
for (const step of steps) {
|
||||||
} else {
|
if (step.status === 'pending' || step.status === 'running') {
|
||||||
steps[0].detail = 'Not supported';
|
step.status = 'done';
|
||||||
|
if (!step.detail) step.detail = 'Done';
|
||||||
}
|
}
|
||||||
steps[0].status = 'done';
|
|
||||||
} catch (e) {
|
|
||||||
steps[0].status = 'error';
|
|
||||||
steps[0].detail = String(e);
|
|
||||||
had_error = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache Storage (SW asset caches)
|
|
||||||
steps[1].status = 'running';
|
|
||||||
try {
|
|
||||||
if ('caches' in window) {
|
|
||||||
const cache_keys = await caches.keys();
|
|
||||||
for (const key of cache_keys) await caches.delete(key);
|
|
||||||
steps[1].detail = `${cache_keys.length} cache${cache_keys.length !== 1 ? 's' : ''} cleared`;
|
|
||||||
} else {
|
|
||||||
steps[1].detail = 'Not supported';
|
|
||||||
}
|
|
||||||
steps[1].status = 'done';
|
|
||||||
} catch (e) {
|
|
||||||
steps[1].status = 'error';
|
|
||||||
steps[1].detail = String(e);
|
|
||||||
had_error = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDB — enumerate and delete every database on this origin
|
|
||||||
steps[2].status = 'running';
|
|
||||||
try {
|
|
||||||
const db_list = await indexedDB.databases();
|
|
||||||
for (const db of db_list) {
|
|
||||||
if (db.name) indexedDB.deleteDatabase(db.name);
|
|
||||||
}
|
|
||||||
steps[2].status = 'done';
|
|
||||||
steps[2].detail = `${db_list.length} database${db_list.length !== 1 ? 's' : ''} cleared`;
|
|
||||||
} catch (e) {
|
|
||||||
steps[2].status = 'error';
|
|
||||||
steps[2].detail = String(e);
|
|
||||||
had_error = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// localStorage
|
|
||||||
steps[3].status = 'running';
|
|
||||||
try {
|
|
||||||
localStorage.clear();
|
|
||||||
steps[3].status = 'done';
|
|
||||||
steps[3].detail = 'Cleared';
|
|
||||||
} catch (e) {
|
|
||||||
steps[3].status = 'error';
|
|
||||||
steps[3].detail = String(e);
|
|
||||||
had_error = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sessionStorage
|
|
||||||
steps[4].status = 'running';
|
|
||||||
try {
|
|
||||||
sessionStorage.clear();
|
|
||||||
steps[4].status = 'done';
|
|
||||||
steps[4].detail = 'Cleared';
|
|
||||||
} catch (e) {
|
|
||||||
steps[4].status = 'error';
|
|
||||||
steps[4].detail = String(e);
|
|
||||||
had_error = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
overall_done = true;
|
overall_done = true;
|
||||||
|
|
||||||
// Notify parent window (Novi page) that the clear is complete.
|
// Notify the parent Novi page that the clear is complete.
|
||||||
// The Novi page can optionally listen for this to show a confirmation to the member.
|
// Novi can optionally listen to show a confirmation or trigger a page reload.
|
||||||
try {
|
try {
|
||||||
window.parent.postMessage({ ae_cache_cleared: true, had_error }, '*');
|
window.parent.postMessage({ ae_cache_cleared: true, had_error }, '*');
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// not in an iframe — ignore
|
// Not in an iframe — ignore.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run once on mount. Reads no reactive state so $effect never re-fires.
|
// Auto-run once on mount. No reactive state is read, so $effect never re-fires.
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!browser) return;
|
if (!browser) return;
|
||||||
clear_all_caches();
|
clear_all_caches();
|
||||||
@@ -127,13 +133,13 @@ $effect(() => {
|
|||||||
Cleared with some errors
|
Cleared with some errors
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
Most caches were cleared. Close this tab and reload the IDAA pages to
|
Most data was cleared. Close this tab and reload the IDAA pages to
|
||||||
get the latest version.
|
get the latest version.
|
||||||
</p>
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<CircleCheck size={40} class="text-success-500" />
|
<CircleCheck size={40} class="text-success-500" />
|
||||||
<div class="text-xl font-semibold">
|
<div class="text-xl font-semibold">
|
||||||
All caches cleared
|
All data cleared
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
Close this tab and reload the IDAA pages to get the latest version.
|
Close this tab and reload the IDAA pages to get the latest version.
|
||||||
@@ -168,13 +174,5 @@ $effect(() => {
|
|||||||
Meeting List, Archives, or Bulletin Board,
|
Meeting List, Archives, or Bulletin Board,
|
||||||
please try once more. We apologize for the inconvenience, and thank you for your patience while we work to improve the IDAA experience!
|
please try once more. We apologize for the inconvenience, and thank you for your patience while we work to improve the IDAA experience!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- <p class="text-sm text-gray-700 dark:text-gray-300">
|
|
||||||
If you were having trouble with the IDAA
|
|
||||||
<a href="https://www.idaa.org/idaa-meetings" style="color:#2563eb;text-decoration:underline;" class="text-blue-600 underline">Meeting List</a>,
|
|
||||||
<a href="https://www.idaa.org/idaa-archives" style="color:#2563eb;text-decoration:underline;" class="text-blue-600 underline">Archives</a>, or
|
|
||||||
<a href="https://www.idaa.org/idaa-bulletin-board" style="color:#2563eb;text-decoration:underline;" class="text-blue-600 underline">Bulletin Board</a>,
|
|
||||||
please try once more. We apologize for the inconvenience, and thank you for your patience while we work to improve the IDAA experience!
|
|
||||||
</p> -->
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user