import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; import App from './App.tsx'; import { ErrorBoundary } from './components/ErrorBoundary'; import { ToastProvider } from './components/Toast'; import { ThemeProvider } from './contexts/ThemeContext'; import { API_ROOT, isDevelopment } from './config'; // Show update notification banner - Addresses ARCH-004 function showUpdateNotification(newWorker: ServiceWorker) { // Create update notification banner const banner = document.createElement('div'); banner.id = 'sw-update-banner'; banner.innerHTML = `
New version available!

A new version of the app is ready. Update now for the latest features and improvements.

`; document.body.appendChild(banner); // Update button click handler document.getElementById('sw-update-btn')?.addEventListener('click', () => { newWorker.postMessage({ type: 'SKIP_WAITING' }); window.location.reload(); }); // Dismiss button click handler document.getElementById('sw-dismiss-btn')?.addEventListener('click', () => { banner.remove(); }); } const applyPwaSettings = async () => { try { // Use driver-specific PWA settings endpoint const res = await fetch(`${API_ROOT}/api/driver/settings/pwa`, { credentials: 'include' }); if (!res.ok) return; const payload = await res.json(); const settings = payload.data || payload; if (!settings?.enabled) return; const normalizeUrl = (value?: string) => { if (!value) return ''; if (value.startsWith('http')) return value; return value.startsWith('/') ? value : `/${value}`; }; const manifest = { name: settings.name, short_name: settings.shortName, description: settings.description, start_url: settings.startUrl || '/', scope: settings.scope || '/', display: settings.display || 'standalone', theme_color: settings.themeColor, background_color: settings.backgroundColor, icons: [ settings.icon192 ? { src: normalizeUrl(settings.icon192), sizes: '192x192', type: 'image/png' } : null, settings.icon512 ? { src: normalizeUrl(settings.icon512), sizes: '512x512', type: 'image/png' } : null ].filter(Boolean) }; const manifestBlob = new Blob([JSON.stringify(manifest)], { type: 'application/manifest+json' }); const manifestUrl = URL.createObjectURL(manifestBlob); let manifestLink = document.querySelector("link[rel='manifest']") as HTMLLinkElement | null; if (!manifestLink) { manifestLink = document.createElement('link'); manifestLink.rel = 'manifest'; document.head.appendChild(manifestLink); } else if (manifestLink.href.startsWith('blob:')) { // Cleanup old blob URL to prevent memory leak - Addresses BUG-018 equivalent URL.revokeObjectURL(manifestLink.href); } manifestLink.href = manifestUrl; let themeMeta = document.querySelector("meta[name='theme-color']") as HTMLMetaElement | null; if (!themeMeta) { themeMeta = document.createElement('meta'); themeMeta.name = 'theme-color'; document.head.appendChild(themeMeta); } themeMeta.content = settings.themeColor || '#1e293b'; // Service worker registration with update handling - Addresses ARCH-004 if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js') .then((registration) => { // Check for updates periodically setInterval(() => { registration.update(); }, 60 * 60 * 1000); // Check every hour // Check for updates registration.addEventListener('updatefound', () => { const newWorker = registration.installing; if (newWorker) { newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { // New content available, show update notification showUpdateNotification(newWorker); } }); } }); }) .catch((error) => { if (isDevelopment) { console.error('Failed to register service worker', error); } }); } } catch (error) { if (isDevelopment) { console.error('Failed to apply PWA settings', error); } } }; applyPwaSettings(); createRoot(document.getElementById('root')!).render( {/* BrowserRouter enables URL-based navigation - Addresses ARCH-001 */} , );