import Alpine from 'alpinejs'; import { logout, restoreSession, verifyConnection } from '../api/auth.js'; import { listKitchens } from '../api/kitchens.js'; import { APP_NAME, APP_VERSION } from './config.js'; import { createRouter, navigate } from './router.js'; import { createAppStore } from './store.js'; import { appShell } from '../components/app-shell.js'; import { navBar } from '../components/nav-bar.js'; import { registerFeatureData } from '../features/register.js'; async function installServiceWorker() { if (!('serviceWorker' in navigator)) { return; } if (import.meta.env.DEV) { const registrations = await navigator.serviceWorker.getRegistrations(); await Promise.all(registrations.map((registration) => registration.unregister())); return; } await navigator.serviceWorker.register('/service-worker.js'); } export function bootstrapApp() { const store = createAppStore(); Alpine.store('app', store); registerFeatureData(Alpine, store); const appRoot = document.querySelector('#app'); appRoot.innerHTML = appShell( APP_NAME, APP_VERSION, import.meta.env.DEV ? 'development' : 'production', ); Alpine.initTree(appRoot); const navRoot = document.querySelector('#app-nav'); function renderNav() { if (!navRoot) { return; } if (typeof Alpine.destroyTree === 'function') { Alpine.destroyTree(navRoot); } navRoot.innerHTML = navBar(APP_NAME); Alpine.initTree(navRoot); } const router = createRouter({ Alpine, store, outlet: document.querySelector('#route-view'), }); let authFailureHandled = false; function applyKitchens(kitchens) { store.setKitchens(kitchens); if (!store.activeKitchen && kitchens.length) { store.setActiveKitchen(kitchens[0]); } renderNav(); return kitchens; } window.__loncApp = { navigate, async refreshKitchens() { return applyKitchens(await listKitchens(store)); }, async restoreSession() { try { const result = await restoreSession(store); if (result?.kitchens) { applyKitchens(result.kitchens); } else if (store.isConnected) { await window.__loncApp.refreshKitchens(); } if (store.isConnected) { authFailureHandled = false; } renderNav(); } catch (error) { renderNav(); if (window.location.hash !== '#/login') { navigate('/login'); } } }, async verifyConnection() { const result = await verifyConnection(store); if (result?.kitchens) { applyKitchens(result.kitchens); } else if (store.isConnected) { await window.__loncApp.refreshKitchens(); } if (store.isConnected) { authFailureHandled = false; } renderNav(); return result; }, handleAuthFailure(error) { if (!store.session?.applicationKey || !store.session?.hasValidated || authFailureHandled) { return; } authFailureHandled = true; store.markSessionInvalid(); renderNav(); const status = error?.status || error?.cause?.status; const message = status === 401 || status === 403 ? 'This application key is no longer accepted by Tryton. Please verify it again or disconnect and create a new key.' : 'Authenticated requests are no longer succeeding. The application key may have been cancelled, or access is being denied by the server. Please reconnect or create a new key.'; store.addAlert({ type: 'warning', timeout: 0, message, }); navigate('/login'); window.setTimeout(() => router.render(), 0); }, async logout() { await logout(store); authFailureHandled = false; renderNav(); navigate('/login'); }, router, }; window.addEventListener('online', () => { store.addAlert({ type: 'success', message: 'Connection restored.' }); }); window.addEventListener('offline', () => { store.addAlert({ type: 'warning', message: 'You are offline. Cached screens stay available, but API actions may fail.', }); }); window.__loncApp .restoreSession() .finally(() => router.start()) .catch(() => router.start()); renderNav(); installServiceWorker().catch(() => { store.addAlert({ type: 'warning', message: 'PWA installation support could not be initialized.', }); }); }