fix(mail): BigInt-Serialisierung blockierte Phase-2-Persistierung

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 <noreply@anthropic.com>
This commit is contained in:
chahinebrini 2026-06-05 12:16:09 +02:00
parent cc549c7f17
commit 1f73bd8d8d

View File

@ -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 {}