imapflow 2.0.6 → 2.0.8
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/CHANGELOG.md +16 -0
- package/dist/cjs/commands/append.js +12 -12
- package/dist/cjs/commands/authenticate.d.ts +3 -8
- package/dist/cjs/commands/close.js +2 -1
- package/dist/cjs/commands/copy.js +4 -4
- package/dist/cjs/commands/create.js +2 -3
- package/dist/cjs/commands/delete.js +4 -4
- package/dist/cjs/commands/expunge.js +8 -5
- package/dist/cjs/commands/fetch.js +12 -10
- package/dist/cjs/commands/idle.js +6 -2
- package/dist/cjs/commands/list.js +2 -2
- package/dist/cjs/commands/move.js +11 -6
- package/dist/cjs/commands/namespace.js +1 -1
- package/dist/cjs/commands/quota.js +10 -9
- package/dist/cjs/commands/rename.js +4 -4
- package/dist/cjs/commands/search.js +7 -8
- package/dist/cjs/commands/select.js +5 -6
- package/dist/cjs/commands/status.js +11 -11
- package/dist/cjs/commands/store.js +5 -5
- package/dist/cjs/commands/subscribe.js +2 -17
- package/dist/cjs/commands/subscription.d.ts +10 -0
- package/dist/cjs/commands/subscription.js +29 -0
- package/dist/cjs/commands/unsubscribe.js +2 -17
- package/dist/cjs/download.d.ts +22 -0
- package/dist/cjs/download.js +588 -0
- package/dist/cjs/errors.d.ts +2 -0
- package/dist/cjs/handler/imap-compiler.js +1 -1
- package/dist/cjs/handler/imap-stream.js +4 -4
- package/dist/cjs/handler/parser-instance.js +2 -2
- package/dist/cjs/handler/token-parser.js +1 -1
- package/dist/cjs/imap-flow.d.ts +16 -7
- package/dist/cjs/imap-flow.js +247 -730
- package/dist/cjs/jp-decoder.js +1 -1
- package/dist/cjs/package-info.d.ts +1 -1
- package/dist/cjs/package-info.js +3 -3
- package/dist/cjs/search-compiler.js +5 -12
- package/dist/cjs/tools.d.ts +51 -10
- package/dist/cjs/tools.js +83 -15
- package/dist/cjs/types.d.ts +30 -16
- package/dist/esm/commands/append.js +13 -13
- package/dist/esm/commands/authenticate.d.ts +3 -8
- package/dist/esm/commands/close.js +2 -1
- package/dist/esm/commands/copy.js +5 -5
- package/dist/esm/commands/create.js +3 -4
- package/dist/esm/commands/delete.js +5 -5
- package/dist/esm/commands/expunge.js +9 -6
- package/dist/esm/commands/fetch.js +13 -11
- package/dist/esm/commands/idle.js +7 -3
- package/dist/esm/commands/list.js +2 -2
- package/dist/esm/commands/move.js +12 -7
- package/dist/esm/commands/namespace.js +2 -2
- package/dist/esm/commands/quota.js +11 -10
- package/dist/esm/commands/rename.js +5 -5
- package/dist/esm/commands/search.js +8 -9
- package/dist/esm/commands/select.js +6 -7
- package/dist/esm/commands/status.js +12 -12
- package/dist/esm/commands/store.js +6 -6
- package/dist/esm/commands/subscribe.js +2 -17
- package/dist/esm/commands/subscription.d.ts +10 -0
- package/dist/esm/commands/subscription.js +26 -0
- package/dist/esm/commands/unsubscribe.js +2 -17
- package/dist/esm/download.d.ts +22 -0
- package/dist/esm/download.js +581 -0
- package/dist/esm/errors.d.ts +2 -0
- package/dist/esm/handler/imap-compiler.js +1 -1
- package/dist/esm/handler/imap-stream.js +4 -4
- package/dist/esm/handler/parser-instance.js +2 -2
- package/dist/esm/handler/token-parser.js +1 -1
- package/dist/esm/imap-flow.d.ts +16 -7
- package/dist/esm/imap-flow.js +248 -731
- package/dist/esm/jp-decoder.js +1 -1
- package/dist/esm/package-info.d.ts +1 -1
- package/dist/esm/package-info.js +3 -3
- package/dist/esm/search-compiler.js +5 -12
- package/dist/esm/tools.d.ts +51 -10
- package/dist/esm/tools.js +78 -15
- package/dist/esm/types.d.ts +30 -16
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatMessageResponse, isRev2Active } from '../tools.js';
|
|
1
|
+
import { formatMessageResponse, isRev2Active, getSelectedMailbox } from '../tools.js';
|
|
2
2
|
/**
|
|
3
3
|
* Fetches emails from the server.
|
|
4
4
|
*
|
|
@@ -9,23 +9,23 @@ import { formatMessageResponse, isRev2Active } from '../tools.js';
|
|
|
9
9
|
* @returns Object with message count and list, or undefined if not in SELECTED state
|
|
10
10
|
*/
|
|
11
11
|
export default async function fetch(connection, range, query, options) {
|
|
12
|
-
|
|
12
|
+
let mailbox = getSelectedMailbox(connection);
|
|
13
|
+
if (!mailbox || !range) {
|
|
13
14
|
// nothing to do here
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
17
|
options = options || {};
|
|
17
|
-
let mailbox = connection.mailbox;
|
|
18
18
|
// Use BINARY extension for fetching if supported and requested, otherwise fall back to BODY.
|
|
19
19
|
// RFC 9051 folds the FETCH side of the BINARY extension into base IMAP4rev2, so an active
|
|
20
20
|
// rev2 session can use it even without the BINARY capability token (the APPEND side is NOT
|
|
21
21
|
// folded in and stays gated on the token in append.ts)
|
|
22
22
|
const canUseBinary = connection.capabilities.has('BINARY') || isRev2Active(connection);
|
|
23
23
|
const commandKey = canUseBinary && options.binary && !connection.disableBinary ? 'BINARY' : 'BODY';
|
|
24
|
-
// Retry logic for ETHROTTLE errors (server rate limiting) with exponential backoff
|
|
25
|
-
|
|
24
|
+
// Retry logic for ETHROTTLE errors (server rate limiting) with exponential backoff.
|
|
25
|
+
// Every pass returns or throws: the last throttled attempt throws instead of retrying.
|
|
26
26
|
const maxRetries = 4;
|
|
27
27
|
const baseDelay = 1000; // Start with 1 second delay
|
|
28
|
-
|
|
28
|
+
for (let retryCount = 0;; retryCount++) {
|
|
29
29
|
let messages = {
|
|
30
30
|
count: 0,
|
|
31
31
|
list: []
|
|
@@ -202,15 +202,18 @@ export default async function fetch(connection, range, query, options) {
|
|
|
202
202
|
return messages;
|
|
203
203
|
}
|
|
204
204
|
catch (err) {
|
|
205
|
-
|
|
205
|
+
// The last throttled attempt falls through and throws, so running out of retries
|
|
206
|
+
// is never mistaken for an empty result
|
|
207
|
+
if (err.code === 'ETHROTTLE' && retryCount < maxRetries - 1) {
|
|
206
208
|
// Server returned a throttle error (rate limiting). Retry with exponential backoff.
|
|
207
|
-
// Delay doubles each retry: 1s, 2s, 4s
|
|
209
|
+
// Delay doubles each retry: 1s, 2s, 4s.
|
|
208
210
|
// If server provides a throttleReset hint, use that if longer.
|
|
209
211
|
const backoffDelay = Math.min(baseDelay * Math.pow(2, retryCount), 30000); // Cap at 30 seconds
|
|
210
212
|
// Use throttle reset time if provided and longer than backoff. The hint is
|
|
211
213
|
// server-controlled, so the wait goes through connection.throttleWait(), which caps
|
|
212
|
-
// it and keeps the timer tracked and abortable.
|
|
213
|
-
|
|
214
|
+
// it and keeps the timer tracked and abortable. The connection already waited
|
|
215
|
+
// part of the back-off before rejecting (throttleWaited), so only the rest is left.
|
|
216
|
+
const delay = Math.max(err.throttleReset || 0, backoffDelay) - (err.throttleWaited || 0);
|
|
214
217
|
connection.log.warn({
|
|
215
218
|
msg: 'Retrying throttled request with exponential backoff',
|
|
216
219
|
cid: connection.id,
|
|
@@ -226,7 +229,6 @@ export default async function fetch(connection, range, query, options) {
|
|
|
226
229
|
if (aborted) {
|
|
227
230
|
throw connection.createNoConnectionError(connection.byeReason, { rejectedFrom: 'throttleAbort', command: 'FETCH' });
|
|
228
231
|
}
|
|
229
|
-
retryCount++;
|
|
230
232
|
continue;
|
|
231
233
|
}
|
|
232
234
|
connection.log.warn({ err, cid: connection.id });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { guardedPromise, hasCapability, logConnectionError, restampConnectionError, unrefTimer, clearTimer } from '../tools.js';
|
|
1
|
+
import { guardedPromise, hasCapability, logConnectionError, restampConnectionError, unrefTimer, clearTimer, getSelectedMailbox } from '../tools.js';
|
|
2
2
|
const NOOP_INTERVAL = 2 * 60 * 1000;
|
|
3
3
|
/**
|
|
4
4
|
* Marks the connection as idling on behalf of one session and returns a release function.
|
|
@@ -121,7 +121,11 @@ async function runIdle(connection) {
|
|
|
121
121
|
}
|
|
122
122
|
catch (err) {
|
|
123
123
|
logConnectionError(connection, 'IDLE session failed', err);
|
|
124
|
-
|
|
124
|
+
// A tagged NO or BAD only means the server refused IDLE; the connection is still usable,
|
|
125
|
+
// so the waiters are released by the finally block below and their own commands run.
|
|
126
|
+
// Anything else (close, lost socket, parser failure) fails the waiters too.
|
|
127
|
+
let refusedByServer = ['NO', 'BAD'].includes(err.responseStatus);
|
|
128
|
+
if (preCheckWaitQueue.length && !refusedByServer) {
|
|
125
129
|
// One error for the whole queue: every waiter failed at the same site, for the same
|
|
126
130
|
// reason. Built inside the guard so a teardown with nothing queued - the common case -
|
|
127
131
|
// does not pay for an Error and its stack capture.
|
|
@@ -237,7 +241,7 @@ async function runPollingFallback(connection, maxIdleTime) {
|
|
|
237
241
|
return;
|
|
238
242
|
}
|
|
239
243
|
// The transport or the mailbox may be gone by the time the timer fires
|
|
240
|
-
if (!connection.socket || connection.socket.destroyed ||
|
|
244
|
+
if (!connection.socket || connection.socket.destroyed || !getSelectedMailbox(connection)) {
|
|
241
245
|
return cancel();
|
|
242
246
|
}
|
|
243
247
|
pollOnce(connection, session)
|
|
@@ -181,7 +181,7 @@ export default async function list(connection, reference, mailbox, options) {
|
|
|
181
181
|
entry.path = entry.path.slice(1);
|
|
182
182
|
}
|
|
183
183
|
// Build parent path hierarchy for tree construction and sorting
|
|
184
|
-
entry.parentPath = entry.delimiter && entry.path ? entry.path.
|
|
184
|
+
entry.parentPath = entry.delimiter && entry.path ? entry.path.substring(0, entry.path.lastIndexOf(entry.delimiter)) : '';
|
|
185
185
|
entry.parent = entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path];
|
|
186
186
|
entry.name = entry.parent.pop();
|
|
187
187
|
// Try to detect special-use from server flags or well-known names
|
|
@@ -392,7 +392,7 @@ export default async function list(connection, reference, mailbox, options) {
|
|
|
392
392
|
if (entry.delimiter && entry.path.charAt(0) === entry.delimiter) {
|
|
393
393
|
entry.path = entry.path.slice(1);
|
|
394
394
|
}
|
|
395
|
-
entry.parentPath = entry.delimiter && entry.path ? entry.path.
|
|
395
|
+
entry.parentPath = entry.delimiter && entry.path ? entry.path.substring(0, entry.path.lastIndexOf(entry.delimiter)) : '';
|
|
396
396
|
entry.parent = entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path];
|
|
397
397
|
entry.name = entry.parent.pop();
|
|
398
398
|
// Merge LSUB data into existing LIST entry if found
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizePath, encodePath,
|
|
1
|
+
import { normalizePath, encodePath, hasCapability, reportCommandError, getSelectedMailbox } from '../tools.js';
|
|
2
2
|
import { parseCopyUid } from './copyuid-parser.js';
|
|
3
3
|
/**
|
|
4
4
|
* Moves messages from the current mailbox to another mailbox.
|
|
@@ -11,7 +11,8 @@ import { parseCopyUid } from './copyuid-parser.js';
|
|
|
11
11
|
* @returns Move result with UID mapping if available, false on failure, or undefined if preconditions not met
|
|
12
12
|
*/
|
|
13
13
|
export default async function move(connection, range, destination, options) {
|
|
14
|
-
|
|
14
|
+
let mailbox = getSelectedMailbox(connection);
|
|
15
|
+
if (!mailbox || !range || !destination) {
|
|
15
16
|
// nothing to do here
|
|
16
17
|
return;
|
|
17
18
|
}
|
|
@@ -21,13 +22,18 @@ export default async function move(connection, range, destination, options) {
|
|
|
21
22
|
{ type: 'SEQUENCE', value: range },
|
|
22
23
|
{ type: 'ATOM', value: encodePath(connection, destination) }
|
|
23
24
|
];
|
|
24
|
-
let map = { path:
|
|
25
|
+
let map = { path: mailbox.path, destination };
|
|
25
26
|
// Fallback for servers without the MOVE extension (RFC 6851):
|
|
26
27
|
// emulate MOVE using COPY + flag as \Deleted + EXPUNGE.
|
|
27
28
|
if (!hasCapability(connection, 'MOVE')) {
|
|
28
29
|
let result = await connection.messageCopy(range, destination, options);
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
if (!result) {
|
|
31
|
+
// The source must stay untouched when the copy failed, otherwise the messages are lost
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
let deleted = await connection.messageDelete(range, Object.assign({ silent: true }, options));
|
|
35
|
+
// Messages that were copied but not removed from the source mean the move did not complete
|
|
36
|
+
return deleted ? result : false;
|
|
31
37
|
}
|
|
32
38
|
let response;
|
|
33
39
|
try {
|
|
@@ -45,8 +51,7 @@ export default async function move(connection, range, destination, options) {
|
|
|
45
51
|
return map;
|
|
46
52
|
}
|
|
47
53
|
catch (err) {
|
|
48
|
-
await
|
|
49
|
-
connection.log.warn({ err, cid: connection.id });
|
|
54
|
+
await reportCommandError(connection, err);
|
|
50
55
|
return false;
|
|
51
56
|
}
|
|
52
57
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { hasCapability, getStringList } from '../tools.js';
|
|
1
|
+
import { hasCapability, getStringList, isAuthenticatedState } from '../tools.js';
|
|
2
2
|
/**
|
|
3
3
|
* Requests NAMESPACE info from the server.
|
|
4
4
|
*
|
|
@@ -6,7 +6,7 @@ import { hasCapability, getStringList } from '../tools.js';
|
|
|
6
6
|
* @returns The primary personal namespace, or an error object on failure
|
|
7
7
|
*/
|
|
8
8
|
export default async function namespace(connection) {
|
|
9
|
-
if (!
|
|
9
|
+
if (!isAuthenticatedState(connection)) {
|
|
10
10
|
// nothing to do here
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { encodePath, normalizePath,
|
|
1
|
+
import { encodePath, normalizePath, parseUintValue, isUnsafeKey, isAuthenticatedState, reportCommandError } from '../tools.js';
|
|
2
2
|
/**
|
|
3
3
|
* Requests quota information for a mailbox.
|
|
4
4
|
*
|
|
@@ -7,7 +7,7 @@ import { encodePath, normalizePath, enhanceCommandError, parseUintValue, isUnsaf
|
|
|
7
7
|
* @returns Quota information object, false if QUOTA not supported or on failure, or undefined if preconditions not met
|
|
8
8
|
*/
|
|
9
9
|
export default async function quota(connection, path) {
|
|
10
|
-
if (!
|
|
10
|
+
if (!isAuthenticatedState(connection) || !path) {
|
|
11
11
|
// nothing to do here
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
@@ -47,19 +47,21 @@ export default async function quota(connection, path) {
|
|
|
47
47
|
if (isUnsafeKey(key) || key === 'path' || key === 'quotaroot') {
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
let resource = map[key];
|
|
51
|
+
if (typeof resource !== 'object') {
|
|
52
|
+
resource = {};
|
|
53
|
+
map[key] = resource;
|
|
52
54
|
}
|
|
53
55
|
// Storage quota is reported in KB by IMAP; convert to bytes for consistency
|
|
54
56
|
const multiplier = key === 'storage' ? 1024 : 1;
|
|
55
57
|
if (position === 1) {
|
|
56
|
-
|
|
58
|
+
resource.usage = value * multiplier;
|
|
57
59
|
}
|
|
58
60
|
else if (position === 2) {
|
|
59
|
-
|
|
61
|
+
resource.limit = value * multiplier;
|
|
60
62
|
// Calculate usage percentage for convenient display
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
+
if (resource.limit) {
|
|
64
|
+
resource.status = Math.round(((resource.usage || 0) / resource.limit) * 100) + '%';
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
});
|
|
@@ -107,8 +109,7 @@ export default async function quota(connection, path) {
|
|
|
107
109
|
return map;
|
|
108
110
|
}
|
|
109
111
|
catch (err) {
|
|
110
|
-
await
|
|
111
|
-
connection.log.warn({ err, cid: connection.id });
|
|
112
|
+
await reportCommandError(connection, err);
|
|
112
113
|
return false;
|
|
113
114
|
}
|
|
114
115
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { encodePath, normalizePath,
|
|
1
|
+
import { encodePath, normalizePath, isAuthenticatedState, reportCommandError, getSelectedMailbox } from '../tools.js';
|
|
2
2
|
/**
|
|
3
3
|
* Renames an existing mailbox.
|
|
4
4
|
*
|
|
@@ -9,7 +9,7 @@ import { encodePath, normalizePath, enhanceCommandError } from '../tools.js';
|
|
|
9
9
|
* @throws If the RENAME command fails
|
|
10
10
|
*/
|
|
11
11
|
export default async function rename(connection, path, newPath) {
|
|
12
|
-
if (!
|
|
12
|
+
if (!isAuthenticatedState(connection)) {
|
|
13
13
|
// nothing to do here
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
@@ -19,7 +19,8 @@ export default async function rename(connection, path, newPath) {
|
|
|
19
19
|
newPath = normalizePath(connection, newPath);
|
|
20
20
|
// Must close/deselect the mailbox before renaming if it's currently selected,
|
|
21
21
|
// as IMAP servers will not rename an active mailbox.
|
|
22
|
-
|
|
22
|
+
let selected = getSelectedMailbox(connection);
|
|
23
|
+
if (selected && selected.path === path) {
|
|
23
24
|
await connection.run('CLOSE');
|
|
24
25
|
}
|
|
25
26
|
let response;
|
|
@@ -36,8 +37,7 @@ export default async function rename(connection, path, newPath) {
|
|
|
36
37
|
return map;
|
|
37
38
|
}
|
|
38
39
|
catch (err) {
|
|
39
|
-
await
|
|
40
|
-
connection.log.warn({ err, cid: connection.id });
|
|
40
|
+
await reportCommandError(connection, err);
|
|
41
41
|
throw err;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hasCapability, isValidSequenceValue, EXPANDED_RANGE_LIMIT, reportCommandError, getSelectedMailbox } from '../tools.js';
|
|
2
2
|
import { searchCompiler } from '../search-compiler.js';
|
|
3
3
|
import { parseEsearchResponse } from './esearch-parser.js';
|
|
4
4
|
/**
|
|
@@ -31,7 +31,8 @@ const stripEsearchPrefix = (attrs) => {
|
|
|
31
31
|
* When server lacks ESEARCH, falls back to plain SEARCH and returns number[].
|
|
32
32
|
*/
|
|
33
33
|
export default async function search(connection, query, options) {
|
|
34
|
-
|
|
34
|
+
const mailbox = getSelectedMailbox(connection);
|
|
35
|
+
if (!mailbox) {
|
|
35
36
|
// nothing to do here
|
|
36
37
|
return false;
|
|
37
38
|
}
|
|
@@ -89,8 +90,7 @@ export default async function search(connection, query, options) {
|
|
|
89
90
|
return esearchResult;
|
|
90
91
|
}
|
|
91
92
|
catch (err) {
|
|
92
|
-
await
|
|
93
|
-
connection.log.warn({ err, cid: connection.id });
|
|
93
|
+
await reportCommandError(connection, err);
|
|
94
94
|
return false;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
@@ -160,7 +160,7 @@ export default async function search(connection, query, options) {
|
|
|
160
160
|
// for message sequence numbers, while server-sent UID sets may
|
|
161
161
|
// not contain '*' at all (RFC 9051 section 4.1.1), so UID
|
|
162
162
|
// parts with '*' are dropped
|
|
163
|
-
let existsCount = () =>
|
|
163
|
+
let existsCount = () => mailbox.exists || 0;
|
|
164
164
|
// The mailbox EXISTS count is itself server-supplied and can be
|
|
165
165
|
// absurdly large, so the budget is additionally capped at the same
|
|
166
166
|
// absolute ceiling expandRange() uses - a hostile server cannot
|
|
@@ -185,8 +185,8 @@ export default async function search(connection, query, options) {
|
|
|
185
185
|
results.add(value);
|
|
186
186
|
continue;
|
|
187
187
|
}
|
|
188
|
-
let first = resolveId(part.
|
|
189
|
-
let second = resolveId(part.
|
|
188
|
+
let first = resolveId(part.substring(0, colon));
|
|
189
|
+
let second = resolveId(part.slice(colon + 1));
|
|
190
190
|
if (!isValidSequenceValue(first) || !isValidSequenceValue(second)) {
|
|
191
191
|
discarded = true;
|
|
192
192
|
continue;
|
|
@@ -216,8 +216,7 @@ export default async function search(connection, query, options) {
|
|
|
216
216
|
return Array.from(results).sort((a, b) => a - b);
|
|
217
217
|
}
|
|
218
218
|
catch (err) {
|
|
219
|
-
await
|
|
220
|
-
connection.log.warn({ err, cid: connection.id });
|
|
219
|
+
await reportCommandError(connection, err);
|
|
221
220
|
return false;
|
|
222
221
|
}
|
|
223
222
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { encodePath, normalizePath,
|
|
1
|
+
import { encodePath, normalizePath, parseBigIntValue, parseUintValue, getStringList, MAX_UINT32_DIGITS, emitSafe, isAuthenticatedState, reportCommandError } from '../tools.js';
|
|
2
2
|
// Response codes carrying a value that SELECT/EXAMINE may write to the mailbox object, keyed by
|
|
3
3
|
// the lowercased code, mapped to the fixed public property name and the parser for the value.
|
|
4
4
|
// Every parser returns false for a value it cannot use, and the field is then left unset.
|
|
@@ -56,7 +56,7 @@ const VALUED_RESPONSE_CODES = Object.assign(Object.create(null), {
|
|
|
56
56
|
* @throws If the SELECT/EXAMINE command fails
|
|
57
57
|
*/
|
|
58
58
|
export default async function select(connection, pathInput, options) {
|
|
59
|
-
if (!
|
|
59
|
+
if (!isAuthenticatedState(connection)) {
|
|
60
60
|
// nothing to do here
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
@@ -212,7 +212,7 @@ export default async function select(connection, pathInput, options) {
|
|
|
212
212
|
// Emit mailboxClose if we're switching from a different mailbox.
|
|
213
213
|
// Re-selecting the same mailbox (e.g., for resync) does not trigger close/open.
|
|
214
214
|
if (currentMailbox && currentMailbox.path !== path) {
|
|
215
|
-
connection
|
|
215
|
+
emitSafe(connection, 'mailboxClose', currentMailbox);
|
|
216
216
|
}
|
|
217
217
|
connection.mailbox = map;
|
|
218
218
|
// Save the SELECT command for potential re-use (e.g., NOOP fallback polling
|
|
@@ -220,13 +220,13 @@ export default async function select(connection, pathInput, options) {
|
|
|
220
220
|
connection.currentSelectCommand = selectCommand;
|
|
221
221
|
connection.state = connection.states.SELECTED;
|
|
222
222
|
if (!currentMailbox || currentMailbox.path !== path) {
|
|
223
|
-
connection
|
|
223
|
+
emitSafe(connection, 'mailboxOpen', connection.mailbox);
|
|
224
224
|
}
|
|
225
225
|
response.next();
|
|
226
226
|
return map;
|
|
227
227
|
}
|
|
228
228
|
catch (err) {
|
|
229
|
-
await
|
|
229
|
+
await reportCommandError(connection, err);
|
|
230
230
|
// If SELECT/EXAMINE fails while a mailbox was already selected, we must
|
|
231
231
|
// reset to AUTHENTICATED state since the server has implicitly deselected
|
|
232
232
|
// the previous mailbox on failure (RFC 3501 Section 6.3.1).
|
|
@@ -236,10 +236,9 @@ export default async function select(connection, pathInput, options) {
|
|
|
236
236
|
connection.currentSelectCommand = false;
|
|
237
237
|
connection.state = connection.states.AUTHENTICATED;
|
|
238
238
|
if (currentMailbox) {
|
|
239
|
-
connection
|
|
239
|
+
emitSafe(connection, 'mailboxClose', currentMailbox);
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
-
connection.log.warn({ err, cid: connection.id });
|
|
243
242
|
throw err;
|
|
244
243
|
}
|
|
245
244
|
}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import { encodePath, normalizePath, buildStatusQueryAttributes, isRev2Active } from '../tools.js';
|
|
1
|
+
import { encodePath, normalizePath, buildStatusQueryAttributes, isRev2Active, isAuthenticatedState, emitSafe, getSelectedMailbox } from '../tools.js';
|
|
2
2
|
import { parseStatusList } from './status-fields.js';
|
|
3
3
|
// STATUS fields that also refresh the live mailbox state when the queried mailbox is the
|
|
4
4
|
// currently selected one. Keyed by the output property name parseStatusList() reports.
|
|
5
5
|
const MAILBOX_UPDATERS = {
|
|
6
|
-
messages: (value, connection, path) => {
|
|
7
|
-
let mailbox = connection.mailbox;
|
|
6
|
+
messages: (value, mailbox, connection, path) => {
|
|
8
7
|
let prevCount = mailbox.exists;
|
|
9
8
|
if (prevCount !== value) {
|
|
10
9
|
mailbox.exists = value;
|
|
11
|
-
connection
|
|
10
|
+
emitSafe(connection, 'exists', { path, count: value, prevCount });
|
|
12
11
|
}
|
|
13
12
|
},
|
|
14
|
-
uidNext: (value,
|
|
15
|
-
|
|
13
|
+
uidNext: (value, mailbox) => {
|
|
14
|
+
mailbox.uidNext = value;
|
|
16
15
|
},
|
|
17
|
-
highestModseq: (value,
|
|
18
|
-
|
|
16
|
+
highestModseq: (value, mailbox) => {
|
|
17
|
+
mailbox.highestModseq = value;
|
|
19
18
|
}
|
|
20
19
|
};
|
|
21
20
|
/**
|
|
@@ -28,7 +27,7 @@ const MAILBOX_UPDATERS = {
|
|
|
28
27
|
* @throws {Error} If the mailbox does not exist
|
|
29
28
|
*/
|
|
30
29
|
export default async function status(connection, path, query) {
|
|
31
|
-
if (!
|
|
30
|
+
if (!isAuthenticatedState(connection) || !path) {
|
|
32
31
|
// nothing to do here
|
|
33
32
|
return false;
|
|
34
33
|
}
|
|
@@ -59,7 +58,8 @@ export default async function status(connection, path, query) {
|
|
|
59
58
|
STATUS: async (untagged) => {
|
|
60
59
|
// If querying the currently selected mailbox, also update the
|
|
61
60
|
// connection's live mailbox state and emit events for changes.
|
|
62
|
-
let
|
|
61
|
+
let selected = getSelectedMailbox(connection);
|
|
62
|
+
let currentMailbox = selected && selected.path === path ? selected : false;
|
|
63
63
|
let list = untagged.attributes && Array.isArray(untagged.attributes[1]) ? untagged.attributes[1] : false;
|
|
64
64
|
if (!list) {
|
|
65
65
|
return;
|
|
@@ -67,8 +67,8 @@ export default async function status(connection, path, query) {
|
|
|
67
67
|
parseStatusList(list, (key, value) => {
|
|
68
68
|
map[key] = value;
|
|
69
69
|
let updater = MAILBOX_UPDATERS[key];
|
|
70
|
-
if (
|
|
71
|
-
updater(value, connection, path);
|
|
70
|
+
if (currentMailbox && updater) {
|
|
71
|
+
updater(value, currentMailbox, connection, path);
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatFlag, canUseFlag,
|
|
1
|
+
import { formatFlag, canUseFlag, reportCommandError, getSelectedMailbox } from '../tools.js';
|
|
2
2
|
/**
|
|
3
3
|
* Updates flags or labels for messages in the selected mailbox.
|
|
4
4
|
*
|
|
@@ -9,7 +9,8 @@ import { formatFlag, canUseFlag, enhanceCommandError } from '../tools.js';
|
|
|
9
9
|
* @returns True on success, false on failure or if nothing to do
|
|
10
10
|
*/
|
|
11
11
|
export default async function store(connection, range, flags, options) {
|
|
12
|
-
|
|
12
|
+
let mailbox = getSelectedMailbox(connection);
|
|
13
|
+
if (!mailbox || !range || (options.useLabels && !connection.capabilities.has('X-GM-EXT-1'))) {
|
|
13
14
|
// nothing to do here
|
|
14
15
|
return false;
|
|
15
16
|
}
|
|
@@ -45,7 +46,7 @@ export default async function store(connection, range, flags, options) {
|
|
|
45
46
|
flags = (Array.isArray(flags) ? flags : [].concat(flags || []))
|
|
46
47
|
.map(flag => {
|
|
47
48
|
let formatted = formatFlag(flag);
|
|
48
|
-
if (!canUseFlag(
|
|
49
|
+
if (!canUseFlag(mailbox, formatted) && options.operation !== 'remove') {
|
|
49
50
|
return false;
|
|
50
51
|
}
|
|
51
52
|
return formatted;
|
|
@@ -62,7 +63,7 @@ export default async function store(connection, range, flags, options) {
|
|
|
62
63
|
];
|
|
63
64
|
// CONDSTORE (RFC 7162): UNCHANGEDSINCE modifier prevents updating messages whose
|
|
64
65
|
// mod-sequence is higher than the specified value, avoiding overwriting concurrent changes.
|
|
65
|
-
if (options.unchangedSince && connection.enabled.has('CONDSTORE') && !
|
|
66
|
+
if (options.unchangedSince && connection.enabled.has('CONDSTORE') && !mailbox.noModseq) {
|
|
66
67
|
attributes.push([
|
|
67
68
|
{
|
|
68
69
|
type: 'ATOM',
|
|
@@ -81,8 +82,7 @@ export default async function store(connection, range, flags, options) {
|
|
|
81
82
|
return true;
|
|
82
83
|
}
|
|
83
84
|
catch (err) {
|
|
84
|
-
await
|
|
85
|
-
connection.log.warn({ err, cid: connection.id });
|
|
85
|
+
await reportCommandError(connection, err);
|
|
86
86
|
return false;
|
|
87
87
|
}
|
|
88
88
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { setSubscription } from './subscription.js';
|
|
2
2
|
/**
|
|
3
3
|
* Subscribes to a mailbox.
|
|
4
4
|
*
|
|
@@ -7,20 +7,5 @@ import { encodePath, normalizePath, enhanceCommandError } from '../tools.js';
|
|
|
7
7
|
* @returns True on success, false on failure, or undefined if preconditions not met
|
|
8
8
|
*/
|
|
9
9
|
export default async function subscribe(connection, path) {
|
|
10
|
-
|
|
11
|
-
// nothing to do here
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
path = normalizePath(connection, path);
|
|
15
|
-
let response;
|
|
16
|
-
try {
|
|
17
|
-
response = await connection.exec('SUBSCRIBE', [{ type: 'ATOM', value: encodePath(connection, path) }]);
|
|
18
|
-
response.next();
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
await enhanceCommandError(err);
|
|
23
|
-
connection.log.warn({ err, cid: connection.id });
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
10
|
+
return await setSubscription(connection, 'SUBSCRIBE', path);
|
|
26
11
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs SUBSCRIBE or UNSUBSCRIBE for a mailbox, the shared body of the two commands.
|
|
4
|
+
*
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @param command - SUBSCRIBE or UNSUBSCRIBE
|
|
7
|
+
* @param path - Mailbox path
|
|
8
|
+
* @returns True on success, false on failure, or undefined if preconditions not met
|
|
9
|
+
*/
|
|
10
|
+
export declare function setSubscription(connection: ImapFlow, command: 'SUBSCRIBE' | 'UNSUBSCRIBE', path: string | string[]): Promise<boolean | undefined>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { encodePath, normalizePath, isAuthenticatedState, reportCommandError } from '../tools.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs SUBSCRIBE or UNSUBSCRIBE for a mailbox, the shared body of the two commands.
|
|
4
|
+
*
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @param command - SUBSCRIBE or UNSUBSCRIBE
|
|
7
|
+
* @param path - Mailbox path
|
|
8
|
+
* @returns True on success, false on failure, or undefined if preconditions not met
|
|
9
|
+
*/
|
|
10
|
+
export async function setSubscription(connection, command, path) {
|
|
11
|
+
if (!isAuthenticatedState(connection)) {
|
|
12
|
+
// nothing to do here
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
path = normalizePath(connection, path);
|
|
16
|
+
let response;
|
|
17
|
+
try {
|
|
18
|
+
response = await connection.exec(command, [{ type: 'ATOM', value: encodePath(connection, path) }]);
|
|
19
|
+
response.next();
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
await reportCommandError(connection, err);
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { setSubscription } from './subscription.js';
|
|
2
2
|
/**
|
|
3
3
|
* Unsubscribes from a mailbox.
|
|
4
4
|
*
|
|
@@ -7,20 +7,5 @@ import { encodePath, normalizePath, enhanceCommandError } from '../tools.js';
|
|
|
7
7
|
* @returns True on success, false on failure, or undefined if preconditions not met
|
|
8
8
|
*/
|
|
9
9
|
export default async function unsubscribe(connection, path) {
|
|
10
|
-
|
|
11
|
-
// nothing to do here
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
path = normalizePath(connection, path);
|
|
15
|
-
let response;
|
|
16
|
-
try {
|
|
17
|
-
response = await connection.exec('UNSUBSCRIBE', [{ type: 'ATOM', value: encodePath(connection, path) }]);
|
|
18
|
-
response.next();
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
await enhanceCommandError(err);
|
|
23
|
-
connection.log.warn({ err, cid: connection.id });
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
10
|
+
return await setSubscription(connection, 'UNSUBSCRIBE', path);
|
|
26
11
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ImapFlow } from './imap-flow.js';
|
|
2
|
+
import type { DownloadManyOptions, DownloadManyResult, DownloadNotFound, DownloadObject, DownloadOptions, SequenceString } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Implements ImapFlow.download(), see its documentation
|
|
5
|
+
*
|
|
6
|
+
* @param client - Connection to download from
|
|
7
|
+
* @param range - UID or sequence number of the message
|
|
8
|
+
* @param part - Body part to download, the whole message when not set
|
|
9
|
+
* @param options - Download options
|
|
10
|
+
* @returns The download, or an empty object when there is nothing to download
|
|
11
|
+
*/
|
|
12
|
+
export declare function downloadMessage(client: ImapFlow, range: SequenceString, part?: string | undefined, options?: DownloadOptions | undefined): Promise<DownloadObject | DownloadNotFound>;
|
|
13
|
+
/**
|
|
14
|
+
* Implements ImapFlow.downloadMany(), see its documentation
|
|
15
|
+
*
|
|
16
|
+
* @param client - Connection to download from
|
|
17
|
+
* @param range - UID or sequence number of the message
|
|
18
|
+
* @param parts - Body parts to download
|
|
19
|
+
* @param options - Download options
|
|
20
|
+
* @returns Downloaded parts keyed by part number
|
|
21
|
+
*/
|
|
22
|
+
export declare function downloadMessageParts(client: ImapFlow, range: SequenceString, parts: string[], options?: DownloadManyOptions | undefined): Promise<DownloadManyResult>;
|