@bobfrankston/iflow-direct 0.1.42 → 0.1.44
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/fetched-message.d.ts +1 -0
- package/fetched-message.js +1 -0
- package/imap-compat.d.ts +46 -0
- package/imap-compat.js +58 -0
- package/imap-native.d.ts +20 -1
- package/imap-native.js +27 -0
- package/package.json +1 -1
package/fetched-message.d.ts
CHANGED
package/fetched-message.js
CHANGED
package/imap-compat.d.ts
CHANGED
|
@@ -106,6 +106,52 @@ export declare class CompatImapClient {
|
|
|
106
106
|
/** Cached CAPABILITY set parsed at connect/login. Callers gate
|
|
107
107
|
* optional features (NOTIFY, QRESYNC, MOVE, ...) on this. */
|
|
108
108
|
getCapabilities(): Set<string>;
|
|
109
|
+
/** Snapshot of the most recently SELECTed mailbox's info. Callers use this
|
|
110
|
+
* to read `highestModSeq` after any operation that did a SELECT — useful
|
|
111
|
+
* for seeding the QRESYNC modSeq watermark on the very first sync of a
|
|
112
|
+
* folder (before the QRESYNC path is eligible). */
|
|
113
|
+
getCurrentMailboxInfo(): {
|
|
114
|
+
uidValidity: number;
|
|
115
|
+
uidNext: number;
|
|
116
|
+
exists: number;
|
|
117
|
+
highestModSeq?: number;
|
|
118
|
+
};
|
|
119
|
+
/** RFC 5161 ENABLE — activates an IMAP extension for the remainder of
|
|
120
|
+
* the session. QRESYNC must be enabled before any SELECT for
|
|
121
|
+
* `* VANISHED` responses + automatic `MODSEQ` to start flowing.
|
|
122
|
+
* Returns the set of extensions the server confirmed are active. */
|
|
123
|
+
enable(extensions: string[]): Promise<Set<string>>;
|
|
124
|
+
/** Convenience: enable QRESYNC if the server advertises it. Returns
|
|
125
|
+
* true if QRESYNC is active on the session afterwards. Idempotent —
|
|
126
|
+
* safe to call once per connection at connect time. */
|
|
127
|
+
enableQresync(): Promise<boolean>;
|
|
128
|
+
/** Fast resync of a mailbox via RFC 7162 QRESYNC. Caller supplies the
|
|
129
|
+
* `uidValidity` and last-seen `modSeq` from its prior visit; the
|
|
130
|
+
* server replies with `* VANISHED` for UIDs expunged since then and
|
|
131
|
+
* unsolicited `* FETCH` for state changes (flag updates etc.) since
|
|
132
|
+
* then, plus the current `HIGHESTMODSEQ` for the caller to persist
|
|
133
|
+
* as its new watermark.
|
|
134
|
+
*
|
|
135
|
+
* Returns:
|
|
136
|
+
* - `uidValidityChanged`: server's UIDVALIDITY no longer matches; the
|
|
137
|
+
* caller's UID set is stale and MUST be full-resynced.
|
|
138
|
+
* - `vanishedUids`: authoritative deletion list. No client-side diff.
|
|
139
|
+
* - `changedMessages`: state-change FETCH responses since the prior
|
|
140
|
+
* modSeq — each carries `uid`, `flags`, and `modSeq` (and possibly
|
|
141
|
+
* other FETCH items the caller asked for).
|
|
142
|
+
* - `newHighestModSeq`: persist this for the next resync.
|
|
143
|
+
*
|
|
144
|
+
* Pre-requisite: `enableQresync()` returned true once on the session.
|
|
145
|
+
* If the server doesn't support QRESYNC, fall back to the older
|
|
146
|
+
* `fetchMessagesSinceUid` / set-diff path. */
|
|
147
|
+
resyncFolder(mailbox: string, uidValidity: number, modSeq: number): Promise<{
|
|
148
|
+
uidValidityChanged: boolean;
|
|
149
|
+
vanishedUids: number[];
|
|
150
|
+
changedMessages: FetchedMessage[];
|
|
151
|
+
newHighestModSeq: number | undefined;
|
|
152
|
+
exists: number;
|
|
153
|
+
uidNext: number;
|
|
154
|
+
}>;
|
|
109
155
|
/** Watch a mailbox for new messages (IDLE). Optionally engage RFC 5465
|
|
110
156
|
* NOTIFY so the server also pushes STATUS responses for non-selected
|
|
111
157
|
* mailboxes named in `opts.notifySpec` — `opts.onMailboxStatus` fires
|
package/imap-compat.js
CHANGED
|
@@ -279,6 +279,64 @@ export class CompatImapClient {
|
|
|
279
279
|
getCapabilities() {
|
|
280
280
|
return this.native.getCapabilities();
|
|
281
281
|
}
|
|
282
|
+
/** Snapshot of the most recently SELECTed mailbox's info. Callers use this
|
|
283
|
+
* to read `highestModSeq` after any operation that did a SELECT — useful
|
|
284
|
+
* for seeding the QRESYNC modSeq watermark on the very first sync of a
|
|
285
|
+
* folder (before the QRESYNC path is eligible). */
|
|
286
|
+
getCurrentMailboxInfo() {
|
|
287
|
+
const info = this.native.getMailboxInfo?.();
|
|
288
|
+
return info || { uidValidity: 0, uidNext: 0, exists: 0 };
|
|
289
|
+
}
|
|
290
|
+
/** RFC 5161 ENABLE — activates an IMAP extension for the remainder of
|
|
291
|
+
* the session. QRESYNC must be enabled before any SELECT for
|
|
292
|
+
* `* VANISHED` responses + automatic `MODSEQ` to start flowing.
|
|
293
|
+
* Returns the set of extensions the server confirmed are active. */
|
|
294
|
+
async enable(extensions) {
|
|
295
|
+
await this.ensureConnected();
|
|
296
|
+
return this.native.enable(extensions);
|
|
297
|
+
}
|
|
298
|
+
/** Convenience: enable QRESYNC if the server advertises it. Returns
|
|
299
|
+
* true if QRESYNC is active on the session afterwards. Idempotent —
|
|
300
|
+
* safe to call once per connection at connect time. */
|
|
301
|
+
async enableQresync() {
|
|
302
|
+
const caps = this.getCapabilities();
|
|
303
|
+
if (!caps.has("QRESYNC"))
|
|
304
|
+
return false;
|
|
305
|
+
const enabled = await this.enable(["QRESYNC"]);
|
|
306
|
+
return enabled.has("QRESYNC");
|
|
307
|
+
}
|
|
308
|
+
/** Fast resync of a mailbox via RFC 7162 QRESYNC. Caller supplies the
|
|
309
|
+
* `uidValidity` and last-seen `modSeq` from its prior visit; the
|
|
310
|
+
* server replies with `* VANISHED` for UIDs expunged since then and
|
|
311
|
+
* unsolicited `* FETCH` for state changes (flag updates etc.) since
|
|
312
|
+
* then, plus the current `HIGHESTMODSEQ` for the caller to persist
|
|
313
|
+
* as its new watermark.
|
|
314
|
+
*
|
|
315
|
+
* Returns:
|
|
316
|
+
* - `uidValidityChanged`: server's UIDVALIDITY no longer matches; the
|
|
317
|
+
* caller's UID set is stale and MUST be full-resynced.
|
|
318
|
+
* - `vanishedUids`: authoritative deletion list. No client-side diff.
|
|
319
|
+
* - `changedMessages`: state-change FETCH responses since the prior
|
|
320
|
+
* modSeq — each carries `uid`, `flags`, and `modSeq` (and possibly
|
|
321
|
+
* other FETCH items the caller asked for).
|
|
322
|
+
* - `newHighestModSeq`: persist this for the next resync.
|
|
323
|
+
*
|
|
324
|
+
* Pre-requisite: `enableQresync()` returned true once on the session.
|
|
325
|
+
* If the server doesn't support QRESYNC, fall back to the older
|
|
326
|
+
* `fetchMessagesSinceUid` / set-diff path. */
|
|
327
|
+
async resyncFolder(mailbox, uidValidity, modSeq) {
|
|
328
|
+
await this.ensureConnected();
|
|
329
|
+
const result = await this.native.select(mailbox, { uidValidity, modSeq });
|
|
330
|
+
const changedMessages = result.changedMessages.map(m => new FetchedMessage(m));
|
|
331
|
+
return {
|
|
332
|
+
uidValidityChanged: result.uidValidityChanged,
|
|
333
|
+
vanishedUids: result.vanishedUids,
|
|
334
|
+
changedMessages,
|
|
335
|
+
newHighestModSeq: result.highestModSeq,
|
|
336
|
+
exists: result.exists,
|
|
337
|
+
uidNext: result.uidNext,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
282
340
|
/** Watch a mailbox for new messages (IDLE). Optionally engage RFC 5465
|
|
283
341
|
* NOTIFY so the server also pushes STATUS responses for non-selected
|
|
284
342
|
* mailboxes named in `opts.notifySpec` — `opts.onMailboxStatus` fires
|
package/imap-native.d.ts
CHANGED
|
@@ -30,6 +30,11 @@ export interface NativeFetchedMessage {
|
|
|
30
30
|
flagged: boolean;
|
|
31
31
|
answered: boolean;
|
|
32
32
|
draft: boolean;
|
|
33
|
+
/** Per-message modification sequence (RFC 7162). Present when the
|
|
34
|
+
* server is CONDSTORE/QRESYNC-capable and the client either has
|
|
35
|
+
* ENABLE QRESYNC active or asked for MODSEQ explicitly. Used by the
|
|
36
|
+
* caller to track its own `last_modseq` watermark per folder. */
|
|
37
|
+
modSeq?: number;
|
|
33
38
|
}
|
|
34
39
|
export interface NativeFolder {
|
|
35
40
|
path: string;
|
|
@@ -62,10 +67,15 @@ export interface QresyncParams {
|
|
|
62
67
|
knownUids?: string;
|
|
63
68
|
}
|
|
64
69
|
/** Result of a QRESYNC-enabled SELECT. The caller drains `vanishedUids`
|
|
65
|
-
* (delete from local store), processes
|
|
70
|
+
* (delete from local store), processes `changedMessages` (upsert state
|
|
66
71
|
* changes), and saves the new `highestModSeq` for the next resync. */
|
|
67
72
|
export interface SelectResult extends MailboxInfo {
|
|
68
73
|
vanishedUids: number[];
|
|
74
|
+
/** Untagged `* FETCH` responses the server emitted during the QRESYNC
|
|
75
|
+
* SELECT — typically flag/MODSEQ changes for messages whose state
|
|
76
|
+
* shifted between the caller's previous modSeq and now. Empty for
|
|
77
|
+
* non-QRESYNC SELECTs. */
|
|
78
|
+
changedMessages: NativeFetchedMessage[];
|
|
69
79
|
/** True if the server's UIDVALIDITY changed since the caller's last
|
|
70
80
|
* visit — in that case knownUids/modSeq are invalid and the caller
|
|
71
81
|
* must full-resync from scratch. */
|
|
@@ -141,6 +151,15 @@ export declare class NativeImapClient {
|
|
|
141
151
|
* without re-issuing CAPABILITY. Returns a defensive copy so callers
|
|
142
152
|
* can't mutate internal state. */
|
|
143
153
|
getCapabilities(): Set<string>;
|
|
154
|
+
/** Snapshot of the currently-SELECTed mailbox's info. Lets the compat
|
|
155
|
+
* client read `highestModSeq` etc. after any select-implying operation
|
|
156
|
+
* (fetchMessagesSinceUid, etc.) without re-issuing SELECT. */
|
|
157
|
+
getMailboxInfo(): {
|
|
158
|
+
uidValidity: number;
|
|
159
|
+
uidNext: number;
|
|
160
|
+
exists: number;
|
|
161
|
+
highestModSeq?: number;
|
|
162
|
+
};
|
|
144
163
|
private parseCapabilities;
|
|
145
164
|
logout(): Promise<void>;
|
|
146
165
|
/** SELECT a mailbox. Optional QRESYNC params trigger RFC 7162 fast resync —
|
package/imap-native.js
CHANGED
|
@@ -236,6 +236,17 @@ export class NativeImapClient {
|
|
|
236
236
|
getCapabilities() {
|
|
237
237
|
return new Set(this.capabilities);
|
|
238
238
|
}
|
|
239
|
+
/** Snapshot of the currently-SELECTed mailbox's info. Lets the compat
|
|
240
|
+
* client read `highestModSeq` etc. after any select-implying operation
|
|
241
|
+
* (fetchMessagesSinceUid, etc.) without re-issuing SELECT. */
|
|
242
|
+
getMailboxInfo() {
|
|
243
|
+
return {
|
|
244
|
+
uidValidity: this.mailboxInfo.uidValidity,
|
|
245
|
+
uidNext: this.mailboxInfo.uidNext,
|
|
246
|
+
exists: this.mailboxInfo.exists,
|
|
247
|
+
highestModSeq: this.mailboxInfo.highestModSeq,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
239
250
|
parseCapabilities(text) {
|
|
240
251
|
const caps = text.replace(/^CAPABILITY\s*/i, "").split(/\s+/);
|
|
241
252
|
this.capabilities.clear();
|
|
@@ -271,6 +282,10 @@ export class NativeImapClient {
|
|
|
271
282
|
this.mailboxInfo.highestModSeq = undefined;
|
|
272
283
|
const vanishedUids = [];
|
|
273
284
|
const priorUidValidity = qresync?.uidValidity;
|
|
285
|
+
// Untagged FETCH responses emitted during a QRESYNC SELECT carry
|
|
286
|
+
// state-change deltas the server is volunteering up-front; parse
|
|
287
|
+
// them through the same path as normal FETCH so the shape matches.
|
|
288
|
+
const fetchResponses = responses.filter(r => r.tag === "*" && r.type === "FETCH");
|
|
274
289
|
for (const r of responses) {
|
|
275
290
|
if (r.tag !== "*")
|
|
276
291
|
continue;
|
|
@@ -311,9 +326,13 @@ export class NativeImapClient {
|
|
|
311
326
|
this.selectedMailbox = mailbox;
|
|
312
327
|
const uidValidityChanged = priorUidValidity !== undefined
|
|
313
328
|
&& this.mailboxInfo.uidValidity !== priorUidValidity;
|
|
329
|
+
const changedMessages = fetchResponses.length > 0
|
|
330
|
+
? this.parseFetchResponses(fetchResponses)
|
|
331
|
+
: [];
|
|
314
332
|
return {
|
|
315
333
|
...this.mailboxInfo,
|
|
316
334
|
vanishedUids,
|
|
335
|
+
changedMessages,
|
|
317
336
|
uidValidityChanged,
|
|
318
337
|
};
|
|
319
338
|
}
|
|
@@ -1336,6 +1355,14 @@ export class NativeImapClient {
|
|
|
1336
1355
|
const sizeMatch = r.text.match(/RFC822\.SIZE\s+(\d+)/);
|
|
1337
1356
|
if (sizeMatch)
|
|
1338
1357
|
msg.size = parseInt(sizeMatch[1]);
|
|
1358
|
+
// Extract MODSEQ (RFC 7162). Format: `MODSEQ (12345)`. Server
|
|
1359
|
+
// emits this when the session has ENABLE QRESYNC/CONDSTORE
|
|
1360
|
+
// active OR when the client explicitly asks for MODSEQ in the
|
|
1361
|
+
// FETCH command. Track on every FETCH so the caller can update
|
|
1362
|
+
// its `last_modseq` watermark.
|
|
1363
|
+
const modSeqMatch = r.text.match(/MODSEQ\s+\((\d+)\)/);
|
|
1364
|
+
if (modSeqMatch)
|
|
1365
|
+
msg.modSeq = parseInt(modSeqMatch[1]);
|
|
1339
1366
|
// Extract INTERNALDATE
|
|
1340
1367
|
const dateMatch = r.text.match(/INTERNALDATE\s+"([^"]+)"/);
|
|
1341
1368
|
if (dateMatch)
|