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.
64 lines
2.1 KiB
JavaScript
64 lines
2.1 KiB
JavaScript
/* Mini Hash-Router für die PWA.
|
|
* Routen:
|
|
* #/login
|
|
* #/orders
|
|
* #/orders/<id>
|
|
* #/orders/<id>/upload
|
|
* #/settings
|
|
*/
|
|
(function () {
|
|
const routes = {};
|
|
let currentRoute = null;
|
|
|
|
function on(pattern, handler) {
|
|
// Pattern: '/orders/:id'
|
|
const re = new RegExp('^' + pattern.replace(/:[a-z]+/gi, '([^/]+)') + '$');
|
|
const params = (pattern.match(/:([a-z]+)/gi) || []).map(s => s.slice(1));
|
|
routes[pattern] = { re, params, handler };
|
|
}
|
|
|
|
async function navigate(hash) {
|
|
if (!hash) hash = window.location.hash || '#/orders';
|
|
const path = hash.replace(/^#/, '') || '/orders';
|
|
for (const key of Object.keys(routes)) {
|
|
const r = routes[key];
|
|
const m = path.match(r.re);
|
|
if (m) {
|
|
const args = {};
|
|
r.params.forEach((p, i) => { args[p] = decodeURIComponent(m[i + 1]); });
|
|
currentRoute = { pattern: key, args };
|
|
// Das Offline-Band gilt immer nur fuer die gerade gezeichnete Ansicht.
|
|
// Ohne dieses Zuruecksetzen bliebe es auf Seiten stehen, die gar keinen
|
|
// Spiegel haben (Einstellungen, Lieferungen).
|
|
if (typeof window.setStale === 'function') window.setStale(false);
|
|
try {
|
|
await r.handler(args);
|
|
} catch (e) {
|
|
console.error(e);
|
|
showToast('Fehler: ' + e.message, 'error');
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
// Default
|
|
navigate('#/orders');
|
|
}
|
|
|
|
function go(hash) {
|
|
if (hash !== window.location.hash) {
|
|
window.location.hash = hash;
|
|
} else {
|
|
navigate(hash);
|
|
}
|
|
}
|
|
|
|
window.addEventListener('hashchange', () => navigate());
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
if (typeof window.appBoot === 'function') {
|
|
try { await window.appBoot(); } catch (e) {}
|
|
}
|
|
navigate();
|
|
});
|
|
|
|
window.router = { on, navigate, go };
|
|
})();
|