imapflow 1.7.8 → 2.0.0
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 +20 -0
- package/README.md +8 -2
- package/dist/cjs/charsets.d.ts +1 -0
- package/dist/cjs/charsets.js +294 -0
- package/dist/cjs/commands/append.d.ts +22 -0
- package/dist/cjs/commands/append.js +151 -0
- package/dist/cjs/commands/authenticate.d.ts +24 -0
- package/dist/cjs/commands/authenticate.js +223 -0
- package/dist/cjs/commands/capability.d.ts +8 -0
- package/dist/cjs/commands/capability.js +32 -0
- package/dist/cjs/commands/close.d.ts +8 -0
- package/dist/cjs/commands/close.js +39 -0
- package/dist/cjs/commands/compress.d.ts +8 -0
- package/dist/cjs/commands/compress.js +56 -0
- package/dist/cjs/commands/copy.d.ts +13 -0
- package/dist/cjs/commands/copy.js +44 -0
- package/dist/cjs/commands/copyuid-parser.d.ts +11 -0
- package/dist/cjs/commands/copyuid-parser.js +32 -0
- package/dist/cjs/commands/create.d.ts +11 -0
- package/dist/cjs/commands/create.js +80 -0
- package/dist/cjs/commands/delete.d.ts +11 -0
- package/dist/cjs/commands/delete.js +40 -0
- package/dist/cjs/commands/enable.d.ts +9 -0
- package/dist/cjs/commands/enable.js +61 -0
- package/dist/cjs/commands/esearch-parser.d.ts +17 -0
- package/dist/cjs/commands/esearch-parser.js +91 -0
- package/dist/cjs/commands/expunge.d.ts +12 -0
- package/dist/cjs/commands/expunge.js +60 -0
- package/dist/cjs/commands/fetch.d.ts +30 -0
- package/dist/cjs/commands/fetch.js +241 -0
- package/dist/cjs/commands/id.d.ts +10 -0
- package/dist/cjs/commands/id.js +80 -0
- package/dist/cjs/commands/idle.d.ts +9 -0
- package/dist/cjs/commands/idle.js +347 -0
- package/dist/cjs/commands/list.d.ts +16 -0
- package/dist/cjs/commands/list.js +518 -0
- package/dist/cjs/commands/login.d.ts +11 -0
- package/dist/cjs/commands/login.js +42 -0
- package/dist/cjs/commands/logout.d.ts +8 -0
- package/dist/cjs/commands/logout.js +47 -0
- package/dist/cjs/commands/move.d.ts +13 -0
- package/dist/cjs/commands/move.js +57 -0
- package/dist/cjs/commands/namespace.d.ts +25 -0
- package/dist/cjs/commands/namespace.js +139 -0
- package/dist/cjs/commands/noop.d.ts +8 -0
- package/dist/cjs/commands/noop.js +22 -0
- package/dist/cjs/commands/quota.d.ts +10 -0
- package/dist/cjs/commands/quota.js +119 -0
- package/dist/cjs/commands/rename.d.ts +12 -0
- package/dist/cjs/commands/rename.js +48 -0
- package/dist/cjs/commands/search.d.ts +15 -0
- package/dist/cjs/commands/search.js +228 -0
- package/dist/cjs/commands/select.d.ts +25 -0
- package/dist/cjs/commands/select.js +250 -0
- package/dist/cjs/commands/starttls.d.ts +8 -0
- package/dist/cjs/commands/starttls.js +30 -0
- package/dist/cjs/commands/status-fields.d.ts +14 -0
- package/dist/cjs/commands/status-fields.js +61 -0
- package/dist/cjs/commands/status.d.ts +12 -0
- package/dist/cjs/commands/status.js +108 -0
- package/dist/cjs/commands/store.d.ts +19 -0
- package/dist/cjs/commands/store.js +93 -0
- package/dist/cjs/commands/subscribe.d.ts +9 -0
- package/dist/cjs/commands/subscribe.js +31 -0
- package/dist/cjs/commands/unsubscribe.d.ts +9 -0
- package/dist/cjs/commands/unsubscribe.js +31 -0
- package/dist/cjs/connection-deadline.d.ts +49 -0
- package/dist/cjs/connection-deadline.js +91 -0
- package/dist/cjs/errors.d.ts +83 -0
- package/dist/cjs/errors.js +13 -0
- package/dist/cjs/handler/imap-compiler.d.ts +24 -0
- package/dist/cjs/handler/imap-compiler.js +285 -0
- package/dist/cjs/handler/imap-formal-syntax.d.ts +28 -0
- package/dist/cjs/handler/imap-formal-syntax.js +121 -0
- package/dist/cjs/handler/imap-handler.d.ts +9 -0
- package/dist/cjs/handler/imap-handler.js +10 -0
- package/dist/cjs/handler/imap-parser.d.ts +16 -0
- package/dist/cjs/handler/imap-parser.js +90 -0
- package/dist/cjs/handler/imap-stream.d.ts +181 -0
- package/dist/cjs/handler/imap-stream.js +446 -0
- package/dist/cjs/handler/limits.d.ts +25 -0
- package/dist/cjs/handler/limits.js +51 -0
- package/dist/cjs/handler/parser-instance.d.ts +68 -0
- package/dist/cjs/handler/parser-instance.js +223 -0
- package/dist/cjs/handler/token-parser.d.ts +91 -0
- package/dist/cjs/handler/token-parser.js +673 -0
- package/dist/cjs/handler/types.d.ts +91 -0
- package/dist/cjs/handler/types.js +4 -0
- package/dist/cjs/imap-commands.d.ts +16 -0
- package/dist/cjs/imap-commands.js +74 -0
- package/dist/cjs/imap-flow.d.ts +676 -0
- package/dist/cjs/imap-flow.js +3949 -0
- package/dist/cjs/jp-decoder.d.ts +12 -0
- package/dist/cjs/jp-decoder.js +79 -0
- package/dist/cjs/limited-passthrough.d.ts +25 -0
- package/dist/cjs/limited-passthrough.js +54 -0
- package/dist/cjs/logger.d.ts +3 -0
- package/dist/cjs/logger.js +11 -0
- package/dist/cjs/package-info.d.ts +3 -0
- package/dist/cjs/package-info.js +7 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/proxy-connection.d.ts +33 -0
- package/dist/cjs/proxy-connection.js +392 -0
- package/dist/cjs/search-compiler.d.ts +34 -0
- package/dist/cjs/search-compiler.js +476 -0
- package/dist/cjs/special-use.d.ts +22 -0
- package/dist/cjs/special-use.js +911 -0
- package/dist/cjs/tools.d.ts +427 -0
- package/dist/cjs/tools.js +1496 -0
- package/{lib/imap-flow.d.ts → dist/cjs/types.d.ts} +386 -516
- package/dist/cjs/types.js +5 -0
- package/dist/esm/charsets.d.ts +1 -0
- package/{lib → dist/esm}/charsets.js +1 -6
- package/dist/esm/commands/append.d.ts +22 -0
- package/{lib → dist/esm}/commands/append.js +22 -52
- package/dist/esm/commands/authenticate.d.ts +24 -0
- package/{lib → dist/esm}/commands/authenticate.js +62 -87
- package/dist/esm/commands/capability.d.ts +8 -0
- package/{lib → dist/esm}/commands/capability.js +6 -9
- package/dist/esm/commands/close.d.ts +8 -0
- package/{lib → dist/esm}/commands/close.js +6 -10
- package/dist/esm/commands/compress.d.ts +8 -0
- package/{lib → dist/esm}/commands/compress.js +7 -11
- package/dist/esm/commands/copy.d.ts +13 -0
- package/{lib → dist/esm}/commands/copy.js +12 -20
- package/dist/esm/commands/copyuid-parser.d.ts +11 -0
- package/{lib → dist/esm}/commands/copyuid-parser.js +9 -15
- package/dist/esm/commands/create.d.ts +11 -0
- package/{lib → dist/esm}/commands/create.js +13 -27
- package/dist/esm/commands/delete.d.ts +11 -0
- package/{lib → dist/esm}/commands/delete.js +9 -14
- package/dist/esm/commands/enable.d.ts +9 -0
- package/{lib → dist/esm}/commands/enable.js +23 -30
- package/dist/esm/commands/esearch-parser.d.ts +17 -0
- package/dist/esm/commands/esearch-parser.js +88 -0
- package/dist/esm/commands/expunge.d.ts +12 -0
- package/{lib → dist/esm}/commands/expunge.js +17 -22
- package/dist/esm/commands/fetch.d.ts +30 -0
- package/{lib → dist/esm}/commands/fetch.js +32 -64
- package/dist/esm/commands/id.d.ts +10 -0
- package/{lib → dist/esm}/commands/id.js +17 -23
- package/dist/esm/commands/idle.d.ts +9 -0
- package/{lib → dist/esm}/commands/idle.js +47 -81
- package/dist/esm/commands/list.d.ts +16 -0
- package/{lib → dist/esm}/commands/list.js +56 -121
- package/dist/esm/commands/login.d.ts +11 -0
- package/{lib → dist/esm}/commands/login.js +10 -15
- package/dist/esm/commands/logout.d.ts +8 -0
- package/{lib → dist/esm}/commands/logout.js +9 -11
- package/dist/esm/commands/move.d.ts +13 -0
- package/{lib → dist/esm}/commands/move.js +13 -21
- package/dist/esm/commands/namespace.d.ts +25 -0
- package/{lib → dist/esm}/commands/namespace.js +34 -44
- package/dist/esm/commands/noop.d.ts +8 -0
- package/{lib → dist/esm}/commands/noop.js +6 -7
- package/dist/esm/commands/quota.d.ts +10 -0
- package/{lib → dist/esm}/commands/quota.js +18 -36
- package/dist/esm/commands/rename.d.ts +12 -0
- package/{lib → dist/esm}/commands/rename.js +10 -15
- package/dist/esm/commands/search.d.ts +15 -0
- package/{lib → dist/esm}/commands/search.js +36 -135
- package/dist/esm/commands/select.d.ts +25 -0
- package/{lib → dist/esm}/commands/select.js +33 -64
- package/dist/esm/commands/starttls.d.ts +8 -0
- package/{lib → dist/esm}/commands/starttls.js +6 -8
- package/dist/esm/commands/status-fields.d.ts +14 -0
- package/{lib → dist/esm}/commands/status-fields.js +5 -16
- package/dist/esm/commands/status.d.ts +12 -0
- package/{lib → dist/esm}/commands/status.js +18 -29
- package/dist/esm/commands/store.d.ts +19 -0
- package/{lib → dist/esm}/commands/store.js +24 -37
- package/dist/esm/commands/subscribe.d.ts +9 -0
- package/{lib → dist/esm}/commands/subscribe.js +8 -12
- package/dist/esm/commands/unsubscribe.d.ts +9 -0
- package/{lib → dist/esm}/commands/unsubscribe.js +8 -12
- package/dist/esm/connection-deadline.d.ts +49 -0
- package/{lib → dist/esm}/connection-deadline.js +14 -25
- package/dist/esm/errors.d.ts +83 -0
- package/dist/esm/errors.js +9 -0
- package/dist/esm/handler/imap-compiler.d.ts +24 -0
- package/{lib → dist/esm}/handler/imap-compiler.js +22 -80
- package/dist/esm/handler/imap-formal-syntax.d.ts +28 -0
- package/dist/esm/handler/imap-formal-syntax.js +117 -0
- package/dist/esm/handler/imap-handler.d.ts +9 -0
- package/dist/esm/handler/imap-handler.js +9 -0
- package/dist/esm/handler/imap-parser.d.ts +16 -0
- package/{lib → dist/esm}/handler/imap-parser.js +31 -44
- package/dist/esm/handler/imap-stream.d.ts +181 -0
- package/{lib → dist/esm}/handler/imap-stream.js +29 -121
- package/dist/esm/handler/limits.d.ts +25 -0
- package/{lib → dist/esm}/handler/limits.js +13 -22
- package/dist/esm/handler/parser-instance.d.ts +68 -0
- package/{lib → dist/esm}/handler/parser-instance.js +19 -47
- package/dist/esm/handler/token-parser.d.ts +91 -0
- package/{lib → dist/esm}/handler/token-parser.js +71 -155
- package/dist/esm/handler/types.d.ts +91 -0
- package/dist/esm/handler/types.js +3 -0
- package/dist/esm/imap-commands.d.ts +16 -0
- package/dist/esm/imap-commands.js +67 -0
- package/dist/esm/imap-flow.d.ts +676 -0
- package/{lib → dist/esm}/imap-flow.js +761 -1789
- package/dist/esm/jp-decoder.d.ts +12 -0
- package/{lib → dist/esm}/jp-decoder.js +6 -21
- package/dist/esm/limited-passthrough.d.ts +25 -0
- package/{lib → dist/esm}/limited-passthrough.js +7 -20
- package/dist/esm/logger.d.ts +3 -0
- package/dist/esm/logger.js +4 -0
- package/dist/esm/package-info.d.ts +3 -0
- package/dist/esm/package-info.js +4 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/proxy-connection.d.ts +33 -0
- package/{lib → dist/esm}/proxy-connection.js +56 -127
- package/dist/esm/search-compiler.d.ts +34 -0
- package/{lib → dist/esm}/search-compiler.js +54 -110
- package/dist/esm/special-use.d.ts +22 -0
- package/dist/esm/special-use.js +907 -0
- package/dist/esm/tools.d.ts +427 -0
- package/dist/esm/tools.js +1446 -0
- package/dist/esm/types.d.ts +828 -0
- package/dist/esm/types.js +4 -0
- package/package.json +60 -20
- package/.gitattributes +0 -1
- package/.github/CODE_OF_CONDUCT.md +0 -76
- package/.github/FUNDING.yml +0 -4
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/.github/contributing.md +0 -17
- package/.github/workflows/release.yaml +0 -36
- package/.github/workflows/stale.yml +0 -29
- package/.github/workflows/test.yml +0 -51
- package/.ncurc.js +0 -4
- package/.prettierignore +0 -4
- package/.prettierrc.js +0 -8
- package/.release-please-manifest.json +0 -3
- package/CLAUDE.md +0 -104
- package/Gruntfile.js +0 -23
- package/eslint.config.js +0 -45
- package/lib/handler/imap-formal-syntax.js +0 -189
- package/lib/handler/imap-handler.js +0 -17
- package/lib/imap-commands.js +0 -45
- package/lib/logger.js +0 -5
- package/lib/special-use.js +0 -923
- package/lib/tools.js +0 -1612
- package/release-please-config.json +0 -10
- package/test/authentication-test.js +0 -101
- package/test/auto-idle-test.js +0 -470
- package/test/bodystructure-test.js +0 -899
- package/test/charsets-test.js +0 -161
- package/test/commands-branches-test.js +0 -1095
- package/test/commands-integration-test.js +0 -11124
- package/test/commands-test.js +0 -73
- package/test/connection-edge-cases-test.js +0 -1828
- package/test/connection-test.js +0 -162
- package/test/copyuid-parser-test.js +0 -173
- package/test/fetch-generator-test.js +0 -218
- package/test/fixtures/fake-timers.js +0 -115
- package/test/fixtures/serialized-mimetorture.js +0 -2738
- package/test/fixtures/test-client.js +0 -101
- package/test/fixtures/test-tls.js +0 -8
- package/test/handler-branches-test.js +0 -310
- package/test/idle-polling-test.js +0 -518
- package/test/imap-compiler-test.js +0 -809
- package/test/imap-flow-compress-test.js +0 -166
- package/test/imap-flow-coverage-test.js +0 -612
- package/test/imap-flow-fetch-download-test.js +0 -909
- package/test/imap-flow-internals-test.js +0 -725
- package/test/imap-flow-methods-test.js +0 -889
- package/test/imap-flow-proxy-paths-test.js +0 -366
- package/test/imap-flow-secure-test.js +0 -573
- package/test/imap-flow-server-test.js +0 -1474
- package/test/imap-formal-syntax-test.js +0 -293
- package/test/imap-parser-test.js +0 -1474
- package/test/imap-stream-edge-cases-test.js +0 -666
- package/test/imap-stream-test.js +0 -177
- package/test/imapflow-test.js +0 -258
- package/test/integration/README.md +0 -52
- package/test/integration/dovecot-test.conf +0 -27
- package/test/integration/rev2-live-test.js +0 -431
- package/test/integration/run-rev2-tests.sh +0 -75
- package/test/integration-test.js +0 -83
- package/test/jp-decoder-test.js +0 -304
- package/test/limited-passthrough-test.js +0 -299
- package/test/memory-cleanup-test.js +0 -144
- package/test/memory-leak-test.js +0 -667
- package/test/parser-limits-test.js +0 -292
- package/test/proxy-connection-test.js +0 -738
- package/test/reliability-improvements-test.js +0 -548
- package/test/search-compiler-test.js +0 -1300
- package/test/search-test.js +0 -329
- package/test/special-use-test.js +0 -418
- package/test/starttls-injection-test.js +0 -181
- package/test/tag-correlation-test.js +0 -333
- package/test/timer-policy-test.js +0 -227
- package/test/token-parser-test.js +0 -456
- package/test/tools-test.js +0 -2013
- package/test/unhandled-rejection-test.js +0 -661
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
import type { FetchMessageObject, FetchOptions, FetchQueryObject } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for the FETCH command
|
|
5
|
+
*/
|
|
6
|
+
export interface FetchCommandOptions extends Omit<FetchOptions, 'changedSince'> {
|
|
7
|
+
/** Only fetch messages changed since this modseq value */
|
|
8
|
+
changedSince?: bigint | number | string | undefined;
|
|
9
|
+
/** Callback for processing each fetched message individually. Call `next()` to release the next message */
|
|
10
|
+
onUntaggedFetch?: ((message: FetchMessageObject, next: (err?: Error | null | undefined) => void) => void) | undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Result of the FETCH command
|
|
14
|
+
*/
|
|
15
|
+
export interface FetchCommandResult {
|
|
16
|
+
/** Number of untagged FETCH responses received */
|
|
17
|
+
count: number;
|
|
18
|
+
/** Formatted messages, empty when `onUntaggedFetch` consumed them */
|
|
19
|
+
list: FetchMessageObject[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Fetches emails from the server.
|
|
23
|
+
*
|
|
24
|
+
* @param connection - IMAP connection instance
|
|
25
|
+
* @param range - Message sequence number or UID range
|
|
26
|
+
* @param query - Fetch query specifying which data to retrieve (e.g., flags, envelope, bodyStructure, headers, source, bodyParts)
|
|
27
|
+
* @param options - Fetch options
|
|
28
|
+
* @returns Object with message count and list, or undefined if not in SELECTED state
|
|
29
|
+
*/
|
|
30
|
+
export default function fetch(connection: ImapFlow, range: string, query: FetchQueryObject, options?: FetchCommandOptions | undefined): Promise<FetchCommandResult | undefined>;
|
|
@@ -1,88 +1,67 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { formatMessageResponse, isRev2Active } = require('../tools');
|
|
4
|
-
|
|
1
|
+
import { formatMessageResponse, isRev2Active } from '../tools.js';
|
|
5
2
|
/**
|
|
6
3
|
* Fetches emails from the server.
|
|
7
4
|
*
|
|
8
|
-
* @param
|
|
9
|
-
* @param
|
|
10
|
-
* @param
|
|
11
|
-
* @param
|
|
12
|
-
* @
|
|
13
|
-
* @param {boolean} [options.binary] - If true, use BINARY fetch when available
|
|
14
|
-
* @param {string} [options.changedSince] - Only fetch messages changed since this modseq value
|
|
15
|
-
* @param {Function} [options.onUntaggedFetch] - Callback for processing each fetched message individually
|
|
16
|
-
* @returns {Promise<{count: number, list: Object[]}|undefined>} Object with message count and list, or undefined if not in SELECTED state
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @param range - Message sequence number or UID range
|
|
7
|
+
* @param query - Fetch query specifying which data to retrieve (e.g., flags, envelope, bodyStructure, headers, source, bodyParts)
|
|
8
|
+
* @param options - Fetch options
|
|
9
|
+
* @returns Object with message count and list, or undefined if not in SELECTED state
|
|
17
10
|
*/
|
|
18
|
-
|
|
11
|
+
export default async function fetch(connection, range, query, options) {
|
|
19
12
|
if (connection.state !== connection.states.SELECTED || !range) {
|
|
20
13
|
// nothing to do here
|
|
21
14
|
return;
|
|
22
15
|
}
|
|
23
|
-
|
|
24
16
|
options = options || {};
|
|
25
|
-
|
|
26
17
|
let mailbox = connection.mailbox;
|
|
27
|
-
|
|
28
18
|
// Use BINARY extension for fetching if supported and requested, otherwise fall back to BODY.
|
|
29
19
|
// RFC 9051 folds the FETCH side of the BINARY extension into base IMAP4rev2, so an active
|
|
30
20
|
// rev2 session can use it even without the BINARY capability token (the APPEND side is NOT
|
|
31
|
-
// folded in and stays gated on the token in append.
|
|
21
|
+
// folded in and stays gated on the token in append.ts)
|
|
32
22
|
const canUseBinary = connection.capabilities.has('BINARY') || isRev2Active(connection);
|
|
33
23
|
const commandKey = canUseBinary && options.binary && !connection.disableBinary ? 'BINARY' : 'BODY';
|
|
34
|
-
|
|
35
24
|
// Retry logic for ETHROTTLE errors (server rate limiting) with exponential backoff
|
|
36
25
|
let retryCount = 0;
|
|
37
26
|
const maxRetries = 4;
|
|
38
27
|
const baseDelay = 1000; // Start with 1 second delay
|
|
39
|
-
|
|
40
28
|
while (retryCount < maxRetries) {
|
|
41
29
|
let messages = {
|
|
42
30
|
count: 0,
|
|
43
31
|
list: []
|
|
44
32
|
};
|
|
45
|
-
|
|
46
33
|
let response;
|
|
47
34
|
try {
|
|
48
35
|
/* c8 ignore next */ // range is guaranteed truthy by the early-return guard above, so the '*' fallback is unreachable
|
|
49
36
|
let attributes = [{ type: 'SEQUENCE', value: (range || '*').toString() }];
|
|
50
|
-
|
|
51
37
|
let queryStructure = [];
|
|
52
|
-
|
|
53
38
|
// Helper to build BODY.PEEK[section]<partial> or BINARY.PEEK[section]<partial> atoms.
|
|
54
39
|
// PEEK avoids marking messages as \Seen. Section identifies what to fetch (HEADER, specific part, etc.)
|
|
55
40
|
// Partial is an optional byte range [start, maxLength].
|
|
56
41
|
let setBodyPeek = (attributes, partial) => {
|
|
57
42
|
let section = [].concat(attributes || []);
|
|
58
|
-
|
|
59
43
|
// BINARY may only address the empty section or a numeric part specifier
|
|
60
44
|
// (RFC 3516 / RFC 9051 section-binary) - HEADER, HEADER.FIELDS, TEXT and
|
|
61
45
|
// n.MIME are invalid after BINARY and must stay BODY fetches
|
|
62
|
-
let
|
|
63
|
-
|
|
64
|
-
|
|
46
|
+
let first = section[0];
|
|
47
|
+
let binaryAddressable = !section.length || (section.length === 1 && typeof first.value === 'string' && /^\d+(\.\d+)*$/.test(first.value));
|
|
65
48
|
let bodyPeek = {
|
|
66
49
|
type: 'ATOM',
|
|
67
50
|
value: `${binaryAddressable ? commandKey : 'BODY'}.PEEK`,
|
|
68
|
-
section,
|
|
51
|
+
section: section,
|
|
69
52
|
partial
|
|
70
53
|
};
|
|
71
|
-
|
|
72
54
|
queryStructure.push(bodyPeek);
|
|
73
55
|
};
|
|
74
|
-
|
|
75
56
|
// IMAP fetch macros (ALL, FAST, FULL) and standard data items map directly to IMAP atoms
|
|
76
57
|
['all', 'fast', 'full', 'uid', 'flags', 'bodyStructure', 'envelope', 'internalDate'].forEach(key => {
|
|
77
58
|
if (query[key]) {
|
|
78
59
|
queryStructure.push({ type: 'ATOM', value: key.toUpperCase() });
|
|
79
60
|
}
|
|
80
61
|
});
|
|
81
|
-
|
|
82
62
|
if (query.size) {
|
|
83
63
|
queryStructure.push({ type: 'ATOM', value: 'RFC822.SIZE' });
|
|
84
64
|
}
|
|
85
|
-
|
|
86
65
|
// Fetch full message source, optionally with byte range (start/maxLength)
|
|
87
66
|
if (query.source) {
|
|
88
67
|
let partial;
|
|
@@ -94,51 +73,47 @@ module.exports = async (connection, range, query, options) => {
|
|
|
94
73
|
}
|
|
95
74
|
setBodyPeek(null, partial);
|
|
96
75
|
}
|
|
97
|
-
|
|
98
76
|
// Always request a unique email ID for message deduplication.
|
|
99
77
|
// Prefer OBJECTID (RFC 8474) over Gmail's X-GM-MSGID extension.
|
|
100
78
|
if (connection.capabilities.has('OBJECTID')) {
|
|
101
79
|
queryStructure.push({ type: 'ATOM', value: 'EMAILID' });
|
|
102
|
-
}
|
|
80
|
+
}
|
|
81
|
+
else if (connection.capabilities.has('X-GM-EXT-1')) {
|
|
103
82
|
queryStructure.push({ type: 'ATOM', value: 'X-GM-MSGID' });
|
|
104
83
|
}
|
|
105
|
-
|
|
106
84
|
// Thread ID: OBJECTID's THREADID or Gmail's X-GM-THRID
|
|
107
85
|
if (query.threadId) {
|
|
108
86
|
if (connection.capabilities.has('OBJECTID')) {
|
|
109
87
|
queryStructure.push({ type: 'ATOM', value: 'THREADID' });
|
|
110
|
-
}
|
|
88
|
+
}
|
|
89
|
+
else if (connection.capabilities.has('X-GM-EXT-1')) {
|
|
111
90
|
queryStructure.push({ type: 'ATOM', value: 'X-GM-THRID' });
|
|
112
91
|
}
|
|
113
92
|
}
|
|
114
|
-
|
|
115
93
|
// Gmail labels are only available with X-GM-EXT-1 extension
|
|
116
94
|
if (query.labels) {
|
|
117
95
|
if (connection.capabilities.has('X-GM-EXT-1')) {
|
|
118
96
|
queryStructure.push({ type: 'ATOM', value: 'X-GM-LABELS' });
|
|
119
97
|
}
|
|
120
98
|
}
|
|
121
|
-
|
|
122
99
|
// always ask for modseq if possible
|
|
123
100
|
if (connection.enabled.has('CONDSTORE') && !mailbox.noModseq) {
|
|
124
101
|
queryStructure.push({ type: 'ATOM', value: 'MODSEQ' });
|
|
125
102
|
}
|
|
126
|
-
|
|
127
103
|
// Always include UID in the response even if not explicitly requested,
|
|
128
104
|
// since we use it internally for message identification and tracking
|
|
129
105
|
if (!query.uid) {
|
|
130
106
|
queryStructure.push({ type: 'ATOM', value: 'UID' });
|
|
131
107
|
}
|
|
132
|
-
|
|
133
108
|
// Headers: fetch all headers or only specific ones via HEADER.FIELDS
|
|
134
109
|
if (query.headers) {
|
|
135
110
|
if (Array.isArray(query.headers)) {
|
|
136
111
|
setBodyPeek([{ type: 'ATOM', value: 'HEADER.FIELDS' }, query.headers.map(header => ({ type: 'ATOM', value: header }))]);
|
|
137
|
-
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
138
114
|
setBodyPeek({ type: 'ATOM', value: 'HEADER' });
|
|
139
115
|
}
|
|
140
116
|
}
|
|
141
|
-
|
|
142
117
|
// Fetch specific body parts by MIME part number (e.g., "1", "1.2", "2.MIME")
|
|
143
118
|
// Each part can optionally include a byte range (start/maxLength)
|
|
144
119
|
if (query.bodyParts && query.bodyParts.length) {
|
|
@@ -159,24 +134,23 @@ module.exports = async (connection, range, query, options) => {
|
|
|
159
134
|
partial.push(Number(part.maxLength));
|
|
160
135
|
}
|
|
161
136
|
}
|
|
162
|
-
}
|
|
137
|
+
}
|
|
138
|
+
else if (typeof part === 'string') {
|
|
163
139
|
key = part.toUpperCase();
|
|
164
|
-
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
165
142
|
return;
|
|
166
143
|
}
|
|
167
|
-
|
|
168
144
|
setBodyPeek({ type: 'ATOM', value: key }, partial);
|
|
169
145
|
});
|
|
170
146
|
}
|
|
171
|
-
|
|
172
147
|
// IMAP requires a single item to not be wrapped in parentheses, but
|
|
173
148
|
// multiple items must be in a list. If only one item, unwrap the array.
|
|
149
|
+
let queryAttribute = queryStructure;
|
|
174
150
|
if (queryStructure.length === 1) {
|
|
175
|
-
|
|
151
|
+
queryAttribute = queryStructure.pop();
|
|
176
152
|
}
|
|
177
|
-
|
|
178
|
-
attributes.push(queryStructure);
|
|
179
|
-
|
|
153
|
+
attributes.push(queryAttribute);
|
|
180
154
|
// CONDSTORE extension: only fetch messages with modseq higher than the given value.
|
|
181
155
|
// QRESYNC adds VANISHED to also get expunged UIDs since last sync.
|
|
182
156
|
if (options.changedSince && connection.enabled.has('CONDSTORE') && !mailbox.noModseq) {
|
|
@@ -190,23 +164,20 @@ module.exports = async (connection, range, query, options) => {
|
|
|
190
164
|
value: options.changedSince.toString()
|
|
191
165
|
}
|
|
192
166
|
];
|
|
193
|
-
|
|
194
167
|
if (options.uid && connection.enabled.has('QRESYNC')) {
|
|
195
168
|
changedSinceArgs.push({
|
|
196
169
|
type: 'ATOM',
|
|
197
170
|
value: 'VANISHED'
|
|
198
171
|
});
|
|
199
172
|
}
|
|
200
|
-
|
|
201
173
|
attributes.push(changedSinceArgs);
|
|
202
174
|
}
|
|
203
|
-
|
|
204
175
|
response = await connection.exec(options.uid ? 'UID FETCH' : 'FETCH', attributes, {
|
|
205
176
|
untagged: {
|
|
206
177
|
// Each matching message triggers an untagged FETCH response.
|
|
207
178
|
// If onUntaggedFetch callback is provided, stream messages to it one by one
|
|
208
179
|
// (useful for large result sets). Otherwise, collect all into messages.list.
|
|
209
|
-
FETCH: async untagged => {
|
|
180
|
+
FETCH: async (untagged) => {
|
|
210
181
|
messages.count++;
|
|
211
182
|
let formatted = await formatMessageResponse(untagged, mailbox);
|
|
212
183
|
if (typeof options.onUntaggedFetch === 'function') {
|
|
@@ -214,32 +185,32 @@ module.exports = async (connection, range, query, options) => {
|
|
|
214
185
|
options.onUntaggedFetch(formatted, err => {
|
|
215
186
|
if (err) {
|
|
216
187
|
reject(err);
|
|
217
|
-
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
218
190
|
resolve();
|
|
219
191
|
}
|
|
220
192
|
});
|
|
221
193
|
});
|
|
222
|
-
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
223
196
|
messages.list.push(formatted);
|
|
224
197
|
}
|
|
225
198
|
}
|
|
226
199
|
}
|
|
227
200
|
});
|
|
228
|
-
|
|
229
201
|
response.next();
|
|
230
202
|
return messages;
|
|
231
|
-
}
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
232
205
|
if (err.code === 'ETHROTTLE') {
|
|
233
206
|
// Server returned a throttle error (rate limiting). Retry with exponential backoff.
|
|
234
207
|
// Delay doubles each retry: 1s, 2s, 4s, 8s (capped at 30s).
|
|
235
208
|
// If server provides a throttleReset hint, use that if longer.
|
|
236
209
|
const backoffDelay = Math.min(baseDelay * Math.pow(2, retryCount), 30000); // Cap at 30 seconds
|
|
237
|
-
|
|
238
210
|
// Use throttle reset time if provided and longer than backoff. The hint is
|
|
239
211
|
// server-controlled, so the wait goes through connection.throttleWait(), which caps
|
|
240
212
|
// it and keeps the timer tracked and abortable.
|
|
241
213
|
const delay = err.throttleReset && err.throttleReset > backoffDelay ? err.throttleReset : backoffDelay;
|
|
242
|
-
|
|
243
214
|
connection.log.warn({
|
|
244
215
|
msg: 'Retrying throttled request with exponential backoff',
|
|
245
216
|
cid: connection.id,
|
|
@@ -249,20 +220,17 @@ module.exports = async (connection, range, query, options) => {
|
|
|
249
220
|
retryCount,
|
|
250
221
|
delayMs: delay
|
|
251
222
|
});
|
|
252
|
-
|
|
253
223
|
// An aborted wait means the client was closed, so give up rather than reissuing
|
|
254
224
|
// the FETCH on a connection that is already gone.
|
|
255
225
|
let aborted = await connection.throttleWait(delay);
|
|
256
226
|
if (aborted) {
|
|
257
227
|
throw connection.createNoConnectionError(connection.byeReason, { rejectedFrom: 'throttleAbort', command: 'FETCH' });
|
|
258
228
|
}
|
|
259
|
-
|
|
260
229
|
retryCount++;
|
|
261
230
|
continue;
|
|
262
231
|
}
|
|
263
|
-
|
|
264
232
|
connection.log.warn({ err, cid: connection.id });
|
|
265
233
|
throw err;
|
|
266
234
|
}
|
|
267
235
|
}
|
|
268
|
-
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
import type { IdInfoObject } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Sends ID info to the server and updates server info data based on the response.
|
|
5
|
+
*
|
|
6
|
+
* @param connection - IMAP connection instance
|
|
7
|
+
* @param clientInfo - Client identification key-value pairs to send to the server
|
|
8
|
+
* @returns Server information map, false on failure, or undefined if ID not supported
|
|
9
|
+
*/
|
|
10
|
+
export default function id(connection: ImapFlow, clientInfo?: IdInfoObject | null | undefined): Promise<IdInfoObject | false | undefined>;
|
|
@@ -1,51 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const { formatDateTime } = require('../tools.js');
|
|
4
|
-
|
|
1
|
+
import { formatDateTime } from '../tools.js';
|
|
5
2
|
/**
|
|
6
3
|
* Sends ID info to the server and updates server info data based on the response.
|
|
7
4
|
*
|
|
8
|
-
* @param
|
|
9
|
-
* @param
|
|
10
|
-
* @returns
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @param clientInfo - Client identification key-value pairs to send to the server
|
|
7
|
+
* @returns Server information map, false on failure, or undefined if ID not supported
|
|
11
8
|
*/
|
|
12
9
|
// RFC 2971: The ID command exchanges client/server implementation info
|
|
13
10
|
// (name, version, vendor, etc.) for diagnostic and compatibility purposes.
|
|
14
|
-
|
|
11
|
+
export default async function id(connection, clientInfo) {
|
|
15
12
|
if (!connection.capabilities.has('ID')) {
|
|
16
13
|
// nothing to do here
|
|
17
14
|
return;
|
|
18
15
|
}
|
|
19
|
-
|
|
20
16
|
let response;
|
|
21
17
|
try {
|
|
22
18
|
let map = {};
|
|
23
|
-
|
|
24
19
|
// Convert the clientInfo object into a flat array of alternating key-value strings
|
|
25
20
|
// for the IMAP wire format: ("key1" "value1" "key2" "value2" ...)
|
|
26
21
|
let formattedClientInfo = !clientInfo
|
|
27
22
|
? null
|
|
28
23
|
: Object.keys(clientInfo)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
.map(key => [key, formatValue(key, clientInfo[key])])
|
|
25
|
+
.filter(entry => entry[1])
|
|
26
|
+
.flatMap(entry => entry);
|
|
33
27
|
if (formattedClientInfo && !formattedClientInfo.length) {
|
|
34
28
|
// value array has no elements
|
|
35
29
|
formattedClientInfo = null;
|
|
36
30
|
}
|
|
37
|
-
|
|
38
31
|
response = await connection.exec('ID', [formattedClientInfo], {
|
|
39
32
|
untagged: {
|
|
40
33
|
// Parse the server's ID response: a flat list of alternating key-value atoms.
|
|
41
34
|
// Even indices (i % 2 === 0) are keys, odd indices are the corresponding values.
|
|
42
|
-
ID: async untagged => {
|
|
35
|
+
ID: async (untagged) => {
|
|
43
36
|
let params = untagged.attributes && untagged.attributes[0];
|
|
44
37
|
let key;
|
|
45
38
|
(Array.isArray(params) ? params : [].concat(params || [])).forEach((val, i) => {
|
|
46
39
|
if (i % 2 === 0) {
|
|
47
40
|
key = val.value;
|
|
48
|
-
}
|
|
41
|
+
}
|
|
42
|
+
else if (typeof key === 'string' && typeof val.value === 'string') {
|
|
49
43
|
map[key.toLowerCase().trim()] = val.value;
|
|
50
44
|
}
|
|
51
45
|
});
|
|
@@ -55,18 +49,18 @@ module.exports = async (connection, clientInfo) => {
|
|
|
55
49
|
connection.serverInfo = map;
|
|
56
50
|
response.next();
|
|
57
51
|
return map;
|
|
58
|
-
}
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
59
54
|
connection.log.warn({ err, cid: connection.id });
|
|
60
55
|
return false;
|
|
61
56
|
}
|
|
62
|
-
}
|
|
63
|
-
|
|
57
|
+
}
|
|
64
58
|
/**
|
|
65
59
|
* Formats a client info value for the ID command.
|
|
66
60
|
*
|
|
67
|
-
* @param
|
|
68
|
-
* @param
|
|
69
|
-
* @returns
|
|
61
|
+
* @param key - The info key name
|
|
62
|
+
* @param value - The value to format
|
|
63
|
+
* @returns Formatted value string
|
|
70
64
|
*/
|
|
71
65
|
function formatValue(key, value) {
|
|
72
66
|
switch (key.toLowerCase()) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ImapFlow } from '../imap-flow.js';
|
|
2
|
+
/**
|
|
3
|
+
* Listens for changes in the selected mailbox using IDLE or NOOP polling fallback.
|
|
4
|
+
*
|
|
5
|
+
* @param connection - IMAP connection instance
|
|
6
|
+
* @param maxIdleTime - Maximum time in milliseconds to stay in IDLE before restarting
|
|
7
|
+
* @returns Void on success, false on failure, or undefined if not in SELECTED state
|
|
8
|
+
*/
|
|
9
|
+
export default function idle(connection: ImapFlow, maxIdleTime?: number | false | undefined): Promise<void | false | undefined>;
|