banyaro/backend/static/js/boot.js
rene 70a1f5856a Offline-Karten Follow-ups: Staging-Default AN, Karten-Download-Button, Glyph-Persistenz
- by_offline_tiles Default AN auf staging.banyaro.app (localStorage/?tilesoffline=1/0 uebersteuert)
- Speed-Dial 'Karte offline speichern': GL -> MapOffline.downloadAround(Kartenmitte, 5km),
  Leaflet -> alter Raster-Prefetch (_cacheTiles war seit FAB-Redesign verwaist)
- Glyphs in IndexedDB (Key-Praefix f/) + byt://f/-Protokoll: ueberlebt App-Updates
- OSM-Raster-Prefetch im Offline-Tiles-Modus uebersprungen (GL nutzt das Raster nicht)
- Button-Sichtbarkeit gated: GL ohne Offline-Flag (= Production) zeigt ihn nicht
2026-06-06 11:03:46 +02:00

179 lines
7.4 KiB
JavaScript

/* ============================================================
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 — wird in map-gl-style.js
// _offlineEnabled() ausgewertet (Staging-Default AN, localStorage übersteuert).
var to = new URLSearchParams(location.search).get('tilesoffline');
if (to !== null) localStorage.setItem('by_offline_tiles', to === '0' ? '0' : '1');
} catch (e) {}
})();
// ----------------------------------------------------------
// Offline-Banner
// ----------------------------------------------------------
(function() {
function _updateBanner() {
var banner = document.getElementById('offline-banner');
if (!banner) return;
banner.style.display = navigator.onLine ? 'none' : 'flex';
}
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();
}
});
}