fix(imap-idle): IDLE-renew 25min→10min + NOOP-heartbeat (GMX silent-drop fix)
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) <noreply@anthropic.com>
This commit is contained in:
parent
a81ba2e54a
commit
01420eaa09
@ -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);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user