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.
111 lines
3.8 KiB
JavaScript
111 lines
3.8 KiB
JavaScript
/* Mini IndexedDB Wrapper für die PWA.
|
|
* Speichert: Auth-Token, Offline-Queue für Uploads.
|
|
*/
|
|
(function () {
|
|
const DB_NAME = 'baustelle-pwa-v1';
|
|
const DB_VERSION = 1;
|
|
let dbPromise = null;
|
|
|
|
function open() {
|
|
if (dbPromise) return dbPromise;
|
|
dbPromise = new Promise((resolve, reject) => {
|
|
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
req.onupgradeneeded = () => {
|
|
const db = req.result;
|
|
if (!db.objectStoreNames.contains('kv')) {
|
|
db.createObjectStore('kv');
|
|
}
|
|
if (!db.objectStoreNames.contains('queue')) {
|
|
db.createObjectStore('queue', { keyPath: 'id', autoIncrement: true });
|
|
}
|
|
};
|
|
req.onsuccess = () => resolve(req.result);
|
|
req.onerror = () => reject(req.error);
|
|
});
|
|
return dbPromise;
|
|
}
|
|
|
|
async function get(key) {
|
|
const db = await open();
|
|
return new Promise((res, rej) => {
|
|
const tx = db.transaction('kv', 'readonly');
|
|
const r = tx.objectStore('kv').get(key);
|
|
r.onsuccess = () => res(r.result);
|
|
r.onerror = () => rej(r.error);
|
|
});
|
|
}
|
|
|
|
async function set(key, val) {
|
|
const db = await open();
|
|
return new Promise((res, rej) => {
|
|
const tx = db.transaction('kv', 'readwrite');
|
|
tx.objectStore('kv').put(val, key);
|
|
tx.oncomplete = () => res();
|
|
tx.onerror = () => rej(tx.error);
|
|
});
|
|
}
|
|
|
|
async function del(key) {
|
|
const db = await open();
|
|
return new Promise((res, rej) => {
|
|
const tx = db.transaction('kv', 'readwrite');
|
|
tx.objectStore('kv').delete(key);
|
|
tx.oncomplete = () => res();
|
|
tx.onerror = () => rej(tx.error);
|
|
});
|
|
}
|
|
|
|
// Alle Schluessel im kv-Store — gebraucht, um den Offline-Spiegel aufzuraeumen
|
|
async function keys() {
|
|
const db = await open();
|
|
return new Promise((res, rej) => {
|
|
const tx = db.transaction('kv', 'readonly');
|
|
const r = tx.objectStore('kv').getAllKeys();
|
|
r.onsuccess = () => res(r.result || []);
|
|
r.onerror = () => rej(r.error);
|
|
});
|
|
}
|
|
|
|
async function queuePush(item) {
|
|
const db = await open();
|
|
return new Promise((res, rej) => {
|
|
const tx = db.transaction('queue', 'readwrite');
|
|
const r = tx.objectStore('queue').add(item);
|
|
r.onsuccess = () => res(r.result);
|
|
r.onerror = () => rej(r.error);
|
|
});
|
|
}
|
|
|
|
async function queueAll() {
|
|
const db = await open();
|
|
return new Promise((res, rej) => {
|
|
const tx = db.transaction('queue', 'readonly');
|
|
const r = tx.objectStore('queue').getAll();
|
|
r.onsuccess = () => res(r.result || []);
|
|
r.onerror = () => rej(r.error);
|
|
});
|
|
}
|
|
|
|
async function queueDelete(id) {
|
|
const db = await open();
|
|
return new Promise((res, rej) => {
|
|
const tx = db.transaction('queue', 'readwrite');
|
|
tx.objectStore('queue').delete(id);
|
|
tx.oncomplete = () => res();
|
|
tx.onerror = () => rej(tx.error);
|
|
});
|
|
}
|
|
|
|
// Item aktualisieren (z.B. Versuchszähler / Fehler-Markierung) — put upsert-t per keyPath id
|
|
async function queueUpdate(item) {
|
|
const db = await open();
|
|
return new Promise((res, rej) => {
|
|
const tx = db.transaction('queue', 'readwrite');
|
|
tx.objectStore('queue').put(item);
|
|
tx.oncomplete = () => res();
|
|
tx.onerror = () => rej(tx.error);
|
|
});
|
|
}
|
|
|
|
window.idb = { get, set, del, keys, queuePush, queueAll, queueDelete, queueUpdate };
|
|
})();
|