@@ -5,6 +5,10 @@ import type { ImapFlow } from "imapflow";
55import type { FlagsEvent } from "imapflow" ;
66import { deltaFetch } from "../../lib/imap/imap-delta-fetch" ;
77
8+ const MAX_RECONNECT_ATTEMPTS = 5 ;
9+ const RECONNECT_BACKOFFS_MS = [ 5000 , 10000 , 20000 , 40000 , 80000 ] ;
10+ const reconnectAttempts = new Map < string , number > ( ) ;
11+
812async function handleFlagsUpdate (
913 identityId : string ,
1014 uid : number ,
@@ -247,27 +251,62 @@ function attachRealtimeEventHandlers(
247251 } ) ;
248252}
249253
250- async function idleForever ( identityId : string , client : ImapFlow ) {
254+ async function idleForever (
255+ identityId : string ,
256+ client : ImapFlow ,
257+ idleImapInstances : Map < string , ImapFlow > ,
258+ imapInstances : Map < string , ImapFlow > ,
259+ ) {
251260 console . log ( `[realtime:${ identityId } ] entering idle loop...` ) ;
252261 while ( client . authenticated && client . usable ) {
253262 try {
254263 await client . idle ( ) ;
264+ reconnectAttempts . set ( identityId , 0 ) ;
255265 } catch ( err ) {
256266 console . error ( `[realtime:${ identityId } ] idle error` , err ) ;
257267 }
258268 }
259269 console . warn ( `[realtime:${ identityId } ] idle loop ended (client closed)` ) ;
270+
271+ const attempts = reconnectAttempts . get ( identityId ) ?? 0 ;
272+ if ( attempts >= MAX_RECONNECT_ATTEMPTS ) {
273+ console . error ( `[realtime:${ identityId } ] reconnect cap reached, giving up` ) ;
274+ return ;
275+ }
276+
277+ const backoffMs = RECONNECT_BACKOFFS_MS [ attempts ] ;
278+ reconnectAttempts . set ( identityId , attempts + 1 ) ;
279+
280+ try {
281+ await client . logout ( ) ;
282+ } catch { }
283+ idleImapInstances . delete ( identityId ) ;
284+
285+ console . log (
286+ `[realtime:${ identityId } ] reconnecting in ${ backoffMs / 1000 } s (attempt ${ attempts + 1 } /${ MAX_RECONNECT_ATTEMPTS } )` ,
287+ ) ;
288+
289+ setTimeout ( ( ) => {
290+ startRealtimeForIdentity (
291+ identityId ,
292+ idleImapInstances ,
293+ imapInstances ,
294+ ) . catch ( ( err ) =>
295+ console . error ( `[realtime:${ identityId } ] reconnect failed` , err ) ,
296+ ) ;
297+ } , backoffMs ) ;
260298}
261299
262300async function startRealtimeSyncForIdentity (
263301 identityId : string ,
264302 client : ImapFlow ,
303+ idleImapInstances : Map < string , ImapFlow > ,
265304 imapInstances : Map < string , ImapFlow > ,
266305) {
267306 try {
268307 await client . getMailboxLock ( "INBOX" ) ;
269308 attachRealtimeEventHandlers ( identityId , client , imapInstances ) ;
270- idleForever ( identityId , client ) ;
309+ idleForever ( identityId , client , idleImapInstances , imapInstances ) ;
271310 } catch ( err ) {
272311 console . error (
273312 `[realtime:${ identityId } ] failed to start realtime sync` ,
@@ -295,7 +334,12 @@ export async function startRealtimeForIdentity(
295334 }
296335
297336 ( client as any ) . __kurrierRealtimeStarted = true ;
298- await startRealtimeSyncForIdentity ( identityId , client , imapInstances ) ;
337+ await startRealtimeSyncForIdentity (
338+ identityId ,
339+ client ,
340+ idleImapInstances ,
341+ imapInstances ,
342+ ) ;
299343}
300344
301345export async function stopRealtimeForIdentity (
0 commit comments