@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 +4 -0
- package/gmail.js +6 -0
- package/imap-native.d.ts +8 -3
- package/imap-native.js +78 -17
- package/package.json +1 -1
- package/types.d.ts +2 -0
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
|
-
|
|
126
|
-
|
|
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 <=
|
|
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 =
|
|
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 <
|
|
315
|
-
chunkSize = Math.min(chunkSize * 4,
|
|
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 =
|
|
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 <
|
|
341
|
-
chunkSize = Math.min(chunkSize * 4,
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
449
|
-
|
|
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
|
-
|
|
466
|
-
|
|
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