@bobfrankston/iflow 1.0.53 → 1.0.55
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-compat.d.ts +2 -2
- package/imaplib/imap-compat.js +4 -3
- package/imaplib/imap-native.d.ts +12 -5
- package/imaplib/imap-native.js +69 -17
- package/package.json +1 -1
package/imaplib/imap-compat.d.ts
CHANGED
|
@@ -37,10 +37,10 @@ export declare class CompatImapClient {
|
|
|
37
37
|
fetchMessagesSinceUid(mailbox: string, sinceUid: number, options?: {
|
|
38
38
|
source?: boolean;
|
|
39
39
|
}): Promise<any[]>;
|
|
40
|
-
/** Fetch messages by date range */
|
|
40
|
+
/** Fetch messages by date range. Optional onChunk callback for incremental processing. */
|
|
41
41
|
fetchMessageByDate(mailbox: string, start: Date, end?: Date, options?: {
|
|
42
42
|
source?: boolean;
|
|
43
|
-
}): Promise<any[]>;
|
|
43
|
+
}, onChunk?: (msgs: any[]) => void): Promise<any[]>;
|
|
44
44
|
/** Fetch a single message by UID */
|
|
45
45
|
fetchMessageByUid(mailbox: string, uid: number, options?: {
|
|
46
46
|
source?: boolean;
|
package/imaplib/imap-compat.js
CHANGED
|
@@ -102,11 +102,12 @@ export class CompatImapClient {
|
|
|
102
102
|
await this.native.closeMailbox();
|
|
103
103
|
return msgs.map(toCompatMessage);
|
|
104
104
|
}
|
|
105
|
-
/** Fetch messages by date range */
|
|
106
|
-
async fetchMessageByDate(mailbox, start, end, options) {
|
|
105
|
+
/** Fetch messages by date range. Optional onChunk callback for incremental processing. */
|
|
106
|
+
async fetchMessageByDate(mailbox, start, end, options, onChunk) {
|
|
107
107
|
await this.ensureConnected();
|
|
108
108
|
await this.native.select(mailbox);
|
|
109
|
-
const
|
|
109
|
+
const chunkCb = onChunk ? (raw) => onChunk(raw.map(toCompatMessage)) : undefined;
|
|
110
|
+
const msgs = await this.native.fetchByDate(start, end, options, chunkCb);
|
|
110
111
|
await this.native.closeMailbox();
|
|
111
112
|
return msgs.map(toCompatMessage);
|
|
112
113
|
}
|
package/imaplib/imap-native.d.ts
CHANGED
|
@@ -86,11 +86,11 @@ export declare class NativeImapClient {
|
|
|
86
86
|
/** Fetch messages since a UID */
|
|
87
87
|
fetchSinceUid(sinceUid: number, options?: {
|
|
88
88
|
source?: boolean;
|
|
89
|
-
}): Promise<NativeFetchedMessage[]>;
|
|
90
|
-
/** Fetch messages by date range */
|
|
89
|
+
}, onChunk?: (msgs: NativeFetchedMessage[]) => void): Promise<NativeFetchedMessage[]>;
|
|
90
|
+
/** Fetch messages by date range. Optional onChunk callback receives each batch as it arrives. */
|
|
91
91
|
fetchByDate(since: Date, before?: Date, options?: {
|
|
92
92
|
source?: boolean;
|
|
93
|
-
}): Promise<NativeFetchedMessage[]>;
|
|
93
|
+
}, onChunk?: (msgs: NativeFetchedMessage[]) => void): Promise<NativeFetchedMessage[]>;
|
|
94
94
|
/** Fetch a single message by UID */
|
|
95
95
|
fetchMessage(uid: number, options?: {
|
|
96
96
|
source?: boolean;
|
|
@@ -115,8 +115,15 @@ 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
|
+
/** Fetch chunk sizes — start small for quick first paint, ramp up for throughput */
|
|
123
|
+
private static INITIAL_CHUNK_SIZE;
|
|
124
|
+
private static MAX_CHUNK_SIZE;
|
|
125
|
+
/** Active command timer — reset by handleData on every data arrival */
|
|
126
|
+
private commandTimer;
|
|
120
127
|
private sendCommand;
|
|
121
128
|
private waitForContinuation;
|
|
122
129
|
private waitForTagged;
|
package/imaplib/imap-native.js
CHANGED
|
@@ -289,12 +289,33 @@ export class NativeImapClient {
|
|
|
289
289
|
return this.parseFetchResponses(responses);
|
|
290
290
|
}
|
|
291
291
|
/** Fetch messages since a UID */
|
|
292
|
-
async fetchSinceUid(sinceUid, options = {}) {
|
|
293
|
-
|
|
292
|
+
async fetchSinceUid(sinceUid, options = {}, onChunk) {
|
|
293
|
+
const uids = await this.search(`UID ${sinceUid + 1}:*`);
|
|
294
|
+
if (uids.length === 0)
|
|
295
|
+
return [];
|
|
296
|
+
console.log(` [fetch] ${uids.length} UIDs since ${sinceUid}`);
|
|
297
|
+
if (uids.length <= NativeImapClient.INITIAL_CHUNK_SIZE) {
|
|
298
|
+
const msgs = await this.fetchMessages(uids.join(","), options);
|
|
299
|
+
if (onChunk)
|
|
300
|
+
onChunk(msgs);
|
|
301
|
+
return msgs;
|
|
302
|
+
}
|
|
303
|
+
const allMessages = [];
|
|
304
|
+
let chunkSize = NativeImapClient.INITIAL_CHUNK_SIZE;
|
|
305
|
+
for (let i = 0; i < uids.length; i += chunkSize) {
|
|
306
|
+
const chunk = uids.slice(i, i + chunkSize);
|
|
307
|
+
const msgs = await this.fetchMessages(chunk.join(","), options);
|
|
308
|
+
allMessages.push(...msgs);
|
|
309
|
+
console.log(` [fetch] ${allMessages.length}/${uids.length} (chunk of ${chunk.length})`);
|
|
310
|
+
if (onChunk)
|
|
311
|
+
onChunk(msgs);
|
|
312
|
+
if (chunkSize < NativeImapClient.MAX_CHUNK_SIZE)
|
|
313
|
+
chunkSize = Math.min(chunkSize * 4, NativeImapClient.MAX_CHUNK_SIZE);
|
|
314
|
+
}
|
|
315
|
+
return allMessages;
|
|
294
316
|
}
|
|
295
|
-
/** Fetch messages by date range */
|
|
296
|
-
async fetchByDate(since, before, options = {}) {
|
|
297
|
-
// First search for UIDs in date range, then fetch
|
|
317
|
+
/** Fetch messages by date range. Optional onChunk callback receives each batch as it arrives. */
|
|
318
|
+
async fetchByDate(since, before, options = {}, onChunk) {
|
|
298
319
|
const criteria = proto.buildSearchCriteria({
|
|
299
320
|
since,
|
|
300
321
|
before: before || undefined,
|
|
@@ -302,14 +323,18 @@ export class NativeImapClient {
|
|
|
302
323
|
const uids = await this.search(criteria);
|
|
303
324
|
if (uids.length === 0)
|
|
304
325
|
return [];
|
|
305
|
-
|
|
306
|
-
const chunkSize = 500;
|
|
326
|
+
console.log(` [fetch] ${uids.length} UIDs to fetch`);
|
|
307
327
|
const allMessages = [];
|
|
328
|
+
let chunkSize = NativeImapClient.INITIAL_CHUNK_SIZE;
|
|
308
329
|
for (let i = 0; i < uids.length; i += chunkSize) {
|
|
309
330
|
const chunk = uids.slice(i, i + chunkSize);
|
|
310
|
-
const
|
|
311
|
-
const msgs = await this.fetchMessages(range, options);
|
|
331
|
+
const msgs = await this.fetchMessages(chunk.join(","), options);
|
|
312
332
|
allMessages.push(...msgs);
|
|
333
|
+
console.log(` [fetch] ${allMessages.length}/${uids.length} (chunk of ${chunk.length})`);
|
|
334
|
+
if (onChunk)
|
|
335
|
+
onChunk(msgs);
|
|
336
|
+
if (chunkSize < NativeImapClient.MAX_CHUNK_SIZE)
|
|
337
|
+
chunkSize = Math.min(chunkSize * 4, NativeImapClient.MAX_CHUNK_SIZE);
|
|
313
338
|
}
|
|
314
339
|
return allMessages;
|
|
315
340
|
}
|
|
@@ -426,23 +451,37 @@ export class NativeImapClient {
|
|
|
426
451
|
return status.messages || 0;
|
|
427
452
|
}
|
|
428
453
|
// ── Low-level command handling ──
|
|
429
|
-
/**
|
|
430
|
-
|
|
454
|
+
/** Inactivity timeout — how long to wait with NO data before declaring the connection dead.
|
|
455
|
+
* This is NOT a wall-clock timeout. Timer resets every time data arrives from the server.
|
|
456
|
+
* A large FETCH returning data continuously will never timeout. */
|
|
457
|
+
inactivityTimeout = 30000;
|
|
458
|
+
/** Fetch chunk sizes — start small for quick first paint, ramp up for throughput */
|
|
459
|
+
static INITIAL_CHUNK_SIZE = 25;
|
|
460
|
+
static MAX_CHUNK_SIZE = 500;
|
|
461
|
+
/** Active command timer — reset by handleData on every data arrival */
|
|
462
|
+
commandTimer = null;
|
|
431
463
|
sendCommand(tag, command) {
|
|
432
464
|
return new Promise((resolve, reject) => {
|
|
433
465
|
if (this.verbose && !command.includes("LOGIN") && !command.includes("AUTHENTICATE")) {
|
|
434
466
|
console.log(` [imap] > ${command.trimEnd()}`);
|
|
435
467
|
}
|
|
436
|
-
const
|
|
468
|
+
const onTimeout = () => {
|
|
469
|
+
this.commandTimer = null;
|
|
437
470
|
this.pendingCommand = null;
|
|
438
|
-
|
|
439
|
-
|
|
471
|
+
// Kill the connection — a timed-out connection has stale data in the pipe
|
|
472
|
+
this.transport.close?.();
|
|
473
|
+
reject(new Error(`IMAP inactivity timeout (${this.inactivityTimeout / 1000}s): ${command.split("\r")[0].substring(0, 80)}`));
|
|
474
|
+
};
|
|
475
|
+
this.commandTimer = setTimeout(onTimeout, this.inactivityTimeout);
|
|
440
476
|
this.pendingCommand = {
|
|
441
477
|
tag, responses: [],
|
|
442
|
-
resolve: (responses) => {
|
|
443
|
-
|
|
478
|
+
resolve: (responses) => { if (this.commandTimer)
|
|
479
|
+
clearTimeout(this.commandTimer); this.commandTimer = null; resolve(responses); },
|
|
480
|
+
reject: (err) => { if (this.commandTimer)
|
|
481
|
+
clearTimeout(this.commandTimer); this.commandTimer = null; reject(err); },
|
|
444
482
|
};
|
|
445
|
-
this.transport.write(command).catch((err) => {
|
|
483
|
+
this.transport.write(command).catch((err) => { if (this.commandTimer)
|
|
484
|
+
clearTimeout(this.commandTimer); this.commandTimer = null; reject(err); });
|
|
446
485
|
});
|
|
447
486
|
}
|
|
448
487
|
waitForContinuation(tag) {
|
|
@@ -464,6 +503,19 @@ export class NativeImapClient {
|
|
|
464
503
|
});
|
|
465
504
|
}
|
|
466
505
|
handleData(data) {
|
|
506
|
+
// Reset inactivity timer — data is flowing, connection is alive
|
|
507
|
+
if (this.commandTimer) {
|
|
508
|
+
clearTimeout(this.commandTimer);
|
|
509
|
+
this.commandTimer = setTimeout(() => {
|
|
510
|
+
this.commandTimer = null;
|
|
511
|
+
if (this.pendingCommand) {
|
|
512
|
+
const cmd = this.pendingCommand;
|
|
513
|
+
this.pendingCommand = null;
|
|
514
|
+
this.transport.close?.();
|
|
515
|
+
cmd.reject(new Error(`IMAP inactivity timeout (${this.inactivityTimeout / 1000}s)`));
|
|
516
|
+
}
|
|
517
|
+
}, this.inactivityTimeout);
|
|
518
|
+
}
|
|
467
519
|
this.buffer += data;
|
|
468
520
|
this.processBuffer();
|
|
469
521
|
}
|