This commit is contained in:
seb
2026-10-05 16:09:23 +02:00
parent bac2d9a202
commit 5c48d1be65
6 changed files with 293 additions and 29 deletions

View File

@@ -35,6 +35,14 @@ export {
selectActiveHelpId,
} from './helpAutomat';
export {
default as pwaAutomat,
initPwa,
openPwaDialog,
closePwaDialog,
applyPwaUpdate,
} from './pwaAutomat';
export { default as supplierAutomat } from './supplierAutomat';
export { default as warehouseAutomat } from './warehouseAutomat';

125
src/automats/pwaAutomat.js Normal file
View File

@@ -0,0 +1,125 @@
/**
* @file pwaAutomat.js
* @description Manages PWA update lifecycle, service worker registration,
* and user prompt dialog state.
*/
import { Automat } from 'react-automat';
/**
* @typedef {Object} PwaState
* @property {boolean} needRefresh Whether a new SW update is available and waiting
* @property {boolean} dialogOpen Whether the update dialog is currently visible
* @property {boolean} offlineReady Whether content is cached for offline use
* @property {boolean} updating Whether an update is currently being applied
*/
/** @type {PwaState} */
const initialState = {
needRefresh: false,
dialogOpen: false,
offlineReady: false,
updating: false,
};
let swUpdater = null;
/**
* State store for PWA lifecycle & update notifications.
* Registered on window.__AUTOMATS__ as 'pwa'.
*
* @type {Automat<PwaState>}
*/
const pwaAutomat = new Automat(
initialState,
{
setRegistration(updater) {
swUpdater = updater;
},
setNeedRefresh() {
pwaAutomat.setState({
needRefresh: true,
dialogOpen: true,
});
},
setOfflineReady() {
pwaAutomat.setState({
offlineReady: true,
});
},
openDialog() {
pwaAutomat.setState({ dialogOpen: true });
},
closeDialog() {
pwaAutomat.setState({ dialogOpen: false });
},
async applyUpdate() {
pwaAutomat.setState({ updating: true });
if (typeof swUpdater === 'function') {
try {
await swUpdater(true);
} catch (err) {
console.error('[PWA] Error activating update:', err);
window.location.reload();
}
} else {
window.location.reload();
}
},
// Allows testing the dialog in dev mode or in browser console:
// window.__AUTOMATS__.pwa.actions.simulateUpdateAvailable()
simulateUpdateAvailable() {
pwaAutomat.setState({
needRefresh: true,
dialogOpen: true,
});
},
},
{ name: 'pwa' },
);
/**
* Initializes PWA service worker registration in production.
*/
export function initPwa() {
if (!import.meta.env.PROD || !('serviceWorker' in navigator)) return;
import('virtual:pwa-register')
.then(({ registerSW }) => {
const updateSW = registerSW({
immediate: true,
onNeedRefresh() {
pwaAutomat.actions.setNeedRefresh();
},
onOfflineReady() {
pwaAutomat.actions.setOfflineReady();
},
onRegistered(registration) {
if (!registration) return;
// Check for new updates periodically (every 30 minutes)
setInterval(() => {
registration.update().catch(() => {});
}, 30 * 60 * 1000);
// Check for updates when tab or PWA returns to foreground
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
registration.update().catch(() => {});
}
});
},
});
pwaAutomat.actions.setRegistration(updateSW);
})
.catch((err) => {
console.warn('[PWA] Service worker registration failed or omitted:', err);
});
}
export const openPwaDialog = () => pwaAutomat.actions.openDialog();
export const closePwaDialog = () => pwaAutomat.actions.closeDialog();
export const applyPwaUpdate = () => pwaAutomat.actions.applyUpdate();
export default pwaAutomat;