149 lines
5.0 KiB
TypeScript
149 lines
5.0 KiB
TypeScript
import { type Component, createEffect, onCleanup } from 'solid-js';
|
|
import { useRegisterSW } from 'virtual:pwa-register/solid';
|
|
import { toast } from 'solid-sonner';
|
|
|
|
// We export a function so other parts of the app can manually trigger an update
|
|
let manualUpdateFn: (() => void) | undefined;
|
|
let updateAndReloadFn: (() => void) | undefined;
|
|
|
|
export const forceServiceWorkerUpdate = () => {
|
|
if (manualUpdateFn) {
|
|
manualUpdateFn();
|
|
} else {
|
|
console.warn("PWA Updater not initialized yet");
|
|
}
|
|
};
|
|
|
|
export const updateAndReloadApp = () => {
|
|
if (updateAndReloadFn) {
|
|
updateAndReloadFn();
|
|
} else {
|
|
console.warn("PWA Updater not initialized yet; reloading directly.");
|
|
window.location.reload();
|
|
}
|
|
};
|
|
|
|
export const PwaUpdater: Component = () => {
|
|
let registration: ServiceWorkerRegistration | undefined;
|
|
|
|
const {
|
|
needRefresh: [needRefresh],
|
|
updateServiceWorker,
|
|
} = useRegisterSW({
|
|
onRegistered(r: ServiceWorkerRegistration | undefined) {
|
|
console.log('SW Registered:', r);
|
|
registration = r;
|
|
|
|
// Periodically check for updates (every 1 hour)
|
|
if (r) {
|
|
setInterval(() => {
|
|
console.log('Periodic SW update check...');
|
|
r.update();
|
|
}, 60 * 60 * 1000);
|
|
}
|
|
},
|
|
onRegisterError(error: Error | any) {
|
|
console.error('SW registration error', error);
|
|
},
|
|
});
|
|
|
|
createEffect(() => {
|
|
// Expose function globally
|
|
manualUpdateFn = () => {
|
|
if (needRefresh()) {
|
|
console.log('User manually forced an update. Reloading now...');
|
|
updateServiceWorker(true);
|
|
} else if (registration) {
|
|
console.log('User manually asked to check for updates...');
|
|
registration.update();
|
|
}
|
|
};
|
|
|
|
updateAndReloadFn = () => {
|
|
if (needRefresh()) {
|
|
console.log('Update ready. Reloading now...');
|
|
updateServiceWorker(true);
|
|
return;
|
|
}
|
|
|
|
if (registration) {
|
|
console.log('Checking for updates before reload...');
|
|
registration.update();
|
|
}
|
|
|
|
const start = Date.now();
|
|
const timeoutMs = 5000;
|
|
const poll = window.setInterval(() => {
|
|
if (needRefresh()) {
|
|
console.log('Update found. Reloading with new SW...');
|
|
window.clearInterval(poll);
|
|
updateServiceWorker(true);
|
|
} else if (Date.now() - start > timeoutMs) {
|
|
console.log('No update detected quickly. Reloading now...');
|
|
window.clearInterval(poll);
|
|
toast.info("No update found");
|
|
}
|
|
}, 250);
|
|
};
|
|
});
|
|
|
|
createEffect(() => {
|
|
// Check for updates when the app regains focus/visibility
|
|
const handleVisibilityFocus = () => {
|
|
if (document.visibilityState === 'visible' && registration) {
|
|
console.log('App became visible, checking for SW updates...');
|
|
registration.update();
|
|
}
|
|
};
|
|
|
|
document.addEventListener('visibilitychange', handleVisibilityFocus);
|
|
window.addEventListener('focus', handleVisibilityFocus);
|
|
|
|
onCleanup(() => {
|
|
document.removeEventListener('visibilitychange', handleVisibilityFocus);
|
|
window.removeEventListener('focus', handleVisibilityFocus);
|
|
});
|
|
});
|
|
|
|
createEffect(() => {
|
|
if (needRefresh()) {
|
|
console.log('New PWA version available. Monitoring for idle/hidden state to update...');
|
|
|
|
let idleTimeout: number | undefined;
|
|
|
|
const triggerUpdate = () => {
|
|
console.log('Triggering PWA update...');
|
|
updateServiceWorker(true);
|
|
};
|
|
|
|
const resetIdle = () => {
|
|
if (idleTimeout) window.clearTimeout(idleTimeout);
|
|
// 10 minutes idle
|
|
idleTimeout = window.setTimeout(triggerUpdate, 10 * 60 * 1000);
|
|
};
|
|
|
|
const handleVisibility = () => {
|
|
if (document.visibilityState === 'hidden') {
|
|
triggerUpdate();
|
|
}
|
|
};
|
|
|
|
// Start idle timer
|
|
resetIdle();
|
|
|
|
// Listen for user activity
|
|
const events = ['mousedown', 'mousemove', 'keydown', 'scroll', 'touchstart'];
|
|
events.forEach(e => window.addEventListener(e, resetIdle, { passive: true }));
|
|
document.addEventListener('visibilitychange', handleVisibility);
|
|
|
|
onCleanup(() => {
|
|
if (idleTimeout) window.clearTimeout(idleTimeout);
|
|
events.forEach(e => window.removeEventListener(e, resetIdle));
|
|
document.removeEventListener('visibilitychange', handleVisibility);
|
|
});
|
|
}
|
|
});
|
|
|
|
return null;
|
|
};
|