240 lines
6.6 KiB
JavaScript
240 lines
6.6 KiB
JavaScript
/**
|
|
* Browser worker/runtime helpers.
|
|
* Keep the top-level module compact, then expose focused namespaces that we
|
|
* can later split into separate files without changing the import surface.
|
|
*/
|
|
|
|
import { isElectronHost, isTauriHost } from './host.js';
|
|
import { getConfig, isDevelopment, CONFIG_KEYS } from './env.js';
|
|
|
|
const SW_PATH = '/sw.js';
|
|
const SW_SCOPE = '/';
|
|
const DEV_SW_RESET_KEY = '__bface_dev_sw_reset__';
|
|
|
|
async function clearAllCaches() {
|
|
if ('caches' in window) {
|
|
try {
|
|
const cacheNames = await caches.keys();
|
|
await Promise.all(cacheNames.map((name) => {
|
|
console.log('[SW] Clearing cache:', name);
|
|
return caches.delete(name);
|
|
}));
|
|
console.log('[SW] All caches cleared');
|
|
return cacheNames.length;
|
|
} catch (error) {
|
|
console.error('[SW] Failed to clear caches:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
async function unregisterAllServiceWorkers() {
|
|
if ('serviceWorker' in navigator) {
|
|
try {
|
|
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
await Promise.all(registrations.map((reg) => {
|
|
console.log('[SW] Unregistering service worker:', reg.scope);
|
|
return reg.unregister();
|
|
}));
|
|
console.log(`[SW] Unregistered ${registrations.length} service worker(s)`);
|
|
return registrations.length;
|
|
} catch (error) {
|
|
console.error('[SW] Failed to unregister service workers:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
async function clearAllStorage() {
|
|
try {
|
|
localStorage.clear();
|
|
console.log('[Storage] localStorage cleared');
|
|
|
|
sessionStorage.clear();
|
|
console.log('[Storage] sessionStorage cleared');
|
|
|
|
if ('indexedDB' in window) {
|
|
const databases = await indexedDB.databases();
|
|
await Promise.all(databases.map((db) => {
|
|
if (db.name) {
|
|
return new Promise((resolve) => {
|
|
const deleteReq = indexedDB.deleteDatabase(db.name);
|
|
deleteReq.onsuccess = () => {
|
|
console.log(`[Storage] IndexedDB database "${db.name}" deleted`);
|
|
resolve();
|
|
};
|
|
deleteReq.onerror = () => {
|
|
console.warn(`[Storage] Failed to delete IndexedDB database "${db.name}"`);
|
|
resolve();
|
|
};
|
|
});
|
|
}
|
|
return Promise.resolve();
|
|
}));
|
|
}
|
|
|
|
console.log('[Storage] All storage cleared');
|
|
return true;
|
|
} catch (error) {
|
|
console.error('[Storage] Failed to clear storage:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function clearPWACache() {
|
|
console.log('Clearing all PWA caches and storage...');
|
|
|
|
try {
|
|
const swCount = await unregisterAllServiceWorkers();
|
|
const cacheCount = await clearAllCaches();
|
|
await clearAllStorage();
|
|
|
|
console.log(`PWA cache cleared: ${swCount} service worker(s), ${cacheCount} cache(s), and all storage`);
|
|
console.log('Reload the page to re-register service workers');
|
|
|
|
return {
|
|
serviceWorkers: swCount,
|
|
caches: cacheCount,
|
|
storage: true
|
|
};
|
|
} catch (error) {
|
|
console.error('Failed to clear PWA cache:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function registerServiceWorker() {
|
|
if (isElectronHost() || isTauriHost()) {
|
|
await unregisterAllServiceWorkers();
|
|
console.log('[SW] Skipping service worker registration in desktop host');
|
|
return null;
|
|
}
|
|
|
|
const devHost = await getConfig(CONFIG_KEYS.DEV_HOST, isDevelopment());
|
|
if (devHost) {
|
|
console.log('[SW] Skipping service worker registration in development');
|
|
return null;
|
|
}
|
|
|
|
if ('serviceWorker' in navigator) {
|
|
try {
|
|
const registration = await navigator.serviceWorker.register(SW_PATH, {
|
|
scope: SW_SCOPE,
|
|
updateViaCache: 'none'
|
|
});
|
|
|
|
console.log('Service Worker registered:', registration);
|
|
await registration.update();
|
|
|
|
registration.addEventListener('updatefound', () => {
|
|
const newWorker = registration.installing;
|
|
if (newWorker) {
|
|
newWorker.addEventListener('statechange', () => {
|
|
if (newWorker.state === 'installed') {
|
|
if (navigator.serviceWorker.controller) {
|
|
console.log('[SW] New service worker available, reloading...');
|
|
window.location.reload();
|
|
} else {
|
|
console.log('[SW] Service worker installed for the first time');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
return registration;
|
|
} catch (error) {
|
|
console.error('Service Worker registration failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
console.warn('Service Workers are not supported');
|
|
return null;
|
|
}
|
|
|
|
async function resetServiceWorkers() {
|
|
if (isElectronHost() || isTauriHost()) {
|
|
await unregisterAllServiceWorkers();
|
|
return false;
|
|
}
|
|
|
|
const devHost = await getConfig(CONFIG_KEYS.DEV_HOST, isDevelopment());
|
|
if (!devHost || typeof window === 'undefined' || !('serviceWorker' in navigator)) {
|
|
return false;
|
|
}
|
|
|
|
const hasController = Boolean(navigator.serviceWorker.controller);
|
|
const alreadyReset = sessionStorage.getItem(DEV_SW_RESET_KEY) === '1';
|
|
|
|
if (!hasController || alreadyReset) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
await unregisterAllServiceWorkers();
|
|
await clearAllCaches();
|
|
} catch (error) {
|
|
console.warn('[SW] Failed to reset dev service workers:', error);
|
|
}
|
|
|
|
sessionStorage.setItem(DEV_SW_RESET_KEY, '1');
|
|
window.location.reload();
|
|
return true;
|
|
}
|
|
|
|
async function getServiceWorkerStatus() {
|
|
if (isElectronHost() || isTauriHost()) {
|
|
return 'Desktop Disabled';
|
|
}
|
|
|
|
if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) {
|
|
return 'Not Supported';
|
|
}
|
|
|
|
const devHost = await getConfig(CONFIG_KEYS.DEV_HOST, isDevelopment());
|
|
try {
|
|
if (devHost) {
|
|
const registration = await navigator.serviceWorker.getRegistration();
|
|
if (registration) {
|
|
registration.update();
|
|
return 'Active';
|
|
}
|
|
return 'Development Disabled';
|
|
}
|
|
|
|
await navigator.serviceWorker.ready;
|
|
return 'Active';
|
|
} catch (error) {
|
|
console.warn('[SW] Failed to resolve service worker status:', error);
|
|
return 'Not Supported';
|
|
}
|
|
}
|
|
|
|
async function unregisterServiceWorker() {
|
|
if (isElectronHost() || isTauriHost()) {
|
|
return;
|
|
}
|
|
|
|
if ('serviceWorker' in navigator) {
|
|
const registration = await navigator.serviceWorker.getRegistration();
|
|
if (registration) {
|
|
await registration.unregister();
|
|
console.log('Service Worker unregistered');
|
|
}
|
|
}
|
|
}
|
|
|
|
export const sw = {
|
|
clearAllCaches,
|
|
unregisterAllServiceWorkers,
|
|
clearAllStorage,
|
|
clearPWACache,
|
|
registerServiceWorker,
|
|
resetServiceWorkers,
|
|
getServiceWorkerStatus,
|
|
unregisterServiceWorker
|
|
};
|