refactor: consolidate obscure_email and browser reset into shared utilities

- Remove 6 local copies of obscure_email(); all now use ae_util.obscure_email()
  (badges list, badge review, badge print, presenter list, presenter detail, session view)
- badge review page: add missing ae_util import
- e_app_sys_bar: replace inline IDB/storage clear implementations with core_func.clear_idb()
  and core_func.clear_all_storage(); adds known-names fallback the inline version lacked
- fix-sw page: replace 110-line inline nuke with core_func.clear_all_storage(log_callback);
  step-by-step logging and countdown UI preserved via BrowserResetLogFn callback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-06-24 14:10:16 -04:00
parent b177fbf3cd
commit 9b471f6639
8 changed files with 21 additions and 190 deletions

View File

@@ -37,6 +37,7 @@ import {
User
} from '@lucide/svelte';
import { ae_loc, ae_sess } from '$lib/stores/ae_stores';
import { core_func } from '$lib/ae_core/ae_core_functions';
import Element_access_type from '$lib/app_components/e_app_access_type.svelte';
import Element_sign_in_out from '$lib/app_components/e_app_sign_in_out.svelte';
@@ -91,18 +92,11 @@ async function handle_clear_idb_only() {
)
)
return;
const db_list = await indexedDB.databases();
console.log('[clear_idb] IDB databases found:', db_list.map((d) => d.name));
for (const db of db_list) {
if (db.name) indexedDB.deleteDatabase(db.name);
}
await core_func.clear_idb();
window.location.reload();
}
// ── Dev: full reset — SW + Cache Storage + IDB + localStorage/sessionStorage ──
// SW and Cache Storage MUST be cleared here. Clearing IDB/localStorage alone leaves
// the SW serving old JS bundles from its own Cache Storage on the next reload,
// which means the user stays stuck on old code. Order: SW → cache → IDB → storage.
async function handle_clear_storage_and_idb() {
if (
!confirm(
@@ -110,21 +104,7 @@ async function handle_clear_storage_and_idb() {
)
)
return;
if ('serviceWorker' in navigator) {
const registrations = await navigator.serviceWorker.getRegistrations();
for (const reg of registrations) await reg.unregister();
}
const cache_keys = await caches.keys();
for (const key of cache_keys) await caches.delete(key);
const db_list = await indexedDB.databases();
console.log('[clear_all] IDB databases found:', db_list.map((d) => d.name));
for (const db of db_list) {
if (db.name) indexedDB.deleteDatabase(db.name);
}
localStorage.clear();
sessionStorage.clear();
await core_func.clear_all_storage();
window.location.reload();
}

View File

@@ -76,12 +76,6 @@ function build_review_url(): string {
return `/events/${$lq__event_badge_obj?.event_id}/badges/${$lq__event_badge_obj?.event_badge_id}/review`;
}
function obscure_email(email: string | null | undefined): string {
if (!email) return '';
const at = email.indexOf('@');
if (at < 0) return email;
return `${email.slice(0, Math.min(3, at))}***${email.slice(at)}`;
}
// TODO: replace alert with actual email API call when available
function send_review_email() {
@@ -92,7 +86,7 @@ function send_review_email() {
`${badge?.given_name ?? ''} ${badge?.family_name ?? ''}`.trim();
const email = is_trusted
? (badge?.email ?? '(no email on file)')
: obscure_email(badge?.email);
: ae_util.obscure_email(badge?.email);
const event_name = EVENTS_MODULE_TITLE;
alert(
`PLACEHOLDER: An email will be sent to ${name} at ${email}. Use that link to review your ${event_name} badge.`

View File

@@ -24,6 +24,7 @@ import { untrack } from 'svelte';
import { liveQuery } from 'dexie';
import { ae_loc } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { db_events } from '$lib/ae_events/db_events';
import { EVENTS_MODULE_TITLE } from '$lib/stores/ae_events_stores';
import { events_func } from '$lib/ae_events/ae_events_functions';
@@ -158,12 +159,6 @@ async function copy_review_link() {
}
}
function obscure_email(email: string | null | undefined): string {
if (!email) return '';
const at = email.indexOf('@');
if (at < 0) return email;
return `${email.slice(0, Math.min(3, at))}***${email.slice(at)}`;
}
// TODO: replace alert with actual email API call when available
function send_review_email() {
@@ -174,7 +169,7 @@ function send_review_email() {
`${badge?.given_name ?? ''} ${badge?.family_name ?? ''}`.trim();
const email = is_trusted
? (badge?.email ?? '(no email on file)')
: obscure_email(badge?.email);
: ae_util.obscure_email(badge?.email);
const event_name = EVENTS_MODULE_TITLE;
alert(
`PLACEHOLDER: An email will be sent to ${name} at ${email}. Use that link to review your ${event_name} badge.`

View File

@@ -52,17 +52,6 @@ let is_manager = $derived($ae_loc.manager_access === true);
let is_public = $derived($ae_loc.public_access === true); // public passcode or higher — may print first prints
let is_edit_mode = $derived($ae_loc.edit_mode === true);
/**
* Obscures an email address for display to non-trusted users.
* e.g. john.doe@example.com → joh***@example.com
*/
function obscure_email(email: string | null | undefined): string {
if (!email) return '';
const at = email.indexOf('@');
if (at < 0) return email;
const visible = email.slice(0, Math.min(3, at));
return `${visible}***${email.slice(at)}`;
}
function build_review_url(event_badge_obj: any): string {
// TODO: append ?passcode=... when person_passcode is added to the event_badge schema
@@ -96,7 +85,7 @@ function do_send_review_email(event_badge_obj: any) {
`${event_badge_obj?.given_name ?? ''} ${event_badge_obj?.family_name ?? ''}`.trim();
const email = is_trusted
? (event_badge_obj?.email ?? '(no email on file)')
: obscure_email(event_badge_obj?.email);
: ae_util.obscure_email(event_badge_obj?.email);
const event_name = EVENTS_MODULE_TITLE;
alert(
`PLACEHOLDER: An email will be sent to ${name} at ${email}. Use that link to review your ${event_name} badge.`
@@ -243,7 +232,7 @@ let visible_badge_obj_li = $derived(
>
{is_trusted
? event_badge_obj.email
: obscure_email(event_badge_obj.email)}
: ae_util.obscure_email(event_badge_obj.email)}
</span>
{/if}
@@ -315,7 +304,7 @@ let visible_badge_obj_li = $derived(
">
{is_trusted
? event_badge_obj.email
: obscure_email(event_badge_obj.email)}
: ae_util.obscure_email(event_badge_obj.email)}
</span>
{/if}

View File

@@ -185,12 +185,6 @@ let presenter_sign_in_url = $derived((() => {
// *** Functions and Logic
function obscure_email(email: string | null | undefined): string {
if (!email) return '';
const at = email.indexOf('@');
if (at < 0) return email;
return `${email.slice(0, Math.min(3, at))}***${email.slice(at)}`;
}
</script>
<svelte:head>
@@ -335,7 +329,7 @@ function obscure_email(email: string | null | undefined): string {
}
const display_email = $ae_loc.trusted_access
? use_email
: obscure_email(use_email);
: ae_util.obscure_email(use_email);
if (!confirm(`This will send the sign in email to ${display_email}`)) {
return;
}
@@ -366,7 +360,7 @@ function obscure_email(email: string | null | undefined): string {
});
}}
class="btn btn-sm preset-tonal-secondary border-secondary-500 hover:preset-filled-secondary-500 m-0.25 border"
title="Email the access link to {$lq__event_presenter_obj?.full_name ?? 'presenter'} ({$ae_loc.trusted_access ? ($lq__event_presenter_obj?.person_primary_email ?? $lq__event_presenter_obj?.email ?? 'no email on file') : obscure_email($lq__event_presenter_obj?.person_primary_email ?? $lq__event_presenter_obj?.email)})">
title="Email the access link to {$lq__event_presenter_obj?.full_name ?? 'presenter'} ({$ae_loc.trusted_access ? ($lq__event_presenter_obj?.person_primary_email ?? $lq__event_presenter_obj?.email ?? 'no email on file') : ae_util.obscure_email($lq__event_presenter_obj?.person_primary_email ?? $lq__event_presenter_obj?.email)})">
<Mail size="1em" class="" />
<Link size="1em" class="" />
Email Access Link

View File

@@ -68,12 +68,6 @@ let ae_tmp: key_val = $state({});
// *** Functions and Logic
function obscure_email(email: string | null | undefined): string {
if (!email) return '';
const at = email.indexOf('@');
if (at < 0) return email;
return `${email.slice(0, Math.min(3, at))}***${email.slice(at)}`;
}
</script>
<div class="float-right flex flex-row items-center">
@@ -210,7 +204,7 @@ function obscure_email(email: string | null | undefined): string {
}
const display_email = $ae_loc.trusted_access
? use_email
: obscure_email(use_email);
: ae_util.obscure_email(use_email);
if (!confirm(`This will send the access link email to ${event_presenter_obj.full_name ?? 'this presenter'} (${display_email})?`)) {
return;
}
@@ -241,7 +235,7 @@ function obscure_email(email: string | null | undefined): string {
}}
class="btn preset-tonal-secondary border-secondary-500 hover:preset-filled-secondary-500 my-0.5 border transition-all hover:transition-all"
class:btn-sm={display_mode != 'default'}
title="Email the access link to {event_presenter_obj.full_name ?? 'presenter'} ({$ae_loc.trusted_access ? (event_presenter_obj.person_primary_email ?? event_presenter_obj.email ?? 'no email on file') : obscure_email(event_presenter_obj.person_primary_email ?? event_presenter_obj.email)})">
title="Email the access link to {event_presenter_obj.full_name ?? 'presenter'} ({$ae_loc.trusted_access ? (event_presenter_obj.person_primary_email ?? event_presenter_obj.email ?? 'no email on file') : ae_util.obscure_email(event_presenter_obj.person_primary_email ?? event_presenter_obj.email)})">
<Mail size="1em" class="mr-1" />
Email Access Link
</button>

View File

@@ -191,19 +191,13 @@ $effect(() => {
}
});
function obscure_email(email: string | null | undefined): string {
if (!email) return '';
const at = email.indexOf('@');
if (at < 0) return email;
return `${email.slice(0, Math.min(3, at))}***${email.slice(at)}`;
}
async function send_poc_email_link() {
const sess = $lq__event_session_obj;
if (!sess?.poc_person_primary_email) return;
const display_email = $ae_loc.trusted_access
? sess.poc_person_primary_email
: obscure_email(sess.poc_person_primary_email);
: ae_util.obscure_email(sess.poc_person_primary_email);
if (!confirm(`Send sign-in link to ${sess.poc_person_full_name} (${display_email})?`)) return;
poc_email_status = 'sending';
try {
@@ -498,7 +492,7 @@ async function send_poc_email_link() {
type="button"
disabled={poc_email_status === 'sending'}
onclick={send_poc_email_link}
title="Email the sign-in link to {pres_mgmt_loc.current.label__session_poc_name}: {$lq__event_session_obj?.poc_person_full_name} ({$ae_loc.trusted_access ? $lq__event_session_obj?.poc_person_primary_email : obscure_email($lq__event_session_obj?.poc_person_primary_email)})"
title="Email the sign-in link to {pres_mgmt_loc.current.label__session_poc_name}: {$lq__event_session_obj?.poc_person_full_name} ({$ae_loc.trusted_access ? $lq__event_session_obj?.poc_person_primary_email : ae_util.obscure_email($lq__event_session_obj?.poc_person_primary_email)})"
class="btn btn-sm preset-outlined-secondary-300-700 transition-all duration-200"
class:preset-tonal-secondary={poc_email_status === 'idle'}
class:preset-tonal-warning={poc_email_status === 'sending'}

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { RefreshCw } from '@lucide/svelte';
import { core_func } from '$lib/ae_core/ae_core_functions';
let status: string[] = $state([]);
let done = $state(false);
@@ -14,122 +15,12 @@ function log(msg: string) {
console.log(`[FIX-SW] ${msg}`);
}
function log_ok(msg: string) {
log(`✓ ${msg}`);
}
function log_warn(msg: string) {
log(`⚠ ${msg}`);
}
async function nuke_everything() {
log('Starting full storage reset...');
// 1. Unregister all service workers
if ('serviceWorker' in navigator) {
try {
const registrations = await navigator.serviceWorker.getRegistrations();
if (registrations.length === 0) {
log_warn('No active service worker registrations found.');
} else {
log(`Found ${registrations.length} service worker registration(s).`);
for (const reg of registrations) {
const ok = await reg.unregister();
log_ok(`Unregistered SW at scope: ${reg.scope} (success: ${ok})`);
}
}
} catch (err: any) {
has_error = true;
log(`ERROR unregistering service workers: ${err.message}`);
}
} else {
log_warn('Service Workers not supported in this browser.');
}
// 2. Clear all Cache Storage caches
try {
const cache_keys = await caches.keys();
if (cache_keys.length === 0) {
log_warn('No Cache Storage entries found.');
} else {
for (const key of cache_keys) {
await caches.delete(key);
log_ok(`Cleared cache: ${key}`);
}
}
} catch (err: any) {
has_error = true;
log(`ERROR clearing Cache Storage: ${err.message}`);
}
// 3. Clear all IndexedDB databases
try {
if ('databases' in indexedDB) {
const db_list = await indexedDB.databases();
if (db_list.length === 0) {
log_warn('No IndexedDB databases found.');
} else {
for (const db_info of db_list) {
if (!db_info.name) continue;
await new Promise<void>((resolve, reject) => {
const req = indexedDB.deleteDatabase(db_info.name!);
req.onsuccess = () => {
log_ok(`Deleted IDB database: ${db_info.name}`);
resolve();
};
req.onerror = () => {
log(`ERROR deleting IDB database: ${db_info.name}`);
has_error = true;
resolve(); // continue anyway
};
req.onblocked = () => {
log_warn(`IDB delete blocked (open connections): ${db_info.name} — will proceed`);
resolve();
};
});
}
}
} else {
// Fallback: delete known Aether databases by name
log_warn('indexedDB.databases() not available — deleting known Aether databases by name.');
const known_dbs = [
'ae_core_db', 'ae_events_db', 'ae_journals_db',
'ae_archives_db', 'ae_posts_db', 'ae_idaa_db',
'ae_sponsorships_db', 'ae_reports_db',
];
for (const name of known_dbs) {
await new Promise<void>((resolve) => {
const req = indexedDB.deleteDatabase(name);
req.onsuccess = () => { log_ok(`Deleted IDB database: ${name}`); resolve(); };
req.onerror = () => { resolve(); }; // not present = silent
req.onblocked = () => { log_warn(`IDB delete blocked: ${name}`); resolve(); };
});
}
}
} catch (err: any) {
has_error = true;
log(`ERROR clearing IndexedDB: ${err.message}`);
}
// 4. Clear localStorage
try {
const local_count = localStorage.length;
localStorage.clear();
log_ok(`Cleared localStorage (${local_count} item(s)).`);
} catch (err: any) {
has_error = true;
log(`ERROR clearing localStorage: ${err.message}`);
}
// 5. Clear sessionStorage
try {
const session_count = sessionStorage.length;
sessionStorage.clear();
log_ok(`Cleared sessionStorage (${session_count} item(s)).`);
} catch (err: any) {
has_error = true;
log(`ERROR clearing sessionStorage: ${err.message}`);
}
await core_func.clear_all_storage((msg, level) => {
if (level === 'error') has_error = true;
const prefix = level === 'ok' ? '✓ ' : level === 'warn' ? '⚠ ' : '';
log(prefix + msg);
});
log(`─── Reset complete. Reloading in ${RELOAD_DELAY_S} seconds... ───`);
done = true;