From 65bc09d9f19ec1f5fb89859aaf6d6c5d12320f8f Mon Sep 17 00:00:00 2001 From: Eddy Date: Fri, 28 Aug 2026 18:41:56 +0200 Subject: [PATCH] Upload-Serie: Screen-Aus bricht nicht mehr ab, Duplikate erkannt [deploy] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Schaltet sich das Display waehrend einer Foto-Serie ab, friert der Browser die laufenden fetch-Aufrufe ein — die halbe Serie blieb liegen, der zweite Anlauf erzeugte Duplikate (28.08.2026, Auftrag PROV105: 11 doppelte Fotos). - Wake Lock waehrend syncQueue(), inkl. Neuanforderung nach App-Wechsel (visibilitychange). Scheitert die Anforderung, laeuft der Sync unveraendert weiter — die Queue faengt Abbrueche ohnehin ab - duplicate:true aus der Server-Antwort wandert durch das photo-uploaded-Event; die Miniatur zeigt dann „War schon hochgeladen" statt eines zweiten Fotos - api.js: Kommentar auf den neuen Server-Vertrag gebracht (Bericht 1.6.0 legt identische Dateien nicht erneut ab, at-least-once bleibt Rueckfallebene) Co-Authored-By: Claude Opus 5 (1M context) --- app.js | 2 ++ lib/api.js | 8 +++++--- lib/offline.js | 31 +++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index 5edd05e..808949e 100644 --- a/app.js +++ b/app.js @@ -3117,6 +3117,8 @@ async function openCameraModal(orderId) { if (d.relpath) thumb.dataset.relpath = d.relpath; thumb.classList.remove('pending', 'saving'); thumb.classList.add('done'); + // Server hatte das Bild schon (Wiederholung nach Abbruch): kein Fehler, nur Hinweis + if (d.duplicate) thumb.title = 'War schon hochgeladen'; } window.addEventListener('photo-uploaded', onUploaded); diff --git a/lib/api.js b/lib/api.js index bf85a5a..e1e0047 100644 --- a/lib/api.js +++ b/lib/api.js @@ -113,9 +113,11 @@ // Semantik: at-least-once. Geht die Server-Antwort NACH dem Speichern verloren // (z.B. Netz-Abbruch, Proxy-2xx-HTML), gilt der Upload als unbestätigt und wird - // wiederholt → das Foto kann dadurch doppelt auf dem Server landen (der Dateiname - // enthält uniqid(), es wird also nicht überschrieben). Bewusst gewählt: lieber ein - // löschbares Duplikat als ein verlorenes Foto. + // wiederholt. Der Server vergleicht seit Bericht 1.6.0 den Inhalt (md5) gegen die + // Dateien im Auftragsordner und legt eine identische Datei kein zweites Mal ab — + // er antwortet dann mit duplicate:true und dem relpath der vorhandenen Datei. + // Die Wiederholung ist damit idempotent statt duplikaterzeugend; die Regel „lieber + // ein Duplikat als ein verlorenes Foto" gilt weiterhin als Rückfallebene. async function uploadOrderPhoto(orderId, fileBlob, filename) { const fd = new FormData(); fd.append('file', fileBlob, filename || 'photo.jpg'); diff --git a/lib/offline.js b/lib/offline.js index c721f27..e9512cc 100644 --- a/lib/offline.js +++ b/lib/offline.js @@ -51,12 +51,41 @@ return false; } + // Screen wachhalten, solange Uploads laufen. Schaltet sich das Display waehrend einer + // Foto-Serie ab, friert der Browser die laufenden fetch-Aufrufe ein und der Rest der + // Serie bleibt liegen (real am 28.08.2026: halbe Serie kam nicht an, der zweite Anlauf + // erzeugte dann Duplikate). Scheitert die Anforderung (Akkusparmodus, alter Browser), + // laeuft der Sync unveraendert weiter — die Queue faengt den Abbruch ohnehin ab. + let wakeLock = null; + + async function acquireWakeLock() { + try { + if (!navigator.wakeLock || wakeLock) return; + wakeLock = await navigator.wakeLock.request('screen'); + wakeLock.addEventListener('release', function () { wakeLock = null; }); + } catch (_) { wakeLock = null; } + } + + function releaseWakeLock() { + try { if (wakeLock) wakeLock.release(); } catch (_) {} + wakeLock = null; + } + + // Ein Wake Lock geht verloren, sobald die Seite in den Hintergrund geht (Anruf, + // App-Wechsel). Kommt sie zurueck und laeuft noch ein Sync, neu anfordern. + try { + document.addEventListener('visibilitychange', function () { + if (document.visibilityState === 'visible' && syncing) acquireWakeLock(); + }); + } catch (_) {} + async function syncQueue() { // Läuft schon einer? Dann nur merken, dass danach nochmal geprüft werden soll // (so werden Fotos erfasst, die während eines laufenden Syncs neu dazukamen). if (syncing) { syncAgain = true; return; } if (!navigator.onLine) return; syncing = true; + await acquireWakeLock(); try { do { syncAgain = false; @@ -74,6 +103,7 @@ detail: { queueId: it.id, relpath: res && res.relpath, + duplicate: !!(res && res.duplicate), orderId: it.order_id, filename: it.filename, }, @@ -98,6 +128,7 @@ } while (syncAgain); } finally { syncing = false; + releaseWakeLock(); await updateBadge(); emitChange(); }