Gemeldet: die App zeigte beim Oeffnen nur "Failed to fetch", waehrend das
Teilen aus WhatsApp weiter ging. Deploy und API waren in Ordnung — die
Meldung ist die rohe Browserausgabe eines fetch(), das gar nicht bis zum
Server kam, und stand 1:1 im UI, weil die Routen im catch e.message
anzeigten. Das Teilen fiel nicht auf, weil share.html sich die letzten
Auftraege seit eaad796 lokal merkt; die Hauptapp hatte fuer die Listen
keinen Offline-Fallback.
- api.js uebersetzt Netzfehler in einen eigenen Fehler mit offline=true
und deutschem Text; 4xx/5xx bleiben Serverfehler
- isTransient() in offline.js erkannte Netzfehler an "instanceof TypeError".
Ohne Anpassung waere ein Foto aus dem Funkloch als dauerhaft
fehlgeschlagen gewertet und nach sechs Versuchen in Quarantaene gelandet
- Datenspiegel in IndexedDB: Auftrags-, Kunden- und Berichtsliste sowie
geoeffnete Details (letzte 30). Ohne Netz wird der Stand angezeigt,
mit Band "Kein Netz — Stand von HH:MM Uhr", und die Suche filtert darin
- beim Abmelden wird der Spiegel geloescht (geteiltes Geraet)
- sw.js: der Same-Origin-Zweig endete auf caches.match(); fand der Cache
nichts, wurde respondWith(undefined) aufgerufen und die Anfrage
scheiterte erneut mit genau der Meldung, die verhindert werden sollte.
Jetzt immer eine echte Response, nur vollstaendige Antworten im Cache
- Precache nach Deploy: die Seite meldet dem Worker ihre echten Asset-URLs,
sonst ist der Cache direkt nach einem Deploy leer (?v=<mtime>)
Muster aus KB #1037 und #1041. Verifiziert auf der lokalen Testinstanz
inklusive echtem Funkloch (setOffline + reload): App startet aus dem
Cache, zeigt Band und Auftragsliste, keine JS-Fehler.
170 lines
7.5 KiB
PHP
170 lines
7.5 KiB
PHP
<?php
|
|
/* Baustelle PWA — Einstiegsseite.
|
|
* Cache-Versionierung via filemtime() nach Pattern claude-db #201.
|
|
* Bei jedem Deploy ändern sich die mtimes → neue URLs → Browser holt frische Files.
|
|
*/
|
|
$cssVersion = @filemtime(__DIR__.'/app.css') ?: time();
|
|
$jsVersion = max(
|
|
@filemtime(__DIR__.'/app.js') ?: time(),
|
|
@filemtime(__DIR__.'/lib/idb.js') ?: time(),
|
|
@filemtime(__DIR__.'/lib/api.js') ?: time(),
|
|
@filemtime(__DIR__.'/lib/offline.js')?: time(),
|
|
@filemtime(__DIR__.'/lib/router.js') ?: time()
|
|
);
|
|
$manifestVersion = @filemtime(__DIR__.'/manifest.webmanifest') ?: time();
|
|
$swVersion = max($cssVersion, $jsVersion, $manifestVersion);
|
|
|
|
// Einstiegsseite NIE cachen
|
|
header('Cache-Control: no-cache, no-store, must-revalidate');
|
|
header('Pragma: no-cache');
|
|
header('Expires: 0');
|
|
?>
|
|
<!doctype html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
|
|
<meta name="theme-color" content="#1a1a1f">
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<title>Baustelle</title>
|
|
<link rel="manifest" href="manifest.webmanifest?v=<?php echo $manifestVersion; ?>">
|
|
<link rel="icon" type="image/svg+xml" href="icons/icon.svg">
|
|
<link rel="icon" type="image/png" sizes="192x192" href="icons/icon-192.png">
|
|
<link rel="icon" type="image/png" sizes="512x512" href="icons/icon-512.png">
|
|
<link rel="apple-touch-icon" href="icons/icon-192.png">
|
|
<link rel="stylesheet" href="app.css?v=<?php echo $cssVersion; ?>">
|
|
</head>
|
|
<body>
|
|
|
|
<div id="app">
|
|
<header id="topbar">
|
|
<button id="back-btn" class="icon-btn" style="display:none">←</button>
|
|
<h1 id="page-title">Baustelle</h1>
|
|
<button id="install-btn" class="icon-btn" title="Als App installieren" style="display:none">📲</button>
|
|
<button id="help-btn" class="icon-btn" title="Hilfe">❓</button>
|
|
<span id="status-badge">🟢</span>
|
|
</header>
|
|
|
|
<main id="main"></main>
|
|
|
|
<nav id="bottom-nav" style="display:none">
|
|
<button data-route="today">☀️ Heute</button>
|
|
<button data-route="orders" class="active">📋 Aufträge</button>
|
|
<button data-route="customers">👥 Kunden</button>
|
|
<button data-route="reports">📑 Berichte</button>
|
|
<button data-route="settings">⚙️</button>
|
|
</nav>
|
|
</div>
|
|
|
|
<button id="fab-new-order" class="fab" title="Neuer Auftrag" aria-label="Neuer Auftrag" hidden>+</button>
|
|
|
|
<div id="toast-container"></div>
|
|
|
|
<script>
|
|
console.log('[Init] PDF.js wird geladen von: /custom/bericht/js/lib/pdf.min.js');
|
|
</script>
|
|
<script src="/custom/bericht/js/lib/pdf.min.js?v=<?php echo $jsVersion; ?>"></script>
|
|
<script>
|
|
// PDF.js wird als pdfjsLib exportiert, nicht als pdfjs
|
|
const pdfjs = window.pdfjsLib;
|
|
console.log('[PDF.js] Nach Script-Load: typeof pdfjs =', typeof pdfjs);
|
|
// PDF.js Worker setzen NACHDEM pdf.min.js geladen wurde
|
|
if (typeof pdfjs !== 'undefined') {
|
|
pdfjs.GlobalWorkerOptions.workerSrc = '/custom/bericht/js/lib/pdf.worker.min.js?v=<?php echo $jsVersion; ?>';
|
|
console.log('[PDF.js] ✓ Geladen! Worker:', pdfjs.GlobalWorkerOptions.workerSrc);
|
|
} else {
|
|
console.error('[PDF.js] ❌ NICHT GELADEN!');
|
|
}
|
|
</script>
|
|
<script src="lib/idb.js?v=<?php echo $jsVersion; ?>"></script>
|
|
<script src="lib/api.js?v=<?php echo $jsVersion; ?>"></script>
|
|
<script src="lib/offline.js?v=<?php echo $jsVersion; ?>"></script>
|
|
<script src="lib/router.js?v=<?php echo $jsVersion; ?>"></script>
|
|
<script src="app.js?v=<?php echo $jsVersion; ?>"></script>
|
|
<!-- awlauth WebAuthn/Passkey (zentrales Modul, same-origin unter /custom/awlauth) -->
|
|
<script>window.AWLAUTH_BASE = '/custom/awlauth';</script>
|
|
<script src="/custom/awlauth/js/awlauth-webauthn.js?v=<?php echo @filemtime($_SERVER['DOCUMENT_ROOT'].'/custom/awlauth/js/awlauth-webauthn.js') ?: $jsVersion; ?>"></script>
|
|
<script>
|
|
/* PWA Install Prompt abfangen */
|
|
let deferredInstallPrompt = null;
|
|
window.addEventListener('beforeinstallprompt', function (e) {
|
|
e.preventDefault();
|
|
deferredInstallPrompt = e;
|
|
var btn = document.getElementById('install-btn');
|
|
if (btn) btn.style.display = '';
|
|
console.log('PWA: Install-Prompt ist verfügbar');
|
|
});
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
var btn = document.getElementById('install-btn');
|
|
if (btn) btn.addEventListener('click', async function () {
|
|
if (!deferredInstallPrompt) {
|
|
alert('Installation nicht verfügbar. Falls du die App bereits installiert hast, öffne sie über das Home-Screen-Icon.\n\nAuf iPhone: Safari → Teilen → "Zum Home-Bildschirm".');
|
|
return;
|
|
}
|
|
deferredInstallPrompt.prompt();
|
|
var choice = await deferredInstallPrompt.userChoice;
|
|
if (choice.outcome === 'accepted') {
|
|
btn.style.display = 'none';
|
|
}
|
|
deferredInstallPrompt = null;
|
|
});
|
|
});
|
|
window.addEventListener('appinstalled', function () {
|
|
var btn = document.getElementById('install-btn');
|
|
if (btn) btn.style.display = 'none';
|
|
console.log('PWA: Installation abgeschlossen');
|
|
});
|
|
|
|
if ('serviceWorker' in navigator) {
|
|
window.addEventListener('load', function () {
|
|
navigator.serviceWorker.register('sw.js?v=<?php echo $swVersion; ?>').then(function (reg) {
|
|
// Periodisch updates prüfen
|
|
setInterval(function () { reg.update(); }, 60000);
|
|
// Bei Tab-Wechsel zurück: sofort prüfen
|
|
document.addEventListener('visibilitychange', function () {
|
|
if (document.visibilityState === 'visible') reg.update();
|
|
});
|
|
// Neuer SW installed → sofort aktivieren
|
|
reg.addEventListener('updatefound', function () {
|
|
var nw = reg.installing;
|
|
if (!nw) return;
|
|
nw.addEventListener('statechange', function () {
|
|
if (nw.state === 'installed' && navigator.serviceWorker.controller) {
|
|
nw.postMessage({ type: 'SKIP_WAITING' });
|
|
}
|
|
});
|
|
});
|
|
}).catch(function (e) { console.warn('SW reg failed', e); });
|
|
|
|
/* Dem Service Worker melden, was diese Seite tatsaechlich geladen hat, damit er
|
|
es nachcachen kann. Noetig, weil der Cache nach jedem Deploy einen neuen Namen
|
|
traegt und leer startet — und weil die Asset-URLs ein ?v=<mtime> tragen, laesst
|
|
sich im Worker keine feste Dateiliste hinterlegen (KB #1041). Ohne das steht,
|
|
wer die neue Fassung laedt und dann ins Funkloch faehrt, vor einer toten App. */
|
|
navigator.serviceWorker.ready.then(function (reg) {
|
|
var ziel = reg.active || navigator.serviceWorker.controller;
|
|
if (!ziel) return;
|
|
var urls = performance.getEntriesByType('resource')
|
|
.map(function (r) { return r.name; })
|
|
.filter(function (u) {
|
|
return u.indexOf(location.origin) === 0
|
|
&& /\.(css|js|woff2?|png|svg|ico|webmanifest)(\?|$)/i.test(u);
|
|
});
|
|
urls.push(location.origin + location.pathname);
|
|
ziel.postMessage({ type: 'PRECACHE', urls: urls });
|
|
}).catch(function () {});
|
|
|
|
// Controller-Change → einmal neu laden
|
|
var reloaded = false;
|
|
navigator.serviceWorker.addEventListener('controllerchange', function () {
|
|
if (reloaded) return;
|
|
reloaded = true;
|
|
window.location.reload();
|
|
});
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|