All checks were successful
Deploy baustelle-pwa / deploy (push) Successful in 14s
- Kamera bleibt nach jedem Schnappschuss offen (getUserMedia Live-Video statt native capture-Input, der nach jedem Foto schliesst) - Filmstreifen mit Status-Badges pro Foto (💾 gesichert → ⏳ lädt hoch → ✓ fertig) - Persist-First: Foto zuerst in IndexedDB schreiben, Upload erst danach - Foto verlässt Queue NUR nach HTTP-2xx mit gültigem relpath - 2xx-HTML (abgelaufene Session, Proxy-Loginseite) wird als Fehler erkannt - HTTP-Status auf Error-Objekt: 5xx/429/408 sind transient (nicht quarantänisiert) - 45-Sekunden-Upload-Timeout via AbortController - Quarantäne nach 6 Fehlversuchen, Recovery via Status-Badge-Tipp - Beforeunload-Warnung wenn Fotos noch nicht gesichert - idb.queueUpdate() für state-preserving ItemUpdates ohne Datenverlust Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
100 lines
3.3 KiB
JavaScript
100 lines
3.3 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);
|
|
});
|
|
}
|
|
|
|
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, queuePush, queueAll, queueDelete, queueUpdate };
|
|
})();
|