From 01420eaa0947011d9a10ce8c32f7b8b0b508fbf5 Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Sat, 9 May 2026 23:42:09 +0200 Subject: [PATCH] =?UTF-8?q?fix(imap-idle):=20IDLE-renew=2025min=E2=86=9210?= =?UTF-8?q?min=20+=20NOOP-heartbeat=20(GMX=20silent-drop=20fix)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User-test: Casino-mail an Chahine@gmx.net wurde nicht geblockt obwohl daemon "connected" zeigte. Mo's diagnose: GMX dropped IDLE-connection silent (kein TCP-error, kein logout). ImapFlow.idle() hängt unbegrenzt ohne reject — exists-events kommen nie an, daemon ist faktisch tot. 2 Fixes: 1) IDLE_RENEW_INTERVAL_MS: 25 min → 10 min. GMX timeout-window ist ~10-15min, 25min war zu lang. Trade-off: alle 10min full reconnect. 2) NOOP-heartbeat alle 2min während IDLE-loop. Wenn NOOP fail (= silent-drop detected) → close → reconnect-loop. Early-detection. Andere provider (Gmail/iCloud/Outlook) sind unaffected — die haben ~29min IDLE-timeout, also passt 10min auch dort safe. Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/imap-idle/index.mjs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/backend/imap-idle/index.mjs b/backend/imap-idle/index.mjs index 42e5281..5860d67 100644 --- a/backend/imap-idle/index.mjs +++ b/backend/imap-idle/index.mjs @@ -35,7 +35,14 @@ const ADMIN_SECRET = process.env.NUXT_ADMIN_SECRET || process.env.ADMIN_SECRET || ""; const DB_REFRESH_INTERVAL_MS = 5 * 60 * 1000; // 5 min — neue Connections entdecken -const IDLE_RENEW_INTERVAL_MS = 25 * 60 * 1000; // 25 min — RFC 3501 max = 29min +// IDLE_RENEW von 25min → 10min: GMX dropped IDLE-connections silent vor 25min +// → exists-events kommen nie an + ImapFlow.idle() hängt ohne reject. 10min +// deckt alle bekannten Provider-Timeouts ab (GMX ~10-15min, Gmail ~29min, +// iCloud ~29min, Outlook ~29min). Trade-off: alle 10min full reconnect-cycle. +const IDLE_RENEW_INTERVAL_MS = 10 * 60 * 1000; // 10 min (war 25) +// NOOP-heartbeat alle 2min während IDLE: defensive check ob connection wirklich +// alive ist. Wenn NOOP fehlschlägt → close + reconnect-loop. +const IDLE_NOOP_INTERVAL_MS = 2 * 60 * 1000; // 2 min — silent-drop early-detection const RECONNECT_DELAYS_MS = [1000, 5000, 30_000]; // exponential backoff, danach 60s loop const RECONNECT_LOOP_DELAY_MS = 60 * 1000; @@ -205,15 +212,29 @@ async function runSession(conn) { }; imap.on("exists", onExists); - // IDLE nach 25min erneuern (RFC 3501: Server darf nach 29min droppen) + // IDLE nach 10min erneuern (war 25; GMX dropped silent vor 25min) const renewTimer = setTimeout(() => { - log(conn.email, "idle renewing (25min threshold)"); + log(conn.email, "idle renewing (10min threshold)"); imap.close(); // Unterbricht idle() → Loop iteriert → reconnect }, IDLE_RENEW_INTERVAL_MS); + // NOOP-heartbeat alle 2min: detect silent-IDLE-drops (GMX-pattern). + // Wenn NOOP fehlschlägt → close → loop iteriert → reconnect. + const noopTimer = setInterval(async () => { + try { + await imap.noop(); + // Optional: verbose-log für debugging — aktuell silent + // log(conn.email, "noop ok"); + } catch (err) { + logError(conn.email, "noop failed — connection dead, force reconnect", err); + imap.close(); + } + }, IDLE_NOOP_INTERVAL_MS); + try { await idlePromise; } finally { + clearInterval(noopTimer); clearTimeout(renewTimer); imap.removeListener("exists", onExists); }