Files
bface/src/platform/worker.js

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
};