@bobfrankston/iflow-direct 0.1.32 → 0.1.33

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/imap-native.d.ts CHANGED
@@ -173,6 +173,11 @@ export declare class NativeImapClient {
173
173
  * 60s accommodates Gmail which is slow on SEARCH for large folders.
174
174
  * Overridable via ImapClientConfig.inactivityTimeout — slow Dovecot servers need 180s+. */
175
175
  private inactivityTimeout;
176
+ /** Hard wall-clock cap per command (independent of inactivityTimeout).
177
+ * A pathological server that returns one byte every (inactivityTimeout-1)
178
+ * seconds would otherwise defer the inactivity timer forever; this is
179
+ * the absolute deadline that always fires. Default 5 minutes. */
180
+ private commandWallClockTimeout;
176
181
  /** Server-greeting timeout in ms — how long to wait for the server's
177
182
  * initial banner after TCP/TLS connects. Default 10s; bumped to 30s
178
183
  * for slow shared-hosting Dovecot in mailx-imap. */
package/imap-native.js CHANGED
@@ -32,6 +32,10 @@ export class NativeImapClient {
32
32
  this.transport = transportFactory();
33
33
  this.verbose = config.verbose || false;
34
34
  this.inactivityTimeout = config.inactivityTimeout ?? 60000;
35
+ // Hard wall-clock deadline per command (default 5 min). Distinct
36
+ // from inactivityTimeout — that one resets on each data chunk and
37
+ // can be deferred indefinitely by a slow-trickling server.
38
+ this.commandWallClockTimeout = config.commandWallClockTimeout ?? 300_000;
35
39
  this.fetchChunkSize = config.fetchChunkSize ?? 25;
36
40
  this.fetchChunkSizeMax = config.fetchChunkSizeMax ?? 500;
37
41
  this.greetingTimeout = config.greetingTimeout ?? 10000;
@@ -729,6 +733,11 @@ export class NativeImapClient {
729
733
  * 60s accommodates Gmail which is slow on SEARCH for large folders.
730
734
  * Overridable via ImapClientConfig.inactivityTimeout — slow Dovecot servers need 180s+. */
731
735
  inactivityTimeout;
736
+ /** Hard wall-clock cap per command (independent of inactivityTimeout).
737
+ * A pathological server that returns one byte every (inactivityTimeout-1)
738
+ * seconds would otherwise defer the inactivity timer forever; this is
739
+ * the absolute deadline that always fires. Default 5 minutes. */
740
+ commandWallClockTimeout;
732
741
  /** Server-greeting timeout in ms — how long to wait for the server's
733
742
  * initial banner after TCP/TLS connects. Default 10s; bumped to 30s
734
743
  * for slow shared-hosting Dovecot in mailx-imap. */
@@ -796,21 +805,39 @@ export class NativeImapClient {
796
805
  const waited = Date.now() - cmdStart;
797
806
  console.log(` [imap] still waiting for tag ${tag} after ${(waited / 1000).toFixed(1)}s — ${cmdSummary}${transportDiag()}`);
798
807
  }, HEARTBEAT_INTERVAL_MS);
799
- const onTimeout = () => {
808
+ const onTimeout = (kind) => {
800
809
  this.commandTimer = null;
801
810
  this.pendingCommand = null;
802
811
  if (heartbeatTimer) {
803
812
  clearInterval(heartbeatTimer);
804
813
  heartbeatTimer = null;
805
814
  }
815
+ if (wallClockTimer) {
816
+ clearTimeout(wallClockTimer);
817
+ wallClockTimer = null;
818
+ }
806
819
  // Kill the connection — a timed-out connection has stale data in the pipe.
807
820
  // Mark disconnected so ensureConnected() reconnects on the next call —
808
821
  // transport.close() may or may not fire onClose synchronously.
809
822
  this._connected = false;
810
823
  this.transport.close?.();
811
- reject(new Error(`IMAP inactivity timeout (${this.inactivityTimeout / 1000}s): ${cmdSummary}${transportDiag()}`));
824
+ reject(new Error(`${kind}: ${cmdSummary}${transportDiag()}`));
812
825
  };
813
- this.commandTimer = setTimeout(onTimeout, this.inactivityTimeout);
826
+ // Two timers, two responsibilities:
827
+ // commandTimer = "no bytes for N seconds" — handleData resets it
828
+ // on every chunk so a steadily-streaming FETCH
829
+ // doesn't get killed mid-flight.
830
+ // wallClockTimer = "this command has been pending too long
831
+ // total" — fires regardless of trickling bytes.
832
+ // Catches the pathological case where a server
833
+ // drips one byte every <inactivityTimeout> seconds
834
+ // for hours (real bobma bug we hit overnight:
835
+ // 3 connections wedged 2-3 hours each because
836
+ // the inactivity reset kept deferring the only
837
+ // timer in play).
838
+ this.commandTimer = setTimeout(() => onTimeout(`IMAP inactivity timeout (${this.inactivityTimeout / 1000}s)`), this.inactivityTimeout);
839
+ const COMMAND_WALL_CLOCK_TIMEOUT_MS = this.commandWallClockTimeout;
840
+ let wallClockTimer = setTimeout(() => onTimeout(`IMAP command wall-clock timeout (${COMMAND_WALL_CLOCK_TIMEOUT_MS / 1000}s)`), COMMAND_WALL_CLOCK_TIMEOUT_MS);
814
841
  const clearTimers = () => {
815
842
  if (this.commandTimer) {
816
843
  clearTimeout(this.commandTimer);
@@ -820,6 +847,10 @@ export class NativeImapClient {
820
847
  clearInterval(heartbeatTimer);
821
848
  heartbeatTimer = null;
822
849
  }
850
+ if (wallClockTimer) {
851
+ clearTimeout(wallClockTimer);
852
+ wallClockTimer = null;
853
+ }
823
854
  };
824
855
  this.pendingCommand = {
825
856
  tag, responses: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/iflow-direct",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
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,6 +11,7 @@ export interface ImapClientConfig {
11
11
  verbose?: boolean;
12
12
  rejectUnauthorized?: boolean;
13
13
  inactivityTimeout?: number;
14
+ commandWallClockTimeout?: number;
14
15
  fetchChunkSize?: number;
15
16
  fetchChunkSizeMax?: number;
16
17
  greetingTimeout?: number;