@bobfrankston/iflow-direct 0.1.10 → 0.1.12
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-compat.d.ts +11 -1
- package/imap-compat.js +17 -3
- package/imap-native.d.ts +3 -0
- package/imap-native.js +64 -7
- package/package.json +1 -1
package/imap-compat.d.ts
CHANGED
|
@@ -49,10 +49,20 @@ export declare class CompatImapClient {
|
|
|
49
49
|
getMessagesCount(mailbox: string): Promise<number>;
|
|
50
50
|
/** Get all UIDs in a mailbox */
|
|
51
51
|
getUids(mailbox: string): Promise<number[]>;
|
|
52
|
-
/** Fetch messages
|
|
52
|
+
/** Fetch messages — supports two calling conventions for compatibility:
|
|
53
|
+
*
|
|
54
|
+
* New: fetchMessages(mailbox, "100:200") — UID range string
|
|
55
|
+
* Old: fetchMessages(mailbox, endSeq, count) — sequence-number range (iflow compat)
|
|
56
|
+
*
|
|
57
|
+
* The old form computes UID range "start:end" from (end - count + 1) : end,
|
|
58
|
+
* matching the legacy iflow/imapflow fetchMessages(mailbox, end, count) API
|
|
59
|
+
* used by the puller. */
|
|
53
60
|
fetchMessages(mailbox: string, uidRange: string, options?: {
|
|
54
61
|
source?: boolean;
|
|
55
62
|
}): Promise<any[]>;
|
|
63
|
+
fetchMessages(mailbox: string, end: number, count: number, options?: {
|
|
64
|
+
source?: boolean;
|
|
65
|
+
}): Promise<any[]>;
|
|
56
66
|
/** Search messages in a mailbox */
|
|
57
67
|
searchMessages(mailbox: string, criteria: any): Promise<number[]>;
|
|
58
68
|
/** Search by header value — returns matching UIDs */
|
package/imap-compat.js
CHANGED
|
@@ -160,11 +160,25 @@ export class CompatImapClient {
|
|
|
160
160
|
await this.native.closeMailbox();
|
|
161
161
|
return uids;
|
|
162
162
|
}
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
async fetchMessages(mailbox, rangeOrEnd, countOrOptions, maybeOptions) {
|
|
164
|
+
let range;
|
|
165
|
+
let options;
|
|
166
|
+
if (typeof rangeOrEnd === "number") {
|
|
167
|
+
// Legacy (iflow) calling convention: (mailbox, endSeq, count, options?)
|
|
168
|
+
const end = rangeOrEnd;
|
|
169
|
+
const count = typeof countOrOptions === "number" ? countOrOptions : 1;
|
|
170
|
+
const start = Math.max(1, end - count + 1);
|
|
171
|
+
range = `${start}:${end}`;
|
|
172
|
+
options = maybeOptions;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// New calling convention: (mailbox, uidRange, options?)
|
|
176
|
+
range = rangeOrEnd;
|
|
177
|
+
options = typeof countOrOptions === "object" ? countOrOptions : undefined;
|
|
178
|
+
}
|
|
165
179
|
await this.ensureConnected();
|
|
166
180
|
await this.native.select(mailbox);
|
|
167
|
-
const msgs = await this.native.fetchMessages(
|
|
181
|
+
const msgs = await this.native.fetchMessages(range, options);
|
|
168
182
|
await this.native.closeMailbox();
|
|
169
183
|
return msgs.map(toCompatMessage);
|
|
170
184
|
}
|
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.
|
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: [] };
|
|
@@ -439,18 +440,74 @@ export class NativeImapClient {
|
|
|
439
440
|
// ── IDLE ──
|
|
440
441
|
async startIdle(onNewMail) {
|
|
441
442
|
this.idleCallback = onNewMail;
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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();
|
|
447
462
|
return async () => {
|
|
463
|
+
stopped = true;
|
|
464
|
+
if (this.idleRefreshTimer) {
|
|
465
|
+
clearTimeout(this.idleRefreshTimer);
|
|
466
|
+
this.idleRefreshTimer = null;
|
|
467
|
+
}
|
|
468
|
+
const tag = this.idleTag;
|
|
448
469
|
this.idleTag = null;
|
|
449
470
|
this.idleCallback = null;
|
|
450
|
-
|
|
451
|
-
|
|
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
|
+
}
|
|
452
481
|
};
|
|
453
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
|
+
}
|
|
454
511
|
// ── Message count (lightweight STATUS) ──
|
|
455
512
|
async getMessageCount(mailbox) {
|
|
456
513
|
const status = await this.getStatus(mailbox);
|