@bobfrankston/iflow-direct 0.1.41 → 0.1.43
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 +36 -0
- package/imap-compat.js +55 -0
- package/imap-native.d.ts +47 -1
- package/imap-native.js +64 -4
- package/imap-protocol.d.ts +20 -2
- package/imap-protocol.js +46 -3
- package/package.json +1 -1
package/imap-compat.d.ts
CHANGED
|
@@ -106,6 +106,42 @@ 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
|
+
/** RFC 5161 ENABLE — activates an IMAP extension for the remainder of
|
|
110
|
+
* the session. QRESYNC must be enabled before any SELECT for
|
|
111
|
+
* `* VANISHED` responses + automatic `MODSEQ` to start flowing.
|
|
112
|
+
* Returns the set of extensions the server confirmed are active. */
|
|
113
|
+
enable(extensions: string[]): Promise<Set<string>>;
|
|
114
|
+
/** Convenience: enable QRESYNC if the server advertises it. Returns
|
|
115
|
+
* true if QRESYNC is active on the session afterwards. Idempotent —
|
|
116
|
+
* safe to call once per connection at connect time. */
|
|
117
|
+
enableQresync(): Promise<boolean>;
|
|
118
|
+
/** Fast resync of a mailbox via RFC 7162 QRESYNC. Caller supplies the
|
|
119
|
+
* `uidValidity` and last-seen `modSeq` from its prior visit; the
|
|
120
|
+
* server replies with `* VANISHED` for UIDs expunged since then and
|
|
121
|
+
* unsolicited `* FETCH` for state changes (flag updates etc.) since
|
|
122
|
+
* then, plus the current `HIGHESTMODSEQ` for the caller to persist
|
|
123
|
+
* as its new watermark.
|
|
124
|
+
*
|
|
125
|
+
* Returns:
|
|
126
|
+
* - `uidValidityChanged`: server's UIDVALIDITY no longer matches; the
|
|
127
|
+
* caller's UID set is stale and MUST be full-resynced.
|
|
128
|
+
* - `vanishedUids`: authoritative deletion list. No client-side diff.
|
|
129
|
+
* - `changedMessages`: state-change FETCH responses since the prior
|
|
130
|
+
* modSeq — each carries `uid`, `flags`, and `modSeq` (and possibly
|
|
131
|
+
* other FETCH items the caller asked for).
|
|
132
|
+
* - `newHighestModSeq`: persist this for the next resync.
|
|
133
|
+
*
|
|
134
|
+
* Pre-requisite: `enableQresync()` returned true once on the session.
|
|
135
|
+
* If the server doesn't support QRESYNC, fall back to the older
|
|
136
|
+
* `fetchMessagesSinceUid` / set-diff path. */
|
|
137
|
+
resyncFolder(mailbox: string, uidValidity: number, modSeq: number): Promise<{
|
|
138
|
+
uidValidityChanged: boolean;
|
|
139
|
+
vanishedUids: number[];
|
|
140
|
+
changedMessages: FetchedMessage[];
|
|
141
|
+
newHighestModSeq: number | undefined;
|
|
142
|
+
exists: number;
|
|
143
|
+
uidNext: number;
|
|
144
|
+
}>;
|
|
109
145
|
/** Watch a mailbox for new messages (IDLE). Optionally engage RFC 5465
|
|
110
146
|
* NOTIFY so the server also pushes STATUS responses for non-selected
|
|
111
147
|
* mailboxes named in `opts.notifySpec` — `opts.onMailboxStatus` fires
|
package/imap-compat.js
CHANGED
|
@@ -279,6 +279,61 @@ export class CompatImapClient {
|
|
|
279
279
|
getCapabilities() {
|
|
280
280
|
return this.native.getCapabilities();
|
|
281
281
|
}
|
|
282
|
+
/** RFC 5161 ENABLE — activates an IMAP extension for the remainder of
|
|
283
|
+
* the session. QRESYNC must be enabled before any SELECT for
|
|
284
|
+
* `* VANISHED` responses + automatic `MODSEQ` to start flowing.
|
|
285
|
+
* Returns the set of extensions the server confirmed are active. */
|
|
286
|
+
async enable(extensions) {
|
|
287
|
+
await this.ensureConnected();
|
|
288
|
+
return this.native.enable(extensions);
|
|
289
|
+
}
|
|
290
|
+
/** Convenience: enable QRESYNC if the server advertises it. Returns
|
|
291
|
+
* true if QRESYNC is active on the session afterwards. Idempotent —
|
|
292
|
+
* safe to call once per connection at connect time. */
|
|
293
|
+
async enableQresync() {
|
|
294
|
+
const caps = this.getCapabilities();
|
|
295
|
+
if (!caps.has("QRESYNC"))
|
|
296
|
+
return false;
|
|
297
|
+
const enabled = await this.enable(["QRESYNC"]);
|
|
298
|
+
return enabled.has("QRESYNC");
|
|
299
|
+
}
|
|
300
|
+
/** Fast resync of a mailbox via RFC 7162 QRESYNC. Caller supplies the
|
|
301
|
+
* `uidValidity` and last-seen `modSeq` from its prior visit; the
|
|
302
|
+
* server replies with `* VANISHED` for UIDs expunged since then and
|
|
303
|
+
* unsolicited `* FETCH` for state changes (flag updates etc.) since
|
|
304
|
+
* then, plus the current `HIGHESTMODSEQ` for the caller to persist
|
|
305
|
+
* as its new watermark.
|
|
306
|
+
*
|
|
307
|
+
* Returns:
|
|
308
|
+
* - `uidValidityChanged`: server's UIDVALIDITY no longer matches; the
|
|
309
|
+
* caller's UID set is stale and MUST be full-resynced.
|
|
310
|
+
* - `vanishedUids`: authoritative deletion list. No client-side diff.
|
|
311
|
+
* - `changedMessages`: state-change FETCH responses since the prior
|
|
312
|
+
* modSeq — each carries `uid`, `flags`, and `modSeq` (and possibly
|
|
313
|
+
* other FETCH items the caller asked for).
|
|
314
|
+
* - `newHighestModSeq`: persist this for the next resync.
|
|
315
|
+
*
|
|
316
|
+
* Pre-requisite: `enableQresync()` returned true once on the session.
|
|
317
|
+
* If the server doesn't support QRESYNC, fall back to the older
|
|
318
|
+
* `fetchMessagesSinceUid` / set-diff path. */
|
|
319
|
+
async resyncFolder(mailbox, uidValidity, modSeq) {
|
|
320
|
+
await this.ensureConnected();
|
|
321
|
+
const result = await this.native.select(mailbox, { uidValidity, modSeq });
|
|
322
|
+
// Untagged FETCH responses emitted during a QRESYNC SELECT are
|
|
323
|
+
// pre-parsed by the native layer into NativeFetchedMessage objects;
|
|
324
|
+
// expose them as FetchedMessage to compat callers. (The native
|
|
325
|
+
// select() doesn't currently surface these — that wiring is in
|
|
326
|
+
// imap-native.ts:select via the FETCH-during-select case.)
|
|
327
|
+
const changedMessages = result.changedMessages?.map((m) => new FetchedMessage(m)) || [];
|
|
328
|
+
return {
|
|
329
|
+
uidValidityChanged: result.uidValidityChanged,
|
|
330
|
+
vanishedUids: result.vanishedUids,
|
|
331
|
+
changedMessages,
|
|
332
|
+
newHighestModSeq: result.highestModSeq,
|
|
333
|
+
exists: result.exists,
|
|
334
|
+
uidNext: result.uidNext,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
282
337
|
/** Watch a mailbox for new messages (IDLE). Optionally engage RFC 5465
|
|
283
338
|
* NOTIFY so the server also pushes STATUS responses for non-selected
|
|
284
339
|
* 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;
|
|
@@ -43,6 +48,33 @@ export interface MailboxInfo {
|
|
|
43
48
|
uidValidity: number;
|
|
44
49
|
flags: string[];
|
|
45
50
|
permanentFlags: string[];
|
|
51
|
+
/** Per RFC 7162. Present when the server is CONDSTORE/QRESYNC-capable
|
|
52
|
+
* and includes [HIGHESTMODSEQ N] in the SELECT-OK response (or after
|
|
53
|
+
* ENABLE QRESYNC). Use as the `since-modseq` argument to subsequent
|
|
54
|
+
* resyncs to ask the server "what changed since this point?" */
|
|
55
|
+
highestModSeq?: number;
|
|
56
|
+
}
|
|
57
|
+
/** Parameters for `SELECT mailbox (QRESYNC (uidvalidity modseq))` per RFC 7162.
|
|
58
|
+
* Caller persists `(uidValidity, modSeq)` per folder; after ENABLE QRESYNC
|
|
59
|
+
* has been issued once on the session, the next SELECT replies with
|
|
60
|
+
* `* VANISHED (EARLIER) <uids>` for every message the server expunged since
|
|
61
|
+
* `modSeq`, plus `* FETCH` for every message whose state changed since then.
|
|
62
|
+
* No client-side UID diffing, no tombstones — the server tells you the
|
|
63
|
+
* authoritative deletions. */
|
|
64
|
+
export interface QresyncParams {
|
|
65
|
+
uidValidity: number;
|
|
66
|
+
modSeq: number;
|
|
67
|
+
knownUids?: string;
|
|
68
|
+
}
|
|
69
|
+
/** Result of a QRESYNC-enabled SELECT. The caller drains `vanishedUids`
|
|
70
|
+
* (delete from local store), processes any FETCH responses (upsert state
|
|
71
|
+
* changes), and saves the new `highestModSeq` for the next resync. */
|
|
72
|
+
export interface SelectResult extends MailboxInfo {
|
|
73
|
+
vanishedUids: number[];
|
|
74
|
+
/** True if the server's UIDVALIDITY changed since the caller's last
|
|
75
|
+
* visit — in that case knownUids/modSeq are invalid and the caller
|
|
76
|
+
* must full-resync from scratch. */
|
|
77
|
+
uidValidityChanged: boolean;
|
|
46
78
|
}
|
|
47
79
|
export declare class NativeImapClient {
|
|
48
80
|
private transport;
|
|
@@ -116,7 +148,21 @@ export declare class NativeImapClient {
|
|
|
116
148
|
getCapabilities(): Set<string>;
|
|
117
149
|
private parseCapabilities;
|
|
118
150
|
logout(): Promise<void>;
|
|
119
|
-
|
|
151
|
+
/** SELECT a mailbox. Optional QRESYNC params trigger RFC 7162 fast resync —
|
|
152
|
+
* on a re-visit the server replies with `* VANISHED (EARLIER) <uids>` for
|
|
153
|
+
* every UID expunged since the caller's last `modSeq`, plus `* FETCH`
|
|
154
|
+
* for every state change since then. The caller must have called
|
|
155
|
+
* `enable(["QRESYNC"])` once on the session for this to take effect.
|
|
156
|
+
* Returns a `SelectResult` rather than a bare `MailboxInfo` so VANISHED
|
|
157
|
+
* is delivered to the caller. The result is upcast-compatible with
|
|
158
|
+
* `MailboxInfo` for code paths that don't care about VANISHED. */
|
|
159
|
+
select(mailbox: string, qresync?: QresyncParams): Promise<SelectResult>;
|
|
160
|
+
/** RFC 5161 ENABLE. Activates an extension for the rest of the session.
|
|
161
|
+
* QRESYNC requires this once before any SELECT for VANISHED responses
|
|
162
|
+
* to be emitted. Capability-gate at the caller — emitting ENABLE for
|
|
163
|
+
* an unadvertised extension is a no-op on compliant servers but spams
|
|
164
|
+
* the response stream. */
|
|
165
|
+
enable(extensions: string[]): Promise<Set<string>>;
|
|
120
166
|
examine(mailbox: string): Promise<MailboxInfo>;
|
|
121
167
|
/** Close the currently selected mailbox */
|
|
122
168
|
closeMailbox(): Promise<void>;
|
package/imap-native.js
CHANGED
|
@@ -251,14 +251,26 @@ export class NativeImapClient {
|
|
|
251
251
|
this._connected = false;
|
|
252
252
|
}
|
|
253
253
|
// ── Mailbox Operations ──
|
|
254
|
-
|
|
254
|
+
/** SELECT a mailbox. Optional QRESYNC params trigger RFC 7162 fast resync —
|
|
255
|
+
* on a re-visit the server replies with `* VANISHED (EARLIER) <uids>` for
|
|
256
|
+
* every UID expunged since the caller's last `modSeq`, plus `* FETCH`
|
|
257
|
+
* for every state change since then. The caller must have called
|
|
258
|
+
* `enable(["QRESYNC"])` once on the session for this to take effect.
|
|
259
|
+
* Returns a `SelectResult` rather than a bare `MailboxInfo` so VANISHED
|
|
260
|
+
* is delivered to the caller. The result is upcast-compatible with
|
|
261
|
+
* `MailboxInfo` for code paths that don't care about VANISHED. */
|
|
262
|
+
async select(mailbox, qresync) {
|
|
255
263
|
const tag = proto.nextTag();
|
|
256
|
-
const responses = await this.sendCommand(tag, proto.selectCommand(tag, mailbox));
|
|
264
|
+
const responses = await this.sendCommand(tag, proto.selectCommand(tag, mailbox, qresync));
|
|
257
265
|
const tagged = responses.find(r => r.tag === tag);
|
|
258
266
|
if (!tagged || tagged.type !== "OK") {
|
|
259
267
|
throw new Error(`SELECT ${mailbox} failed: ${tagged?.text || "unknown"}`);
|
|
260
268
|
}
|
|
261
|
-
//
|
|
269
|
+
// Reset transient fields each SELECT so a stale value from a previous
|
|
270
|
+
// mailbox doesn't leak into the new one.
|
|
271
|
+
this.mailboxInfo.highestModSeq = undefined;
|
|
272
|
+
const vanishedUids = [];
|
|
273
|
+
const priorUidValidity = qresync?.uidValidity;
|
|
262
274
|
for (const r of responses) {
|
|
263
275
|
if (r.tag !== "*")
|
|
264
276
|
continue;
|
|
@@ -281,10 +293,50 @@ export class NativeImapClient {
|
|
|
281
293
|
const permMatch = r.text.match(/PERMANENTFLAGS\s+\(([^)]*)\)/i);
|
|
282
294
|
if (permMatch)
|
|
283
295
|
this.mailboxInfo.permanentFlags = permMatch[1].split(/\s+/).filter(Boolean);
|
|
296
|
+
const modSeqMatch = r.text.match(/HIGHESTMODSEQ\s+(\d+)/i);
|
|
297
|
+
if (modSeqMatch)
|
|
298
|
+
this.mailboxInfo.highestModSeq = parseInt(modSeqMatch[1]);
|
|
299
|
+
}
|
|
300
|
+
else if (r.type === "VANISHED") {
|
|
301
|
+
// `* VANISHED (EARLIER) 1:5,8,12` or `* VANISHED 1:5,8,12` —
|
|
302
|
+
// strip the optional `(EARLIER)` modifier and expand the UID
|
|
303
|
+
// set. EARLIER means "deleted before your last sync", which
|
|
304
|
+
// is the QRESYNC case; without it, the message just vanished
|
|
305
|
+
// mid-session (post-EXPUNGE on another client).
|
|
306
|
+
const text = r.text.replace(/^\(EARLIER\)\s*/i, "").trim();
|
|
307
|
+
for (const uid of proto.parseUidSet(text))
|
|
308
|
+
vanishedUids.push(uid);
|
|
284
309
|
}
|
|
285
310
|
}
|
|
286
311
|
this.selectedMailbox = mailbox;
|
|
287
|
-
|
|
312
|
+
const uidValidityChanged = priorUidValidity !== undefined
|
|
313
|
+
&& this.mailboxInfo.uidValidity !== priorUidValidity;
|
|
314
|
+
return {
|
|
315
|
+
...this.mailboxInfo,
|
|
316
|
+
vanishedUids,
|
|
317
|
+
uidValidityChanged,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
/** RFC 5161 ENABLE. Activates an extension for the rest of the session.
|
|
321
|
+
* QRESYNC requires this once before any SELECT for VANISHED responses
|
|
322
|
+
* to be emitted. Capability-gate at the caller — emitting ENABLE for
|
|
323
|
+
* an unadvertised extension is a no-op on compliant servers but spams
|
|
324
|
+
* the response stream. */
|
|
325
|
+
async enable(extensions) {
|
|
326
|
+
const tag = proto.nextTag();
|
|
327
|
+
const responses = await this.sendCommand(tag, proto.enableCommand(tag, extensions));
|
|
328
|
+
const tagged = responses.find(r => r.tag === tag);
|
|
329
|
+
if (!tagged || tagged.type !== "OK") {
|
|
330
|
+
throw new Error(`ENABLE ${extensions.join(" ")} failed: ${tagged?.text || "unknown"}`);
|
|
331
|
+
}
|
|
332
|
+
const enabled = new Set();
|
|
333
|
+
for (const r of responses) {
|
|
334
|
+
if (r.tag === "*" && r.type === "ENABLED") {
|
|
335
|
+
for (const ext of r.text.split(/\s+/).filter(Boolean))
|
|
336
|
+
enabled.add(ext.toUpperCase());
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return enabled;
|
|
288
340
|
}
|
|
289
341
|
async examine(mailbox) {
|
|
290
342
|
const tag = proto.nextTag();
|
|
@@ -1284,6 +1336,14 @@ export class NativeImapClient {
|
|
|
1284
1336
|
const sizeMatch = r.text.match(/RFC822\.SIZE\s+(\d+)/);
|
|
1285
1337
|
if (sizeMatch)
|
|
1286
1338
|
msg.size = parseInt(sizeMatch[1]);
|
|
1339
|
+
// Extract MODSEQ (RFC 7162). Format: `MODSEQ (12345)`. Server
|
|
1340
|
+
// emits this when the session has ENABLE QRESYNC/CONDSTORE
|
|
1341
|
+
// active OR when the client explicitly asks for MODSEQ in the
|
|
1342
|
+
// FETCH command. Track on every FETCH so the caller can update
|
|
1343
|
+
// its `last_modseq` watermark.
|
|
1344
|
+
const modSeqMatch = r.text.match(/MODSEQ\s+\((\d+)\)/);
|
|
1345
|
+
if (modSeqMatch)
|
|
1346
|
+
msg.modSeq = parseInt(modSeqMatch[1]);
|
|
1287
1347
|
// Extract INTERNALDATE
|
|
1288
1348
|
const dateMatch = r.text.match(/INTERNALDATE\s+"([^"]+)"/);
|
|
1289
1349
|
if (dateMatch)
|
package/imap-protocol.d.ts
CHANGED
|
@@ -71,8 +71,26 @@ export declare function loginCommand(tag: string, user: string, pass: string): s
|
|
|
71
71
|
export declare function xoauth2Command(tag: string, user: string, token: string): string;
|
|
72
72
|
/** Build LIST command */
|
|
73
73
|
export declare function listCommand(tag: string, ref?: string, pattern?: string): string;
|
|
74
|
-
/** Build SELECT command
|
|
75
|
-
|
|
74
|
+
/** Build SELECT command. Optional `qresync` triggers RFC 7162 fast resync:
|
|
75
|
+
* the server emits `* VANISHED (EARLIER) <uid-set>` for every UID expunged
|
|
76
|
+
* since `modSeq` and `* FETCH` for every state change since then, in lieu
|
|
77
|
+
* of the caller having to diff UID sets. Capability-gated by caller;
|
|
78
|
+
* requires `ENABLE QRESYNC` already issued on the session. */
|
|
79
|
+
export declare function selectCommand(tag: string, mailbox: string, qresync?: {
|
|
80
|
+
uidValidity: number;
|
|
81
|
+
modSeq: number;
|
|
82
|
+
knownUids?: string;
|
|
83
|
+
}): string;
|
|
84
|
+
/** Build ENABLE command per RFC 5161. Common extensions to enable: QRESYNC,
|
|
85
|
+
* CONDSTORE, UTF8=ACCEPT. Must be issued before any SELECT for the
|
|
86
|
+
* extension to take effect on the session. */
|
|
87
|
+
export declare function enableCommand(tag: string, extensions: string[]): string;
|
|
88
|
+
/** Expand an RFC 3501 UID set (e.g. `"1:5,8,12,20:*"`) into a flat number
|
|
89
|
+
* list. Does NOT resolve `*` — caller passes a known upper bound only
|
|
90
|
+
* when it has one; here `*` is treated as a sentinel and skipped so the
|
|
91
|
+
* call site can decide what to do (typically: ignore — VANISHED never
|
|
92
|
+
* emits `*` because that would be open-ended). */
|
|
93
|
+
export declare function parseUidSet(set: string): number[];
|
|
76
94
|
/** Build EXAMINE command (read-only SELECT) */
|
|
77
95
|
export declare function examineCommand(tag: string, mailbox: string): string;
|
|
78
96
|
/** Build STATUS command */
|
package/imap-protocol.js
CHANGED
|
@@ -32,9 +32,52 @@ export function xoauth2Command(tag, user, token) {
|
|
|
32
32
|
export function listCommand(tag, ref = '""', pattern = '"*"') {
|
|
33
33
|
return buildCommand(tag, `LIST ${ref} ${pattern}`);
|
|
34
34
|
}
|
|
35
|
-
/** Build SELECT command
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
/** Build SELECT command. Optional `qresync` triggers RFC 7162 fast resync:
|
|
36
|
+
* the server emits `* VANISHED (EARLIER) <uid-set>` for every UID expunged
|
|
37
|
+
* since `modSeq` and `* FETCH` for every state change since then, in lieu
|
|
38
|
+
* of the caller having to diff UID sets. Capability-gated by caller;
|
|
39
|
+
* requires `ENABLE QRESYNC` already issued on the session. */
|
|
40
|
+
export function selectCommand(tag, mailbox, qresync) {
|
|
41
|
+
if (!qresync)
|
|
42
|
+
return buildCommand(tag, `SELECT ${quoteMailbox(mailbox)}`);
|
|
43
|
+
let params = `${qresync.uidValidity} ${qresync.modSeq}`;
|
|
44
|
+
if (qresync.knownUids)
|
|
45
|
+
params += ` ${qresync.knownUids}`;
|
|
46
|
+
return buildCommand(tag, `SELECT ${quoteMailbox(mailbox)} (QRESYNC (${params}))`);
|
|
47
|
+
}
|
|
48
|
+
/** Build ENABLE command per RFC 5161. Common extensions to enable: QRESYNC,
|
|
49
|
+
* CONDSTORE, UTF8=ACCEPT. Must be issued before any SELECT for the
|
|
50
|
+
* extension to take effect on the session. */
|
|
51
|
+
export function enableCommand(tag, extensions) {
|
|
52
|
+
return buildCommand(tag, `ENABLE ${extensions.join(" ")}`);
|
|
53
|
+
}
|
|
54
|
+
/** Expand an RFC 3501 UID set (e.g. `"1:5,8,12,20:*"`) into a flat number
|
|
55
|
+
* list. Does NOT resolve `*` — caller passes a known upper bound only
|
|
56
|
+
* when it has one; here `*` is treated as a sentinel and skipped so the
|
|
57
|
+
* call site can decide what to do (typically: ignore — VANISHED never
|
|
58
|
+
* emits `*` because that would be open-ended). */
|
|
59
|
+
export function parseUidSet(set) {
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const part of set.split(",")) {
|
|
62
|
+
const trimmed = part.trim();
|
|
63
|
+
if (!trimmed)
|
|
64
|
+
continue;
|
|
65
|
+
const range = trimmed.split(":");
|
|
66
|
+
if (range.length === 1) {
|
|
67
|
+
const n = parseInt(range[0], 10);
|
|
68
|
+
if (Number.isFinite(n))
|
|
69
|
+
out.push(n);
|
|
70
|
+
}
|
|
71
|
+
else if (range.length === 2) {
|
|
72
|
+
const lo = parseInt(range[0], 10);
|
|
73
|
+
const hi = range[1] === "*" ? lo : parseInt(range[1], 10);
|
|
74
|
+
if (!Number.isFinite(lo) || !Number.isFinite(hi) || hi < lo)
|
|
75
|
+
continue;
|
|
76
|
+
for (let u = lo; u <= hi; u++)
|
|
77
|
+
out.push(u);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
38
81
|
}
|
|
39
82
|
/** Build EXAMINE command (read-only SELECT) */
|
|
40
83
|
export function examineCommand(tag, mailbox) {
|