From 130d9d1ec3e02de9163440bd00ca813c48b09c92 Mon Sep 17 00:00:00 2001 From: Eddy Date: Fri, 28 Aug 2026 18:58:35 +0200 Subject: [PATCH] Fotos gingen beim Screen-Aus verloren: Verkleinern konnte ewig haengen [deploy] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eddy: „beim ersten Upload nach Screen schliessen kamen nicht mehr alle an, ich habe noch die gruenen Labels gesehen, aber die Bilder waren nicht da". Ursache: uploadPhoto() verkleinert das Foto, BEVOR es in die Queue geschrieben wird. resizeImage() haengt an img.onload / canvas.toBlob und hatte keinen Timeout — schaltet sich das Display ab, pausiert der Browser Decoding und Canvas, es feuert weder onload noch onerror, das Promise loest nie auf. Damit war das Foto NIRGENDS gesichert (nicht in der Queue, nicht am Server), und weil die Galerie-Auswahl sequentiell laeuft, stand die komplette restliche Auswahl. Deshalb hat auch nichts automatisch nachgeladen: es gab nichts nachzuholen. An Auftrag (PROV105) fehlten so 8 von 19 Fotos. - resizeImage() bekommt eine Schutzzeit (Promise.race, 15s): im Zweifel wird das unverkleinerte Original gesichert statt gar nichts - Auswahl-Schleife: jedes Foto einzeln abgesichert, ein Fehlschlag blockiert die restliche Auswahl nicht mehr - Wake Lock schon waehrend der Auswahl-Verarbeitung, nicht erst beim Sync — das Verkleinern ist genau die Stelle, die pausiert wurde. Refcount, weil sich Auswahl-Schleife und Sync verschachteln Schutzzeit isoliert geprueft (haengendes Decoding simuliert): faellt nach der Frist auf das Original zurueck statt zu blockieren. Co-Authored-By: Claude Opus 5 (1M context) --- app.js | 29 +++++++++++++++++++++++++---- lib/offline.js | 15 +++++++++++++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/app.js b/app.js index 808949e..28445ee 100644 --- a/app.js +++ b/app.js @@ -907,8 +907,20 @@ router.on('/orders/:id', async (args) => { document.getElementById('btn-pick-photo').onclick = () => galInput.click(); async function handleFiles(files) { - for (const f of files) { - await uploadPhoto(args.id, f); + // Screen wachhalten, solange die Auswahl abgearbeitet wird — sonst pausiert + // das Verkleinern mitten in der Serie (siehe resizeImage). + await offline.acquireWakeLock(); + try { + for (const f of files) { + // Ein einzelnes Foto darf die restliche Auswahl nicht blockieren + try { + await uploadPhoto(args.id, f); + } catch (e) { + showToast('Foto ' + (f.name || '') + ' fehlgeschlagen: ' + ((e && e.message) || ''), 'error'); + } + } + } finally { + offline.releaseWakeLock(); } // Nach Upload einfach die Route neu rendern — so werden Select-Mode, // Click-Handler und Thumbnails sauber neu aufgebaut. @@ -935,8 +947,15 @@ async function uploadPhoto(orderId, file) { await offline.syncQueue(); } -async function resizeImage(file, maxSide) { - return new Promise((resolve) => { +async function resizeImage(file, maxSide, timeoutMs) { + // SCHUTZZEIT (v1.1): Schaltet sich das Display waehrend einer Serie ab, pausiert der + // Browser Bild-Decoding und Canvas — dann feuert weder onload noch onerror und das + // Promise loeste NIE auf. Das passiert VOR dem Sichern in der Queue, die betroffenen + // Fotos waren also nirgends gespeichert, und weil die Auswahl sequentiell laeuft, + // stand die komplette restliche Auswahl still (28.08.2026, Auftrag PROV105: 8 von 19 + // Fotos kamen nie an und waren auch nicht in der Queue zum Nachholen). + // Im Zweifel lieber das unverkleinerte Original sichern als gar nichts. + const shrink = new Promise((resolve) => { const img = new Image(); const url = URL.createObjectURL(file); img.onload = () => { @@ -952,6 +971,8 @@ async function resizeImage(file, maxSide) { img.onerror = () => { URL.revokeObjectURL(url); resolve(file); }; img.src = url; }); + const guard = new Promise((resolve) => setTimeout(() => resolve(file), timeoutMs || 15000)); + return Promise.race([shrink, guard]); } /** diff --git a/lib/offline.js b/lib/offline.js index e9512cc..98df374 100644 --- a/lib/offline.js +++ b/lib/offline.js @@ -56,9 +56,14 @@ // 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. + // Referenzzaehler, weil zwei Stellen den Lock brauchen und sich verschachteln: + // die Auswahl-Schleife (Verkleinern + Sichern) und der Sync selbst. Wer zuerst + // fertig ist, darf dem anderen den Lock nicht wegnehmen. let wakeLock = null; + let wakeRefs = 0; async function acquireWakeLock() { + wakeRefs++; try { if (!navigator.wakeLock || wakeLock) return; wakeLock = await navigator.wakeLock.request('screen'); @@ -67,6 +72,8 @@ } function releaseWakeLock() { + wakeRefs = Math.max(0, wakeRefs - 1); + if (wakeRefs > 0) return; try { if (wakeLock) wakeLock.release(); } catch (_) {} wakeLock = null; } @@ -75,7 +82,10 @@ // App-Wechsel). Kommt sie zurueck und laeuft noch ein Sync, neu anfordern. try { document.addEventListener('visibilitychange', function () { - if (document.visibilityState === 'visible' && syncing) acquireWakeLock(); + if (document.visibilityState === 'visible' && wakeRefs > 0 && !wakeLock) { + wakeRefs--; // acquireWakeLock zaehlt gleich wieder hoch + acquireWakeLock(); + } }); } catch (_) {} @@ -203,5 +213,6 @@ setInterval(() => { if (navigator.onLine) syncQueue(); }, 15000); }); - window.offline = { enqueuePhoto, syncQueue, updateBadge, queueCount, listQueue, retryFailed }; + window.offline = { enqueuePhoto, syncQueue, updateBadge, queueCount, listQueue, retryFailed, + acquireWakeLock, releaseWakeLock }; })();