From 1f73bd8d8df101c787f04333ad54a7c6b16d839d Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Fri, 5 Jun 2026 12:16:09 +0200 Subject: [PATCH] fix(mail): BigInt-Serialisierung blockierte Phase-2-Persistierung MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit imapflow.status() liefert uidValidity als BigInt. Der Code reichte den BigInt in JSON.stringify (patchFolderScanState) → 'TypeError: Do not know how to serialize a BigInt' → vom stummen connection-level catch verschluckt → weder patchFolderScanState noch markFullSweepDone liefen je → folder_scan_state blieb {} + last_full_sweep_at NULL → inkrementeller Scan aktivierte nie (immer Full-Sweep). Fix: - serverUidValidity: Number((status).uidValidity ?? 0) — BigInt → number vor JSON. - Stumme catches (auth/lock/conn) loggen jetzt; Persist-Calls (patchFolderScanState x2, markFullSweepDone) in eigene try/catch mit console.error — Diagnostik bleibt drin für Post-Deploy-Verify. Lokal verifiziert: Build EXIT 0, imapflow extern. Co-Authored-By: Claude Opus 4.8 --- backend/server/api/mail/scan-internal.post.ts | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/backend/server/api/mail/scan-internal.post.ts b/backend/server/api/mail/scan-internal.post.ts index dc9b90a..c49ed9c 100644 --- a/backend/server/api/mail/scan-internal.post.ts +++ b/backend/server/api/mail/scan-internal.post.ts @@ -78,7 +78,8 @@ export default defineEventHandler(async (event) => { let imapAuth: { user: string; accessToken: string } | { user: string; pass: string }; try { imapAuth = await resolveImapAuth(connection, oauthClientIds); - } catch { + } catch (authErr) { + console.error(`[scan-internal] resolveImapAuth failed for ${connection.email}:`, authErr); continue; } @@ -136,7 +137,8 @@ export default defineEventHandler(async (event) => { let lock: any; try { lock = await imap.getMailboxLock(mb.path); - } catch { + } catch (lockErr) { + console.warn(`[scan-internal] ${connection.email} | ${mb.path} | getMailboxLock failed, skipping folder:`, lockErr); continue; } try { @@ -149,16 +151,25 @@ export default defineEventHandler(async (event) => { uidValidity: true, }); const msgCount = (status as any).messages ?? 0; - const serverUidValidity: number = (status as any).uidValidity ?? 0; + // imapflow liefert uidValidity als BigInt (IMAP-Spec: 32-bit unsigned). + // Number() konvertiert BigInt sicher — UIDVALIDITY ist max 2^32-1, weit unter + // Number.MAX_SAFE_INTEGER (2^53-1), kein Präzisionsverlust möglich. + // Ohne Number() würde JSON.stringify({uidvalidity: BigInt(x)}) werfen und + // patchFolderScanState still crashen (verschluckt vom äußeren catch {}). + const serverUidValidity: number = Number((status as any).uidValidity ?? 0); if (msgCount === 0) { // Ordner leer — trotzdem Zustand für diesen Ordner persistieren // (verhindert endloses Re-Fetching auf leere Ordner). if (serverUidValidity > 0) { - await patchFolderScanState(connection.id, mb.path, { - lastUid: 0, - uidvalidity: serverUidValidity, - }); + try { + await patchFolderScanState(connection.id, mb.path, { + lastUid: 0, + uidvalidity: serverUidValidity, + }); + } catch (e) { + console.error(`[scan-internal] persist failed — patchFolderScanState(${mb.path}, empty folder):`, e); + } } continue; } @@ -353,10 +364,14 @@ export default defineEventHandler(async (event) => { }, 0); if (maxUid > 0 && serverUidValidity > 0) { - await patchFolderScanState(connection.id, mb.path, { - lastUid: maxUid, - uidvalidity: serverUidValidity, - }); + try { + await patchFolderScanState(connection.id, mb.path, { + lastUid: maxUid, + uidvalidity: serverUidValidity, + }); + } catch (e) { + console.error(`[scan-internal] persist failed — patchFolderScanState(${mb.path}, maxUid=${maxUid}, uidvalidity=${serverUidValidity}):`, e); + } } } finally { lock.release(); @@ -365,12 +380,17 @@ export default defineEventHandler(async (event) => { // ─── Full-Sweep abschließen ───────────────────────────────────────────── if (needsFullSweep) { - await markFullSweepDone(connection.id); - console.log(`[scan-internal] ${connection.email} | full-sweep complete, lastFullSweepAt updated`); + try { + await markFullSweepDone(connection.id); + console.log(`[scan-internal] ${connection.email} | full-sweep complete, lastFullSweepAt updated`); + } catch (e) { + console.error(`[scan-internal] persist failed — markFullSweepDone(${connection.id}):`, e); + } } await imap.logout(); - } catch { + } catch (connErr) { + console.error(`[scan-internal] connection-level error for ${connection.email}:`, connErr); try { await imap.logout(); } catch {}