@bobfrankston/iflow-direct 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/gmail.d.ts CHANGED
@@ -10,6 +10,8 @@ export interface GmailOAuthConfig {
10
10
  verbose?: boolean;
11
11
  rejectUnauthorized?: boolean;
12
12
  inactivityTimeout?: number;
13
+ fetchChunkSize?: number;
14
+ fetchChunkSizeMax?: number;
13
15
  }
14
16
  /**
15
17
  * Check if email address is Gmail
@@ -37,5 +39,7 @@ export declare function createAutoImapConfig(config: {
37
39
  verbose?: boolean;
38
40
  rejectUnauthorized?: boolean;
39
41
  inactivityTimeout?: number;
42
+ fetchChunkSize?: number;
43
+ fetchChunkSizeMax?: number;
40
44
  }): ImapClientConfig;
41
45
  //# sourceMappingURL=gmail.d.ts.map
package/gmail.js CHANGED
@@ -34,6 +34,8 @@ export function createGmailConfig(config) {
34
34
  verbose: config.verbose,
35
35
  rejectUnauthorized: config.rejectUnauthorized,
36
36
  inactivityTimeout: config.inactivityTimeout,
37
+ fetchChunkSize: config.fetchChunkSize,
38
+ fetchChunkSizeMax: config.fetchChunkSizeMax,
37
39
  };
38
40
  }
39
41
  /**
@@ -52,6 +54,8 @@ export function createAutoImapConfig(config) {
52
54
  verbose: config.verbose,
53
55
  rejectUnauthorized: config.rejectUnauthorized,
54
56
  inactivityTimeout: config.inactivityTimeout,
57
+ fetchChunkSize: config.fetchChunkSize,
58
+ fetchChunkSizeMax: config.fetchChunkSizeMax,
55
59
  });
56
60
  }
57
61
  else {
@@ -63,6 +67,8 @@ export function createAutoImapConfig(config) {
63
67
  verbose: config.verbose,
64
68
  rejectUnauthorized: config.rejectUnauthorized,
65
69
  inactivityTimeout: config.inactivityTimeout,
70
+ fetchChunkSize: config.fetchChunkSize,
71
+ fetchChunkSizeMax: config.fetchChunkSizeMax,
66
72
  };
67
73
  }
68
74
  }
package/imap-native.d.ts CHANGED
@@ -54,6 +54,7 @@ export declare class NativeImapClient {
54
54
  private _connected;
55
55
  private idleTag;
56
56
  private idleCallback;
57
+ private idleRefreshTimer;
57
58
  private verbose;
58
59
  private selectedMailbox;
59
60
  private mailboxInfo;
@@ -114,6 +115,8 @@ export declare class NativeImapClient {
114
115
  /** Append a message to a mailbox */
115
116
  appendMessage(mailbox: string, message: string | Uint8Array, flags?: string[]): Promise<number | null>;
116
117
  startIdle(onNewMail: (count: number) => void): Promise<() => Promise<void>>;
118
+ /** Send DONE + re-IDLE. Called by the 28-minute refresh timer. */
119
+ private refreshIdle;
117
120
  getMessageCount(mailbox: string): Promise<number>;
118
121
  /** Inactivity timeout — how long to wait with NO data before declaring the connection dead.
119
122
  * This is NOT a wall-clock timeout. Timer resets every time data arrives from the server.
@@ -121,9 +124,11 @@ export declare class NativeImapClient {
121
124
  * 60s accommodates Gmail which is slow on SEARCH for large folders.
122
125
  * Overridable via ImapClientConfig.inactivityTimeout — slow Dovecot servers need 180s+. */
123
126
  private inactivityTimeout;
124
- /** Fetch chunk sizes — start small for quick first paint, ramp up for throughput */
125
- private static INITIAL_CHUNK_SIZE;
126
- private static MAX_CHUNK_SIZE;
127
+ /** Fetch chunk sizes — start small for quick first paint, ramp up for throughput.
128
+ * Default 25 initial → 500 max. Overridable via ImapClientConfig.fetchChunkSize /
129
+ * fetchChunkSizeMax. Slow servers benefit from smaller chunks (fewer timeouts). */
130
+ private fetchChunkSize;
131
+ private fetchChunkSizeMax;
127
132
  /** Active command timer — reset by handleData on every data arrival */
128
133
  private commandTimer;
129
134
  private sendCommand;
package/imap-native.js CHANGED
@@ -17,6 +17,7 @@ export class NativeImapClient {
17
17
  _connected = false;
18
18
  idleTag = null;
19
19
  idleCallback = null;
20
+ idleRefreshTimer = null;
20
21
  verbose;
21
22
  selectedMailbox = null;
22
23
  mailboxInfo = { exists: 0, recent: 0, uidNext: 0, uidValidity: 0, flags: [], permanentFlags: [] };
@@ -29,6 +30,8 @@ export class NativeImapClient {
29
30
  this.transport = transportFactory();
30
31
  this.verbose = config.verbose || false;
31
32
  this.inactivityTimeout = config.inactivityTimeout ?? 60000;
33
+ this.fetchChunkSize = config.fetchChunkSize ?? 25;
34
+ this.fetchChunkSizeMax = config.fetchChunkSizeMax ?? 500;
32
35
  }
33
36
  get connected() { return this._connected; }
34
37
  // ── Connection ──
@@ -296,14 +299,14 @@ export class NativeImapClient {
296
299
  return [];
297
300
  uids.reverse(); // Newest first
298
301
  console.log(` [fetch] ${uids.length} UIDs since ${sinceUid} (newest first)`);
299
- if (uids.length <= NativeImapClient.INITIAL_CHUNK_SIZE) {
302
+ if (uids.length <= this.fetchChunkSize) {
300
303
  const msgs = await this.fetchMessages(uids.join(","), options);
301
304
  if (onChunk)
302
305
  onChunk(msgs);
303
306
  return msgs;
304
307
  }
305
308
  const allMessages = [];
306
- let chunkSize = NativeImapClient.INITIAL_CHUNK_SIZE;
309
+ let chunkSize = this.fetchChunkSize;
307
310
  for (let i = 0; i < uids.length; i += chunkSize) {
308
311
  const chunk = uids.slice(i, i + chunkSize);
309
312
  const msgs = await this.fetchMessages(chunk.join(","), options);
@@ -311,8 +314,8 @@ export class NativeImapClient {
311
314
  console.log(` [fetch] ${allMessages.length}/${uids.length} (chunk of ${chunk.length})`);
312
315
  if (onChunk)
313
316
  onChunk(msgs);
314
- if (chunkSize < NativeImapClient.MAX_CHUNK_SIZE)
315
- chunkSize = Math.min(chunkSize * 4, NativeImapClient.MAX_CHUNK_SIZE);
317
+ if (chunkSize < this.fetchChunkSizeMax)
318
+ chunkSize = Math.min(chunkSize * 4, this.fetchChunkSizeMax);
316
319
  }
317
320
  return allMessages;
318
321
  }
@@ -329,7 +332,7 @@ export class NativeImapClient {
329
332
  uids.reverse();
330
333
  console.log(` [fetch] ${uids.length} UIDs to fetch (newest first)`);
331
334
  const allMessages = [];
332
- let chunkSize = NativeImapClient.INITIAL_CHUNK_SIZE;
335
+ let chunkSize = this.fetchChunkSize;
333
336
  for (let i = 0; i < uids.length; i += chunkSize) {
334
337
  const chunk = uids.slice(i, i + chunkSize);
335
338
  const msgs = await this.fetchMessages(chunk.join(","), options);
@@ -337,8 +340,8 @@ export class NativeImapClient {
337
340
  console.log(` [fetch] ${allMessages.length}/${uids.length} (chunk of ${chunk.length})`);
338
341
  if (onChunk)
339
342
  onChunk(msgs);
340
- if (chunkSize < NativeImapClient.MAX_CHUNK_SIZE)
341
- chunkSize = Math.min(chunkSize * 4, NativeImapClient.MAX_CHUNK_SIZE);
343
+ if (chunkSize < this.fetchChunkSizeMax)
344
+ chunkSize = Math.min(chunkSize * 4, this.fetchChunkSizeMax);
342
345
  }
343
346
  return allMessages;
344
347
  }
@@ -437,18 +440,74 @@ export class NativeImapClient {
437
440
  // ── IDLE ──
438
441
  async startIdle(onNewMail) {
439
442
  this.idleCallback = onNewMail;
440
- const tag = proto.nextTag();
441
- this.idleTag = tag;
442
- await this.transport.write(proto.idleCommand(tag));
443
- // Wait for "+" continuation
444
- await this.waitForContinuation(tag);
443
+ let stopped = false;
444
+ const beginIdleCycle = async () => {
445
+ const tag = proto.nextTag();
446
+ this.idleTag = tag;
447
+ await this.transport.write(proto.idleCommand(tag));
448
+ await this.waitForContinuation(tag);
449
+ // RFC 2177: re-IDLE every ~28 minutes to avoid servers that terminate
450
+ // idle sessions at the 29-minute mark, and to give us a steady
451
+ // application-level keepalive that detects silently-dropped sockets.
452
+ this.idleRefreshTimer = setTimeout(() => {
453
+ if (stopped)
454
+ return;
455
+ this.refreshIdle().catch(err => {
456
+ if (this.verbose)
457
+ console.error(` [imap] IDLE refresh failed: ${err.message}`);
458
+ });
459
+ }, 28 * 60 * 1000);
460
+ };
461
+ await beginIdleCycle();
445
462
  return async () => {
463
+ stopped = true;
464
+ if (this.idleRefreshTimer) {
465
+ clearTimeout(this.idleRefreshTimer);
466
+ this.idleRefreshTimer = null;
467
+ }
468
+ const tag = this.idleTag;
446
469
  this.idleTag = null;
447
470
  this.idleCallback = null;
448
- await this.transport.write(proto.doneCommand());
449
- await this.waitForTagged(tag);
471
+ try {
472
+ await this.transport.write(proto.doneCommand());
473
+ }
474
+ catch { /* ignore */ }
475
+ if (tag) {
476
+ try {
477
+ await this.waitForTagged(tag);
478
+ }
479
+ catch { /* ignore */ }
480
+ }
450
481
  };
451
482
  }
483
+ /** Send DONE + re-IDLE. Called by the 28-minute refresh timer. */
484
+ async refreshIdle() {
485
+ const oldTag = this.idleTag;
486
+ if (!oldTag || !this.idleCallback)
487
+ return;
488
+ const cb = this.idleCallback;
489
+ // DONE the current IDLE
490
+ await this.transport.write(proto.doneCommand());
491
+ try {
492
+ await this.waitForTagged(oldTag);
493
+ }
494
+ catch { /* best-effort */ }
495
+ // Re-IDLE with a new tag
496
+ this.idleCallback = cb; // waitForTagged resolved pendingCommand, reinstall callback
497
+ const newTag = proto.nextTag();
498
+ this.idleTag = newTag;
499
+ await this.transport.write(proto.idleCommand(newTag));
500
+ await this.waitForContinuation(newTag);
501
+ if (this.verbose)
502
+ console.log(` [imap] IDLE refreshed (${newTag})`);
503
+ // Schedule the next refresh
504
+ this.idleRefreshTimer = setTimeout(() => {
505
+ this.refreshIdle().catch(err => {
506
+ if (this.verbose)
507
+ console.error(` [imap] IDLE refresh failed: ${err.message}`);
508
+ });
509
+ }, 28 * 60 * 1000);
510
+ }
452
511
  // ── Message count (lightweight STATUS) ──
453
512
  async getMessageCount(mailbox) {
454
513
  const status = await this.getStatus(mailbox);
@@ -461,9 +520,11 @@ export class NativeImapClient {
461
520
  * 60s accommodates Gmail which is slow on SEARCH for large folders.
462
521
  * Overridable via ImapClientConfig.inactivityTimeout — slow Dovecot servers need 180s+. */
463
522
  inactivityTimeout;
464
- /** Fetch chunk sizes — start small for quick first paint, ramp up for throughput */
465
- static INITIAL_CHUNK_SIZE = 25;
466
- static MAX_CHUNK_SIZE = 500;
523
+ /** Fetch chunk sizes — start small for quick first paint, ramp up for throughput.
524
+ * Default 25 initial → 500 max. Overridable via ImapClientConfig.fetchChunkSize /
525
+ * fetchChunkSizeMax. Slow servers benefit from smaller chunks (fewer timeouts). */
526
+ fetchChunkSize;
527
+ fetchChunkSizeMax;
467
528
  /** Active command timer — reset by handleData on every data arrival */
468
529
  commandTimer = null;
469
530
  sendCommand(tag, command) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/iflow-direct",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Direct IMAP client — transport-agnostic, no Node.js dependencies, browser-ready",
5
5
  "main": "index.js",
6
6
  "types": "index.ts",
package/types.d.ts CHANGED
@@ -11,5 +11,7 @@ export interface ImapClientConfig {
11
11
  verbose?: boolean;
12
12
  rejectUnauthorized?: boolean;
13
13
  inactivityTimeout?: number;
14
+ fetchChunkSize?: number;
15
+ fetchChunkSizeMax?: number;
14
16
  }
15
17
  //# sourceMappingURL=types.d.ts.map