@bobfrankston/iflow 1.0.52 → 1.0.54
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/imaplib/imap-native.d.ts
CHANGED
|
@@ -115,8 +115,12 @@ export declare class NativeImapClient {
|
|
|
115
115
|
appendMessage(mailbox: string, message: string | Uint8Array, flags?: string[]): Promise<number | null>;
|
|
116
116
|
startIdle(onNewMail: (count: number) => void): Promise<() => Promise<void>>;
|
|
117
117
|
getMessageCount(mailbox: string): Promise<number>;
|
|
118
|
-
/**
|
|
119
|
-
|
|
118
|
+
/** Inactivity timeout — how long to wait with NO data before declaring the connection dead.
|
|
119
|
+
* This is NOT a wall-clock timeout. Timer resets every time data arrives from the server.
|
|
120
|
+
* A large FETCH returning data continuously will never timeout. */
|
|
121
|
+
private inactivityTimeout;
|
|
122
|
+
/** Active command timer — reset by handleData on every data arrival */
|
|
123
|
+
private commandTimer;
|
|
120
124
|
private sendCommand;
|
|
121
125
|
private waitForContinuation;
|
|
122
126
|
private waitForTagged;
|
package/imaplib/imap-native.js
CHANGED
|
@@ -290,7 +290,21 @@ export class NativeImapClient {
|
|
|
290
290
|
}
|
|
291
291
|
/** Fetch messages since a UID */
|
|
292
292
|
async fetchSinceUid(sinceUid, options = {}) {
|
|
293
|
-
|
|
293
|
+
// Search for UIDs first, then fetch in chunks (avoids unbounded single command)
|
|
294
|
+
const uids = await this.search(`UID ${sinceUid + 1}:*`);
|
|
295
|
+
if (uids.length === 0)
|
|
296
|
+
return [];
|
|
297
|
+
if (uids.length <= 500) {
|
|
298
|
+
return this.fetchMessages(uids.join(","), options);
|
|
299
|
+
}
|
|
300
|
+
// Chunk large fetches
|
|
301
|
+
const allMessages = [];
|
|
302
|
+
for (let i = 0; i < uids.length; i += 500) {
|
|
303
|
+
const chunk = uids.slice(i, i + 500);
|
|
304
|
+
const msgs = await this.fetchMessages(chunk.join(","), options);
|
|
305
|
+
allMessages.push(...msgs);
|
|
306
|
+
}
|
|
307
|
+
return allMessages;
|
|
294
308
|
}
|
|
295
309
|
/** Fetch messages by date range */
|
|
296
310
|
async fetchByDate(since, before, options = {}) {
|
|
@@ -426,23 +440,34 @@ export class NativeImapClient {
|
|
|
426
440
|
return status.messages || 0;
|
|
427
441
|
}
|
|
428
442
|
// ── Low-level command handling ──
|
|
429
|
-
/**
|
|
430
|
-
|
|
443
|
+
/** Inactivity timeout — how long to wait with NO data before declaring the connection dead.
|
|
444
|
+
* This is NOT a wall-clock timeout. Timer resets every time data arrives from the server.
|
|
445
|
+
* A large FETCH returning data continuously will never timeout. */
|
|
446
|
+
inactivityTimeout = 30000;
|
|
447
|
+
/** Active command timer — reset by handleData on every data arrival */
|
|
448
|
+
commandTimer = null;
|
|
431
449
|
sendCommand(tag, command) {
|
|
432
450
|
return new Promise((resolve, reject) => {
|
|
433
451
|
if (this.verbose && !command.includes("LOGIN") && !command.includes("AUTHENTICATE")) {
|
|
434
452
|
console.log(` [imap] > ${command.trimEnd()}`);
|
|
435
453
|
}
|
|
436
|
-
const
|
|
454
|
+
const onTimeout = () => {
|
|
455
|
+
this.commandTimer = null;
|
|
437
456
|
this.pendingCommand = null;
|
|
438
|
-
|
|
439
|
-
|
|
457
|
+
// Kill the connection — a timed-out connection has stale data in the pipe
|
|
458
|
+
this.transport.close?.();
|
|
459
|
+
reject(new Error(`IMAP inactivity timeout (${this.inactivityTimeout / 1000}s): ${command.split("\r")[0].substring(0, 80)}`));
|
|
460
|
+
};
|
|
461
|
+
this.commandTimer = setTimeout(onTimeout, this.inactivityTimeout);
|
|
440
462
|
this.pendingCommand = {
|
|
441
463
|
tag, responses: [],
|
|
442
|
-
resolve: (responses) => {
|
|
443
|
-
|
|
464
|
+
resolve: (responses) => { if (this.commandTimer)
|
|
465
|
+
clearTimeout(this.commandTimer); this.commandTimer = null; resolve(responses); },
|
|
466
|
+
reject: (err) => { if (this.commandTimer)
|
|
467
|
+
clearTimeout(this.commandTimer); this.commandTimer = null; reject(err); },
|
|
444
468
|
};
|
|
445
|
-
this.transport.write(command).catch((err) => {
|
|
469
|
+
this.transport.write(command).catch((err) => { if (this.commandTimer)
|
|
470
|
+
clearTimeout(this.commandTimer); this.commandTimer = null; reject(err); });
|
|
446
471
|
});
|
|
447
472
|
}
|
|
448
473
|
waitForContinuation(tag) {
|
|
@@ -464,6 +489,19 @@ export class NativeImapClient {
|
|
|
464
489
|
});
|
|
465
490
|
}
|
|
466
491
|
handleData(data) {
|
|
492
|
+
// Reset inactivity timer — data is flowing, connection is alive
|
|
493
|
+
if (this.commandTimer) {
|
|
494
|
+
clearTimeout(this.commandTimer);
|
|
495
|
+
this.commandTimer = setTimeout(() => {
|
|
496
|
+
this.commandTimer = null;
|
|
497
|
+
if (this.pendingCommand) {
|
|
498
|
+
const cmd = this.pendingCommand;
|
|
499
|
+
this.pendingCommand = null;
|
|
500
|
+
this.transport.close?.();
|
|
501
|
+
cmd.reject(new Error(`IMAP inactivity timeout (${this.inactivityTimeout / 1000}s)`));
|
|
502
|
+
}
|
|
503
|
+
}, this.inactivityTimeout);
|
|
504
|
+
}
|
|
467
505
|
this.buffer += data;
|
|
468
506
|
this.processBuffer();
|
|
469
507
|
}
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as net from "node:net";
|
|
6
6
|
import * as tls from "node:tls";
|
|
7
|
+
import { StringDecoder } from "node:string_decoder";
|
|
7
8
|
export class NodeTransport {
|
|
8
9
|
socket = null;
|
|
10
|
+
decoder = new StringDecoder("utf-8");
|
|
9
11
|
dataHandler = null;
|
|
10
12
|
closeHandler = null;
|
|
11
13
|
errorHandler = null;
|
|
@@ -40,7 +42,7 @@ export class NodeTransport {
|
|
|
40
42
|
// Wire up persistent handlers after connect
|
|
41
43
|
this.socket.on("data", (chunk) => {
|
|
42
44
|
if (this.dataHandler)
|
|
43
|
-
this.dataHandler(
|
|
45
|
+
this.dataHandler(this.decoder.write(chunk));
|
|
44
46
|
});
|
|
45
47
|
this.socket.on("close", (hadError) => {
|
|
46
48
|
this._connected = false;
|
|
@@ -64,10 +66,11 @@ export class NodeTransport {
|
|
|
64
66
|
rejectUnauthorized: this.rejectUnauthorized,
|
|
65
67
|
}, () => {
|
|
66
68
|
this.socket = tlsSocket;
|
|
69
|
+
this.decoder = new StringDecoder("utf-8");
|
|
67
70
|
// Re-wire data handler to new socket
|
|
68
71
|
tlsSocket.on("data", (chunk) => {
|
|
69
72
|
if (this.dataHandler)
|
|
70
|
-
this.dataHandler(
|
|
73
|
+
this.dataHandler(this.decoder.write(chunk));
|
|
71
74
|
});
|
|
72
75
|
tlsSocket.on("close", (hadError) => {
|
|
73
76
|
this._connected = false;
|