@bobfrankston/iflow-direct 0.1.18 → 0.1.19
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/README.md +10 -0
- package/imap-compat.d.ts +6 -2
- package/imap-compat.js +7 -5
- package/imap-native.js +37 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,6 +106,16 @@ the same connection). Suspend sends DONE, awaits the tagged OK, runs the
|
|
|
106
106
|
command, then re-enters IDLE. If the caller stops IDLE (via the `startIdle`
|
|
107
107
|
stop function) during the command, IDLE is not re-entered.
|
|
108
108
|
|
|
109
|
+
## Connection liveness
|
|
110
|
+
|
|
111
|
+
`NativeImapClient.connected` (and `CompatImapClient`'s lazy `ensureConnected`
|
|
112
|
+
path, which now delegates to it) is authoritative: it gets cleared on transport
|
|
113
|
+
error, transport close, inactivity-timeout socket kill, and write failure. Any
|
|
114
|
+
of those paths also drops IDLE state so auto-suspend doesn't try to DONE a dead
|
|
115
|
+
socket. Callers that detect connection-style errors (e.g. DNS "hostname not
|
|
116
|
+
known" from a dead Android socket) can safely retry — the next operation will
|
|
117
|
+
reconnect from scratch rather than reusing the dead socket.
|
|
118
|
+
|
|
109
119
|
## Configuration
|
|
110
120
|
|
|
111
121
|
`ImapClientConfig` fields relevant to throughput/resilience:
|
package/imap-compat.d.ts
CHANGED
|
@@ -23,11 +23,15 @@ export interface SpecialFolders {
|
|
|
23
23
|
*/
|
|
24
24
|
export declare class CompatImapClient {
|
|
25
25
|
private native;
|
|
26
|
-
private _connected;
|
|
27
26
|
constructor(config: ImapClientConfig, transportFactory: TransportFactory);
|
|
28
27
|
/** Connect and authenticate */
|
|
29
28
|
connect(): Promise<void>;
|
|
30
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Ensure connected (lazy connect). Delegates to the native client's
|
|
31
|
+
* `connected` flag, which is cleared by transport close/error, inactivity
|
|
32
|
+
* timeout, and write failures — so a silently dead socket reconnects on the
|
|
33
|
+
* next call instead of cascading ENOTFOUND across every folder.
|
|
34
|
+
*/
|
|
31
35
|
private ensureConnected;
|
|
32
36
|
logout(): Promise<void>;
|
|
33
37
|
/** Get folder list */
|
package/imap-compat.js
CHANGED
|
@@ -11,22 +11,24 @@ import { FetchedMessage } from "./fetched-message.js";
|
|
|
11
11
|
*/
|
|
12
12
|
export class CompatImapClient {
|
|
13
13
|
native;
|
|
14
|
-
_connected = false;
|
|
15
14
|
constructor(config, transportFactory) {
|
|
16
15
|
this.native = new NativeImapClient(config, transportFactory);
|
|
17
16
|
}
|
|
18
17
|
/** Connect and authenticate */
|
|
19
18
|
async connect() {
|
|
20
19
|
await this.native.connect();
|
|
21
|
-
this._connected = true;
|
|
22
20
|
}
|
|
23
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Ensure connected (lazy connect). Delegates to the native client's
|
|
23
|
+
* `connected` flag, which is cleared by transport close/error, inactivity
|
|
24
|
+
* timeout, and write failures — so a silently dead socket reconnects on the
|
|
25
|
+
* next call instead of cascading ENOTFOUND across every folder.
|
|
26
|
+
*/
|
|
24
27
|
async ensureConnected() {
|
|
25
|
-
if (!this.
|
|
28
|
+
if (!this.native.connected)
|
|
26
29
|
await this.connect();
|
|
27
30
|
}
|
|
28
31
|
async logout() {
|
|
29
|
-
this._connected = false;
|
|
30
32
|
await this.native.logout();
|
|
31
33
|
}
|
|
32
34
|
/** Get folder list */
|
package/imap-native.js
CHANGED
|
@@ -42,7 +42,14 @@ export class NativeImapClient {
|
|
|
42
42
|
this.transport.onData((data) => this.handleData(data));
|
|
43
43
|
this.transport.onClose(() => {
|
|
44
44
|
this._connected = false;
|
|
45
|
-
//
|
|
45
|
+
// Drop stale IDLE state — the socket is gone.
|
|
46
|
+
this.idleTag = null;
|
|
47
|
+
this.idleCallback = null;
|
|
48
|
+
this.idleStopped = true;
|
|
49
|
+
if (this.idleRefreshTimer) {
|
|
50
|
+
clearTimeout(this.idleRefreshTimer);
|
|
51
|
+
this.idleRefreshTimer = null;
|
|
52
|
+
}
|
|
46
53
|
if (this.pendingCommand) {
|
|
47
54
|
const { reject } = this.pendingCommand;
|
|
48
55
|
this.pendingCommand = null;
|
|
@@ -52,7 +59,18 @@ export class NativeImapClient {
|
|
|
52
59
|
this.transport.onError((err) => {
|
|
53
60
|
if (this.verbose)
|
|
54
61
|
console.error(` [imap] Transport error: ${err.message}`);
|
|
55
|
-
//
|
|
62
|
+
// Transport errors (DNS failure, ECONNRESET, TLS failure, etc.) mean
|
|
63
|
+
// the connection is dead — clear the flag so ensureConnected() will
|
|
64
|
+
// reconnect on the next call, and drop the stale IDLE state so we
|
|
65
|
+
// don't try to DONE a dead socket during auto-suspend.
|
|
66
|
+
this._connected = false;
|
|
67
|
+
this.idleTag = null;
|
|
68
|
+
this.idleCallback = null;
|
|
69
|
+
this.idleStopped = true;
|
|
70
|
+
if (this.idleRefreshTimer) {
|
|
71
|
+
clearTimeout(this.idleRefreshTimer);
|
|
72
|
+
this.idleRefreshTimer = null;
|
|
73
|
+
}
|
|
56
74
|
if (this.pendingCommand) {
|
|
57
75
|
const { reject } = this.pendingCommand;
|
|
58
76
|
this.pendingCommand = null;
|
|
@@ -674,7 +692,10 @@ export class NativeImapClient {
|
|
|
674
692
|
const onTimeout = () => {
|
|
675
693
|
this.commandTimer = null;
|
|
676
694
|
this.pendingCommand = null;
|
|
677
|
-
// Kill the connection — a timed-out connection has stale data in the pipe
|
|
695
|
+
// Kill the connection — a timed-out connection has stale data in the pipe.
|
|
696
|
+
// Mark disconnected so ensureConnected() reconnects on the next call —
|
|
697
|
+
// transport.close() may or may not fire onClose synchronously.
|
|
698
|
+
this._connected = false;
|
|
678
699
|
this.transport.close?.();
|
|
679
700
|
reject(new Error(`IMAP inactivity timeout (${this.inactivityTimeout / 1000}s): ${command.split("\r")[0].substring(0, 80)}`));
|
|
680
701
|
};
|
|
@@ -687,8 +708,18 @@ export class NativeImapClient {
|
|
|
687
708
|
clearTimeout(this.commandTimer); this.commandTimer = null; reject(err); },
|
|
688
709
|
onUntagged,
|
|
689
710
|
};
|
|
690
|
-
this.transport.write(command).catch((err) => {
|
|
691
|
-
|
|
711
|
+
this.transport.write(command).catch((err) => {
|
|
712
|
+
if (this.commandTimer)
|
|
713
|
+
clearTimeout(this.commandTimer);
|
|
714
|
+
this.commandTimer = null;
|
|
715
|
+
// Write failure = dead socket. Clear pendingCommand + disconnect flag
|
|
716
|
+
// so the next ensureConnected() does a fresh connect instead of
|
|
717
|
+
// reusing a socket that the OS layer has already declared dead
|
|
718
|
+
// (the source of the "hostname nor servname provided" cascade).
|
|
719
|
+
this.pendingCommand = null;
|
|
720
|
+
this._connected = false;
|
|
721
|
+
reject(err);
|
|
722
|
+
});
|
|
692
723
|
});
|
|
693
724
|
}
|
|
694
725
|
waitForContinuation(tag) {
|
|
@@ -718,6 +749,7 @@ export class NativeImapClient {
|
|
|
718
749
|
if (this.pendingCommand) {
|
|
719
750
|
const cmd = this.pendingCommand;
|
|
720
751
|
this.pendingCommand = null;
|
|
752
|
+
this._connected = false;
|
|
721
753
|
this.transport.close?.();
|
|
722
754
|
cmd.reject(new Error(`IMAP inactivity timeout (${this.inactivityTimeout / 1000}s)`));
|
|
723
755
|
}
|