/* ============================================================ BAN YARO — Boot-Phase Offline-Banner + Service Worker Registration + Update-Flow Extrahiert aus index.html für CSP-Härtung (kein unsafe-inline) ============================================================ */ // ---------------------------------------------------------- // Referral-Code aus ?ref= SOFORT in localStorage sichern — so früh wie möglich, // bevor ein SW-Update-Reload die URL durch /?_t=... ersetzt und den Code verliert. // localStorage (statt sessionStorage) überlebt auch App-Schließen/PWA-Neustart, // sodass die Zuordnung auch klappt, wenn sich die Person erst später registriert. // ---------------------------------------------------------- (function() { try { var rc = new URLSearchParams(location.search).get('ref'); if (rc) { localStorage.setItem('by_ref_code', rc.toUpperCase()); localStorage.setItem('by_ref_code_ts', String(Date.now())); } // Vektor-Basemap-Feature-Flag aus ?vectormap=1/0 SOFORT sichern (bevor Boot // die URL-Query strippt). Wird in ui.js Map.create ausgewertet. var vm = new URLSearchParams(location.search).get('vectormap'); if (vm !== null) localStorage.setItem('by_vector_map', vm === '0' ? '0' : '1'); // MapLibre-GL-Karte (zentrale Karte) aus ?mapgl=1/0 — wird in pages/map.js _useGL() ausgewertet. var mg = new URLSearchParams(location.search).get('mapgl'); if (mg !== null) localStorage.setItem('by_map_gl', mg === '0' ? '0' : '1'); // Offline-Vektorkacheln (byt://) aus ?tilesoffline=1/0 — ausgewertet via BY.offlineTiles(). var to = new URLSearchParams(location.search).get('tilesoffline'); if (to !== null) localStorage.setItem('by_offline_tiles', to === '0' ? '0' : '1'); } catch (e) {} })(); // ---------------------------------------------------------- // Zentrale Feature-Flag-Helper (boot.js lädt vor allen Modulen) // ---------------------------------------------------------- window.BY = window.BY || {}; // Offline-Vektorkacheln (byt://): Staging-Default AN seit 2026-06-06, Production AUS // bis Freigabe; localStorage by_offline_tiles '1'/'0' bzw. ?tilesoffline übersteuert. window.BY.offlineTiles = function () { try { var flag = localStorage.getItem('by_offline_tiles'); if (flag === '1') return true; if (flag === '0') return false; return location.hostname === 'staging.banyaro.app'; } catch (e) { return false; } }; // ---------------------------------------------------------- // Offline-Banner // ---------------------------------------------------------- (function() { var _collapseTimer = null; function _updateBanner() { var banner = document.getElementById('offline-banner'); if (!banner) return; clearTimeout(_collapseTimer); banner.classList.remove('collapsed'); banner.style.display = navigator.onLine ? 'none' : 'flex'; // Nach 5s auf schmale Icon-Leiste einklappen — das volle Banner verdeckt // sonst die Steuerung oben (z.B. Karten-Legende; Gerätetest iOS 2026-06-06). if (!navigator.onLine) { _collapseTimer = setTimeout(function() { banner.classList.add('collapsed'); }, 5000); } } window.addEventListener('offline', function() { _updateBanner(); // Einmaliger Hinweis pro Session: App im Vordergrund lassen if (!sessionStorage.getItem('by_offline_hint_shown')) { sessionStorage.setItem('by_offline_hint_shown', '1'); setTimeout(function() { window.UI?.toast?.info( 'App im Vordergrund lassen — so bleiben Offline-Funktionen wie GPS und Datenspeicherung aktiv.', 8000 ); }, 800); } // Queue-Count abfragen if (navigator.serviceWorker) { navigator.serviceWorker.ready.then(function(reg) { if (reg.active) reg.active.postMessage({ type: 'QUEUE_COUNT' }); }); } }); window.addEventListener('online', function() { _updateBanner(); var badge = document.getElementById('offline-queue-badge'); if (badge) badge.style.display = 'none'; // Queue abarbeiten if (navigator.serviceWorker) { navigator.serviceWorker.ready.then(function(reg) { if (reg.active) reg.active.postMessage({ type: 'PROCESS_QUEUE' }); }); } }); _updateBanner(); })(); // ---------------------------------------------------------- // Aufzeichnungs-Speicher (Sicherheitsnetz gegen Datenverlust bei Reload/Crash) // ---------------------------------------------------------- window.RecStore = { save: function(s) { try { localStorage.setItem('by_active_recording', JSON.stringify(Object.assign({ ts: Date.now() }, s))); } catch (e) {} }, load: function() { try { return JSON.parse(localStorage.getItem('by_active_recording') || 'null'); } catch (e) { return null; } }, clear: function() { try { localStorage.removeItem('by_active_recording'); } catch (e) {} }, }; // ---------------------------------------------------------- // SW-Reload — wird während einer laufenden Routen-Aufzeichnung AUFGESCHOBEN, // damit der nur im RAM gehaltene Track nicht verloren geht. Sobald die // Aufzeichnung beendet ist, holt window._byReloadIfPending() den Reload nach. // ---------------------------------------------------------- window._byReloadIfPending = function() { if (window._byReloadPending) { window._byReloadPending = false; window.location.replace('/?_t=' + Date.now()); } }; function _bySwReload() { if (sessionStorage.getItem('by_skip_sw_reload')) { sessionStorage.removeItem('by_skip_sw_reload'); // einmalig konsumieren return; } if (window._byRecording) { // Aufzeichnung läuft → Reload aufschieben window._byReloadPending = true; return; } window.location.replace('/?_t=' + Date.now()); } // ---------------------------------------------------------- // Service Worker Registration + Update-Flow // ---------------------------------------------------------- if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/sw.js', { updateViaCache: 'none' }) .then(function(reg) { function _watchSW(sw) { if (!sw) return; sw.addEventListener('statechange', function() { if (sw.state === 'activated') _bySwReload(); }); } reg.addEventListener('updatefound', function() { _watchSW(reg.installing); }); if (reg.installing) _watchSW(reg.installing); reg.update(); }) .catch(function(err) { console.warn('SW Registration failed:', err); }); }); // App aus dem Hintergrund: erneut prüfen document.addEventListener('visibilitychange', function() { if (document.visibilityState === 'visible') { navigator.serviceWorker.getRegistration().then(function(reg) { if (reg) reg.update(); }); } }); // Backup: controllerchange falls updatefound nicht feuert // NICHT registrieren wenn diese Seite selbst durch SW-Reload entstand if (!window._BY_SW_RELOAD) { navigator.serviceWorker.addEventListener('controllerchange', function() { _bySwReload(); }); } navigator.serviceWorker.addEventListener('message', function(e) { if (e.data && e.data.type === 'QUEUE_PROCESSED') { var synced = e.data.synced, failed = e.data.failed, total = e.data.total; if (total === 0) return; if (synced > 0 && window.UI && window.UI.toast) { window.UI.toast.success( synced === 1 ? '1 offline gespeicherter Eintrag synchronisiert' : synced + ' offline gespeicherte Einträge synchronisiert' ); if (window.App && window.App.state && window.pages) { var p = window.pages[window.App.state.page]; if (p && p.module && p.module.refresh) p.module.refresh(); } } if (failed > 0 && window.UI && window.UI.toast) { window.UI.toast.warning(failed + ' Eintrag' + (failed > 1 ? 'e' : '') + ' noch nicht synchronisiert — kein Netz'); } return; } if (e.data && e.data.type === 'QUEUE_COUNT') { var badge = document.getElementById('offline-queue-badge'); if (badge) { if (e.data.count > 0) { badge.textContent = e.data.count; badge.style.display = ''; } else { badge.style.display = 'none'; } } return; } if (e.data && e.data.type === 'CHECK_NEARBY_ALERTS') { if (window.App && window.App._checkNearbyAlerts) window.App._checkNearbyAlerts(); } }); }