imapflow 2.2.8 → 2.2.10
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 +15 -0
- package/dist/cjs/commands/select.d.ts +3 -14
- package/dist/cjs/commands/select.js +1 -4
- package/dist/cjs/imap-flow.js +30 -6
- package/dist/cjs/package-info.d.ts +1 -1
- package/dist/cjs/package-info.js +1 -1
- package/dist/cjs/tools.d.ts +10 -0
- package/dist/cjs/tools.js +29 -0
- package/dist/cjs/types.d.ts +9 -1
- package/dist/esm/commands/select.d.ts +3 -14
- package/dist/esm/commands/select.js +1 -4
- package/dist/esm/imap-flow.js +31 -7
- package/dist/esm/package-info.d.ts +1 -1
- package/dist/esm/package-info.js +1 -1
- package/dist/esm/tools.d.ts +10 -0
- package/dist/esm/tools.js +28 -0
- package/dist/esm/types.d.ts +9 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.2.10](https://github.com/postalsys/imapflow/compare/v2.2.9...v2.2.10) (2026-10-07)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* log socket errors after a failed STARTTLS upgrade instead of throwing them on Bun ([74fad57](https://github.com/postalsys/imapflow/commit/74fad57386cde6a60211b9b1671228b72aa9f94a))
|
|
9
|
+
* take the requested message in fetchOne() when a FETCH answer also carries unsolicited rows ([74fad57](https://github.com/postalsys/imapflow/commit/74fad57386cde6a60211b9b1671228b72aa9f94a)), closes [#426](https://github.com/postalsys/imapflow/issues/426)
|
|
10
|
+
|
|
11
|
+
## [2.2.9](https://github.com/postalsys/imapflow/compare/v2.2.8...v2.2.9) (2026-10-07)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* declare DownloadOptions.binary, the QRESYNC mailboxOpen options and FetchQueryObject.emailId ([2ae26e6](https://github.com/postalsys/imapflow/commit/2ae26e64c39c83ac3e04a67e9d4d808a1258c55b))
|
|
17
|
+
|
|
3
18
|
## [2.2.8](https://github.com/postalsys/imapflow/compare/v2.2.7...v2.2.8) (2026-10-07)
|
|
4
19
|
|
|
5
20
|
|
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
import type { ImapFlow } from '../imap-flow.js';
|
|
2
2
|
import type { MailboxObject, MailboxOpenOptions } from '../types.js';
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
* parameters, which are only honored when the QRESYNC extension has been enabled
|
|
6
|
-
*/
|
|
7
|
-
export interface SelectOptions extends MailboxOpenOptions {
|
|
8
|
-
/** QRESYNC modseq value to fetch changes since */
|
|
9
|
-
changedSince?: bigint | number | string | undefined;
|
|
10
|
-
/** QRESYNC UID validity value */
|
|
11
|
-
uidValidity?: bigint | number | string | undefined;
|
|
12
|
-
}
|
|
3
|
+
/** SELECT/EXAMINE options, the QRESYNC parameters are part of the public mailboxOpen() options */
|
|
4
|
+
export type SelectOptions = MailboxOpenOptions;
|
|
13
5
|
/**
|
|
14
6
|
* Selects or examines a mailbox, making it the current mailbox for subsequent operations.
|
|
15
7
|
*
|
|
16
8
|
* @param connection - IMAP connection instance
|
|
17
9
|
* @param path - Mailbox path to select
|
|
18
|
-
* @param options - Select options
|
|
19
|
-
* @param options.readOnly - If true, use EXAMINE instead of SELECT (read-only access)
|
|
20
|
-
* @param options.changedSince - QRESYNC modseq value to fetch changes since
|
|
21
|
-
* @param options.uidValidity - QRESYNC UID validity value
|
|
10
|
+
* @param options - Select options, see MailboxOpenOptions
|
|
22
11
|
* @returns Mailbox info object with path, flags, exists, uidNext, uidValidity, highestModseq, etc., or undefined if preconditions not met
|
|
23
12
|
* @throws If the SELECT/EXAMINE command fails
|
|
24
13
|
*/
|
|
@@ -51,10 +51,7 @@ const VALUED_RESPONSE_CODES = Object.assign(Object.create(null), {
|
|
|
51
51
|
*
|
|
52
52
|
* @param connection - IMAP connection instance
|
|
53
53
|
* @param path - Mailbox path to select
|
|
54
|
-
* @param options - Select options
|
|
55
|
-
* @param options.readOnly - If true, use EXAMINE instead of SELECT (read-only access)
|
|
56
|
-
* @param options.changedSince - QRESYNC modseq value to fetch changes since
|
|
57
|
-
* @param options.uidValidity - QRESYNC UID validity value
|
|
54
|
+
* @param options - Select options, see MailboxOpenOptions
|
|
58
55
|
* @returns Mailbox info object with path, flags, exists, uidNext, uidValidity, highestModseq, etc., or undefined if preconditions not met
|
|
59
56
|
* @throws If the SELECT/EXAMINE command fails
|
|
60
57
|
*/
|
package/dist/cjs/imap-flow.js
CHANGED
|
@@ -1512,6 +1512,13 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1512
1512
|
// socket event cannot re-enter an already settled upgrade or leave state behind.
|
|
1513
1513
|
const settle = (err, result) => {
|
|
1514
1514
|
if (settled) {
|
|
1515
|
+
// A failed handshake can produce more than one error (Bun emits ECONNRESET on
|
|
1516
|
+
// the TLS socket after the one that settled the upgrade). settle() stays the
|
|
1517
|
+
// error listener of both sockets until they are torn down, so later errors
|
|
1518
|
+
// end up here instead of being thrown as unhandled 'error' events.
|
|
1519
|
+
if (err) {
|
|
1520
|
+
this.log.debug({ msg: 'Socket error after the TLS upgrade was settled', err, cid: this.id });
|
|
1521
|
+
}
|
|
1515
1522
|
return;
|
|
1516
1523
|
}
|
|
1517
1524
|
settled = true;
|
|
@@ -1519,9 +1526,12 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1519
1526
|
this.upgradeTimeout = null;
|
|
1520
1527
|
this.upgrading = false;
|
|
1521
1528
|
this._upgradeReject = null;
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1529
|
+
if (!err) {
|
|
1530
|
+
// the generic socket handlers took over in the success callback
|
|
1531
|
+
socketPlain.removeListener('error', settle);
|
|
1532
|
+
if (this.socket && this.socket !== socketPlain) {
|
|
1533
|
+
this.socket.removeListener('error', settle);
|
|
1534
|
+
}
|
|
1525
1535
|
}
|
|
1526
1536
|
if (err) {
|
|
1527
1537
|
(0, tools_js_1.clearTimer)(this.connectTimeout);
|
|
@@ -1539,7 +1549,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1539
1549
|
// one function, one settlement, and removeListener() in settle() needs no separate
|
|
1540
1550
|
// handler references. A TLS handshake failure (bad certificate, protocol mismatch)
|
|
1541
1551
|
// is emitted on the new TLS socket rather than on the plain one, so both are covered.
|
|
1542
|
-
socketPlain.
|
|
1552
|
+
socketPlain.on('error', settle);
|
|
1543
1553
|
/* c8 ignore start */ // UPGRADE_TIMEOUT is 10s; firing it deterministically would make the test suite hang
|
|
1544
1554
|
this.upgradeTimeout = setTimeout(() => {
|
|
1545
1555
|
let err = new Error('Failed to upgrade connection in required time');
|
|
@@ -1617,7 +1627,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1617
1627
|
// error listener during the handshake window; the generic handlers are installed
|
|
1618
1628
|
// by setSocketHandlers() inside the success callback above, so a handshake error
|
|
1619
1629
|
// has a single error path.
|
|
1620
|
-
tlsSocket.
|
|
1630
|
+
tlsSocket.on('error', settle);
|
|
1621
1631
|
this.writeSocket = tlsSocket;
|
|
1622
1632
|
});
|
|
1623
1633
|
if (upgraded) {
|
|
@@ -3152,7 +3162,21 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
3152
3162
|
if (!response || !response.list || !response.list.length) {
|
|
3153
3163
|
return false;
|
|
3154
3164
|
}
|
|
3155
|
-
|
|
3165
|
+
// Every FETCH row that arrived during the command is in the list, also unsolicited ones
|
|
3166
|
+
// for other messages or with only a flag change, and a server may split the answer for
|
|
3167
|
+
// one message over several rows. Taking the first row returned a flag update instead
|
|
3168
|
+
// of the requested data, which ended a download after its first chunk (issue #426).
|
|
3169
|
+
let rows = response.list;
|
|
3170
|
+
let requested = (0, tools_js_1.parseUintValue)(String(seq), tools_js_1.MAX_UINT32_DIGITS);
|
|
3171
|
+
// a range: the first message of the answer, as before
|
|
3172
|
+
let target = requested === false ? rows[0] : rows.find(row => (options && options.uid ? row.uid : row.seq) === requested);
|
|
3173
|
+
if (!target) {
|
|
3174
|
+
return false;
|
|
3175
|
+
}
|
|
3176
|
+
// rows of the same message, without the ones a malformed answer gives no usable
|
|
3177
|
+
// sequence number or another UID
|
|
3178
|
+
let { seq: targetSeq, uid: targetUid } = target;
|
|
3179
|
+
return targetSeq ? (0, tools_js_1.mergeFetchRows)(rows.filter(row => row.seq === targetSeq && (!row.uid || !targetUid || row.uid === targetUid))) : target;
|
|
3156
3180
|
}
|
|
3157
3181
|
/**
|
|
3158
3182
|
* Download either full rfc822 formatted message or a specific bodystructure part as a Stream.
|
package/dist/cjs/package-info.js
CHANGED
package/dist/cjs/tools.d.ts
CHANGED
|
@@ -287,6 +287,16 @@ export declare function getColorFlags(color: string | null | undefined): {
|
|
|
287
287
|
add: string[];
|
|
288
288
|
remove: string[];
|
|
289
289
|
} | null;
|
|
290
|
+
/**
|
|
291
|
+
* Merges the FETCH rows a server sent for one message into one message object. A server may
|
|
292
|
+
* split the data items of a message over several FETCH responses, and may put an unsolicited
|
|
293
|
+
* one (a flag change made by another session) in between (RFC 9051 sections 7.5.2 and 5.2).
|
|
294
|
+
* Later rows win for single values, bodyParts, binaryParts and partialOrigins are combined.
|
|
295
|
+
*
|
|
296
|
+
* @param rows - Formatted rows of the same message, in the order they arrived
|
|
297
|
+
* @returns One message object holding the data of every row
|
|
298
|
+
*/
|
|
299
|
+
export declare function mergeFetchRows(rows: FetchMessageObject[]): FetchMessageObject;
|
|
290
300
|
/**
|
|
291
301
|
* Formats a raw untagged FETCH response into a structured message object.
|
|
292
302
|
*
|
package/dist/cjs/tools.js
CHANGED
|
@@ -32,6 +32,7 @@ exports.getSelectedMailbox = getSelectedMailbox;
|
|
|
32
32
|
exports.getFolderTree = getFolderTree;
|
|
33
33
|
exports.getFlagColor = getFlagColor;
|
|
34
34
|
exports.getColorFlags = getColorFlags;
|
|
35
|
+
exports.mergeFetchRows = mergeFetchRows;
|
|
35
36
|
exports.formatMessageResponse = formatMessageResponse;
|
|
36
37
|
exports.processName = processName;
|
|
37
38
|
exports.decodeText = decodeText;
|
|
@@ -713,6 +714,34 @@ function getColorFlags(color) {
|
|
|
713
714
|
}
|
|
714
715
|
return result;
|
|
715
716
|
}
|
|
717
|
+
/**
|
|
718
|
+
* Merges the FETCH rows a server sent for one message into one message object. A server may
|
|
719
|
+
* split the data items of a message over several FETCH responses, and may put an unsolicited
|
|
720
|
+
* one (a flag change made by another session) in between (RFC 9051 sections 7.5.2 and 5.2).
|
|
721
|
+
* Later rows win for single values, bodyParts, binaryParts and partialOrigins are combined.
|
|
722
|
+
*
|
|
723
|
+
* @param rows - Formatted rows of the same message, in the order they arrived
|
|
724
|
+
* @returns One message object holding the data of every row
|
|
725
|
+
*/
|
|
726
|
+
function mergeFetchRows(rows) {
|
|
727
|
+
if (rows.length === 1) {
|
|
728
|
+
return rows[0];
|
|
729
|
+
}
|
|
730
|
+
let merged = {};
|
|
731
|
+
let partialOrigins;
|
|
732
|
+
for (let row of rows) {
|
|
733
|
+
let { bodyParts, binaryParts, ...rest } = row;
|
|
734
|
+
Object.assign(merged, rest);
|
|
735
|
+
// combined into collections of their own, so the rows stay untouched
|
|
736
|
+
bodyParts?.forEach((value, key) => (merged.bodyParts ??= new Map()).set(key, value));
|
|
737
|
+
binaryParts?.forEach(key => (merged.binaryParts ??= new Set()).add(key));
|
|
738
|
+
row.partialOrigins?.forEach((value, key) => (partialOrigins ??= new Map()).set(key, value));
|
|
739
|
+
}
|
|
740
|
+
if (partialOrigins) {
|
|
741
|
+
Object.defineProperty(merged, 'partialOrigins', { value: partialOrigins, writable: true, configurable: true });
|
|
742
|
+
}
|
|
743
|
+
return merged;
|
|
744
|
+
}
|
|
716
745
|
/**
|
|
717
746
|
* Formats a raw untagged FETCH response into a structured message object.
|
|
718
747
|
*
|
package/dist/cjs/types.d.ts
CHANGED
|
@@ -499,6 +499,8 @@ export interface FetchQueryObject {
|
|
|
499
499
|
/** Include full message in the response, up to maxLength bytes */
|
|
500
500
|
maxLength?: number | undefined;
|
|
501
501
|
} | undefined;
|
|
502
|
+
/** Email ID (OBJECTID EMAILID or Gmail X-GM-MSGID) is always requested when the server supports either extension, so this is accepted but changes nothing */
|
|
503
|
+
emailId?: boolean | undefined;
|
|
502
504
|
/** If true then include thread ID in the response (only if server supports either OBJECTID or X-GM-EXT-1 extensions) */
|
|
503
505
|
threadId?: boolean | undefined;
|
|
504
506
|
/** If true then include GMail labels in the response (only if server supports X-GM-EXT-1 extension) */
|
|
@@ -653,9 +655,11 @@ export interface DownloadOptions {
|
|
|
653
655
|
maxBytes?: number | undefined;
|
|
654
656
|
/** How large content parts to ask from the server. Defaults to 65536 */
|
|
655
657
|
chunkSize?: number | undefined;
|
|
658
|
+
/** If true then requests the content with FETCH BINARY when the server supports it (BINARY or IMAP4rev2), so the server removes the transfer encoding */
|
|
659
|
+
binary?: boolean | undefined;
|
|
656
660
|
}
|
|
657
661
|
/** Options for downloadMany(): the download() options without `chunkSize`, as the parts come in one FETCH */
|
|
658
|
-
export type DownloadManyOptions = Pick<DownloadOptions, 'uid' | 'maxBytes'>;
|
|
662
|
+
export type DownloadManyOptions = Pick<DownloadOptions, 'uid' | 'maxBytes' | 'binary'>;
|
|
659
663
|
export interface DownloadManyPart {
|
|
660
664
|
meta: DownloadMeta;
|
|
661
665
|
content?: Buffer | null | undefined;
|
|
@@ -706,6 +710,10 @@ export interface MailboxOpenOptions {
|
|
|
706
710
|
readOnly?: boolean | undefined;
|
|
707
711
|
/** Optional description for mailbox lock tracking */
|
|
708
712
|
description?: string | undefined;
|
|
713
|
+
/** QRESYNC (RFC 7162): HIGHESTMODSEQ from an earlier session. With `uidValidity` and QRESYNC enabled, changes since then are reported as `flags` and `expunge` events. getMailboxLock() only applies it when it selects the mailbox, not when the mailbox is already open */
|
|
714
|
+
changedSince?: bigint | number | string | undefined;
|
|
715
|
+
/** QRESYNC (RFC 7162): the UIDVALIDITY known from the previous session, required with `changedSince` */
|
|
716
|
+
uidValidity?: bigint | number | string | undefined;
|
|
709
717
|
}
|
|
710
718
|
export interface MailboxLockOptions extends MailboxOpenOptions {
|
|
711
719
|
/**
|
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
import type { ImapFlow } from '../imap-flow.js';
|
|
2
2
|
import type { MailboxObject, MailboxOpenOptions } from '../types.js';
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
* parameters, which are only honored when the QRESYNC extension has been enabled
|
|
6
|
-
*/
|
|
7
|
-
export interface SelectOptions extends MailboxOpenOptions {
|
|
8
|
-
/** QRESYNC modseq value to fetch changes since */
|
|
9
|
-
changedSince?: bigint | number | string | undefined;
|
|
10
|
-
/** QRESYNC UID validity value */
|
|
11
|
-
uidValidity?: bigint | number | string | undefined;
|
|
12
|
-
}
|
|
3
|
+
/** SELECT/EXAMINE options, the QRESYNC parameters are part of the public mailboxOpen() options */
|
|
4
|
+
export type SelectOptions = MailboxOpenOptions;
|
|
13
5
|
/**
|
|
14
6
|
* Selects or examines a mailbox, making it the current mailbox for subsequent operations.
|
|
15
7
|
*
|
|
16
8
|
* @param connection - IMAP connection instance
|
|
17
9
|
* @param path - Mailbox path to select
|
|
18
|
-
* @param options - Select options
|
|
19
|
-
* @param options.readOnly - If true, use EXAMINE instead of SELECT (read-only access)
|
|
20
|
-
* @param options.changedSince - QRESYNC modseq value to fetch changes since
|
|
21
|
-
* @param options.uidValidity - QRESYNC UID validity value
|
|
10
|
+
* @param options - Select options, see MailboxOpenOptions
|
|
22
11
|
* @returns Mailbox info object with path, flags, exists, uidNext, uidValidity, highestModseq, etc., or undefined if preconditions not met
|
|
23
12
|
* @throws If the SELECT/EXAMINE command fails
|
|
24
13
|
*/
|
|
@@ -48,10 +48,7 @@ const VALUED_RESPONSE_CODES = Object.assign(Object.create(null), {
|
|
|
48
48
|
*
|
|
49
49
|
* @param connection - IMAP connection instance
|
|
50
50
|
* @param path - Mailbox path to select
|
|
51
|
-
* @param options - Select options
|
|
52
|
-
* @param options.readOnly - If true, use EXAMINE instead of SELECT (read-only access)
|
|
53
|
-
* @param options.changedSince - QRESYNC modseq value to fetch changes since
|
|
54
|
-
* @param options.uidValidity - QRESYNC UID validity value
|
|
51
|
+
* @param options - Select options, see MailboxOpenOptions
|
|
55
52
|
* @returns Mailbox info object with path, flags, exists, uidNext, uidValidity, highestModseq, etc., or undefined if preconditions not met
|
|
56
53
|
* @throws If the SELECT/EXAMINE command fails
|
|
57
54
|
*/
|
package/dist/esm/imap-flow.js
CHANGED
|
@@ -16,7 +16,7 @@ import { ConnectionDeadline } from './connection-deadline.js';
|
|
|
16
16
|
import { downloadMessage, downloadMessageParts } from './download.js';
|
|
17
17
|
import { AuthenticationFailure } from './errors.js';
|
|
18
18
|
import imapCommands from './imap-commands.js';
|
|
19
|
-
import { comparePaths, updateCapabilities, getFolderTree, formatMessageResponse, packMessageRange, normalizePath, expandRange, getColorFlags, hasCapability, isRev2Active, logConnectionError, unrefTimer, clearTimer, parseUintValue, getStringList, getTextValues, emitSafe, buildConnectionError, guardedPromise, guardedReject, MAX_UINT32_DIGITS } from './tools.js';
|
|
19
|
+
import { comparePaths, updateCapabilities, getFolderTree, formatMessageResponse, mergeFetchRows, packMessageRange, normalizePath, expandRange, getColorFlags, hasCapability, isRev2Active, logConnectionError, unrefTimer, clearTimer, parseUintValue, getStringList, getTextValues, emitSafe, buildConnectionError, guardedPromise, guardedReject, MAX_UINT32_DIGITS } from './tools.js';
|
|
20
20
|
export { AuthenticationFailure, ImapFlowErrorCode } from './errors.js';
|
|
21
21
|
const GREETING_TIMEOUT = 16 * 1000;
|
|
22
22
|
const UPGRADE_TIMEOUT = 10 * 1000;
|
|
@@ -1471,6 +1471,13 @@ export class ImapFlow extends EventEmitter {
|
|
|
1471
1471
|
// socket event cannot re-enter an already settled upgrade or leave state behind.
|
|
1472
1472
|
const settle = (err, result) => {
|
|
1473
1473
|
if (settled) {
|
|
1474
|
+
// A failed handshake can produce more than one error (Bun emits ECONNRESET on
|
|
1475
|
+
// the TLS socket after the one that settled the upgrade). settle() stays the
|
|
1476
|
+
// error listener of both sockets until they are torn down, so later errors
|
|
1477
|
+
// end up here instead of being thrown as unhandled 'error' events.
|
|
1478
|
+
if (err) {
|
|
1479
|
+
this.log.debug({ msg: 'Socket error after the TLS upgrade was settled', err, cid: this.id });
|
|
1480
|
+
}
|
|
1474
1481
|
return;
|
|
1475
1482
|
}
|
|
1476
1483
|
settled = true;
|
|
@@ -1478,9 +1485,12 @@ export class ImapFlow extends EventEmitter {
|
|
|
1478
1485
|
this.upgradeTimeout = null;
|
|
1479
1486
|
this.upgrading = false;
|
|
1480
1487
|
this._upgradeReject = null;
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1488
|
+
if (!err) {
|
|
1489
|
+
// the generic socket handlers took over in the success callback
|
|
1490
|
+
socketPlain.removeListener('error', settle);
|
|
1491
|
+
if (this.socket && this.socket !== socketPlain) {
|
|
1492
|
+
this.socket.removeListener('error', settle);
|
|
1493
|
+
}
|
|
1484
1494
|
}
|
|
1485
1495
|
if (err) {
|
|
1486
1496
|
clearTimer(this.connectTimeout);
|
|
@@ -1498,7 +1508,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1498
1508
|
// one function, one settlement, and removeListener() in settle() needs no separate
|
|
1499
1509
|
// handler references. A TLS handshake failure (bad certificate, protocol mismatch)
|
|
1500
1510
|
// is emitted on the new TLS socket rather than on the plain one, so both are covered.
|
|
1501
|
-
socketPlain.
|
|
1511
|
+
socketPlain.on('error', settle);
|
|
1502
1512
|
/* c8 ignore start */ // UPGRADE_TIMEOUT is 10s; firing it deterministically would make the test suite hang
|
|
1503
1513
|
this.upgradeTimeout = setTimeout(() => {
|
|
1504
1514
|
let err = new Error('Failed to upgrade connection in required time');
|
|
@@ -1576,7 +1586,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1576
1586
|
// error listener during the handshake window; the generic handlers are installed
|
|
1577
1587
|
// by setSocketHandlers() inside the success callback above, so a handshake error
|
|
1578
1588
|
// has a single error path.
|
|
1579
|
-
tlsSocket.
|
|
1589
|
+
tlsSocket.on('error', settle);
|
|
1580
1590
|
this.writeSocket = tlsSocket;
|
|
1581
1591
|
});
|
|
1582
1592
|
if (upgraded) {
|
|
@@ -3111,7 +3121,21 @@ export class ImapFlow extends EventEmitter {
|
|
|
3111
3121
|
if (!response || !response.list || !response.list.length) {
|
|
3112
3122
|
return false;
|
|
3113
3123
|
}
|
|
3114
|
-
|
|
3124
|
+
// Every FETCH row that arrived during the command is in the list, also unsolicited ones
|
|
3125
|
+
// for other messages or with only a flag change, and a server may split the answer for
|
|
3126
|
+
// one message over several rows. Taking the first row returned a flag update instead
|
|
3127
|
+
// of the requested data, which ended a download after its first chunk (issue #426).
|
|
3128
|
+
let rows = response.list;
|
|
3129
|
+
let requested = parseUintValue(String(seq), MAX_UINT32_DIGITS);
|
|
3130
|
+
// a range: the first message of the answer, as before
|
|
3131
|
+
let target = requested === false ? rows[0] : rows.find(row => (options && options.uid ? row.uid : row.seq) === requested);
|
|
3132
|
+
if (!target) {
|
|
3133
|
+
return false;
|
|
3134
|
+
}
|
|
3135
|
+
// rows of the same message, without the ones a malformed answer gives no usable
|
|
3136
|
+
// sequence number or another UID
|
|
3137
|
+
let { seq: targetSeq, uid: targetUid } = target;
|
|
3138
|
+
return targetSeq ? mergeFetchRows(rows.filter(row => row.seq === targetSeq && (!row.uid || !targetUid || row.uid === targetUid))) : target;
|
|
3115
3139
|
}
|
|
3116
3140
|
/**
|
|
3117
3141
|
* Download either full rfc822 formatted message or a specific bodystructure part as a Stream.
|
package/dist/esm/package-info.js
CHANGED
package/dist/esm/tools.d.ts
CHANGED
|
@@ -287,6 +287,16 @@ export declare function getColorFlags(color: string | null | undefined): {
|
|
|
287
287
|
add: string[];
|
|
288
288
|
remove: string[];
|
|
289
289
|
} | null;
|
|
290
|
+
/**
|
|
291
|
+
* Merges the FETCH rows a server sent for one message into one message object. A server may
|
|
292
|
+
* split the data items of a message over several FETCH responses, and may put an unsolicited
|
|
293
|
+
* one (a flag change made by another session) in between (RFC 9051 sections 7.5.2 and 5.2).
|
|
294
|
+
* Later rows win for single values, bodyParts, binaryParts and partialOrigins are combined.
|
|
295
|
+
*
|
|
296
|
+
* @param rows - Formatted rows of the same message, in the order they arrived
|
|
297
|
+
* @returns One message object holding the data of every row
|
|
298
|
+
*/
|
|
299
|
+
export declare function mergeFetchRows(rows: FetchMessageObject[]): FetchMessageObject;
|
|
290
300
|
/**
|
|
291
301
|
* Formats a raw untagged FETCH response into a structured message object.
|
|
292
302
|
*
|
package/dist/esm/tools.js
CHANGED
|
@@ -657,6 +657,34 @@ export function getColorFlags(color) {
|
|
|
657
657
|
}
|
|
658
658
|
return result;
|
|
659
659
|
}
|
|
660
|
+
/**
|
|
661
|
+
* Merges the FETCH rows a server sent for one message into one message object. A server may
|
|
662
|
+
* split the data items of a message over several FETCH responses, and may put an unsolicited
|
|
663
|
+
* one (a flag change made by another session) in between (RFC 9051 sections 7.5.2 and 5.2).
|
|
664
|
+
* Later rows win for single values, bodyParts, binaryParts and partialOrigins are combined.
|
|
665
|
+
*
|
|
666
|
+
* @param rows - Formatted rows of the same message, in the order they arrived
|
|
667
|
+
* @returns One message object holding the data of every row
|
|
668
|
+
*/
|
|
669
|
+
export function mergeFetchRows(rows) {
|
|
670
|
+
if (rows.length === 1) {
|
|
671
|
+
return rows[0];
|
|
672
|
+
}
|
|
673
|
+
let merged = {};
|
|
674
|
+
let partialOrigins;
|
|
675
|
+
for (let row of rows) {
|
|
676
|
+
let { bodyParts, binaryParts, ...rest } = row;
|
|
677
|
+
Object.assign(merged, rest);
|
|
678
|
+
// combined into collections of their own, so the rows stay untouched
|
|
679
|
+
bodyParts?.forEach((value, key) => (merged.bodyParts ??= new Map()).set(key, value));
|
|
680
|
+
binaryParts?.forEach(key => (merged.binaryParts ??= new Set()).add(key));
|
|
681
|
+
row.partialOrigins?.forEach((value, key) => (partialOrigins ??= new Map()).set(key, value));
|
|
682
|
+
}
|
|
683
|
+
if (partialOrigins) {
|
|
684
|
+
Object.defineProperty(merged, 'partialOrigins', { value: partialOrigins, writable: true, configurable: true });
|
|
685
|
+
}
|
|
686
|
+
return merged;
|
|
687
|
+
}
|
|
660
688
|
/**
|
|
661
689
|
* Formats a raw untagged FETCH response into a structured message object.
|
|
662
690
|
*
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -499,6 +499,8 @@ export interface FetchQueryObject {
|
|
|
499
499
|
/** Include full message in the response, up to maxLength bytes */
|
|
500
500
|
maxLength?: number | undefined;
|
|
501
501
|
} | undefined;
|
|
502
|
+
/** Email ID (OBJECTID EMAILID or Gmail X-GM-MSGID) is always requested when the server supports either extension, so this is accepted but changes nothing */
|
|
503
|
+
emailId?: boolean | undefined;
|
|
502
504
|
/** If true then include thread ID in the response (only if server supports either OBJECTID or X-GM-EXT-1 extensions) */
|
|
503
505
|
threadId?: boolean | undefined;
|
|
504
506
|
/** If true then include GMail labels in the response (only if server supports X-GM-EXT-1 extension) */
|
|
@@ -653,9 +655,11 @@ export interface DownloadOptions {
|
|
|
653
655
|
maxBytes?: number | undefined;
|
|
654
656
|
/** How large content parts to ask from the server. Defaults to 65536 */
|
|
655
657
|
chunkSize?: number | undefined;
|
|
658
|
+
/** If true then requests the content with FETCH BINARY when the server supports it (BINARY or IMAP4rev2), so the server removes the transfer encoding */
|
|
659
|
+
binary?: boolean | undefined;
|
|
656
660
|
}
|
|
657
661
|
/** Options for downloadMany(): the download() options without `chunkSize`, as the parts come in one FETCH */
|
|
658
|
-
export type DownloadManyOptions = Pick<DownloadOptions, 'uid' | 'maxBytes'>;
|
|
662
|
+
export type DownloadManyOptions = Pick<DownloadOptions, 'uid' | 'maxBytes' | 'binary'>;
|
|
659
663
|
export interface DownloadManyPart {
|
|
660
664
|
meta: DownloadMeta;
|
|
661
665
|
content?: Buffer | null | undefined;
|
|
@@ -706,6 +710,10 @@ export interface MailboxOpenOptions {
|
|
|
706
710
|
readOnly?: boolean | undefined;
|
|
707
711
|
/** Optional description for mailbox lock tracking */
|
|
708
712
|
description?: string | undefined;
|
|
713
|
+
/** QRESYNC (RFC 7162): HIGHESTMODSEQ from an earlier session. With `uidValidity` and QRESYNC enabled, changes since then are reported as `flags` and `expunge` events. getMailboxLock() only applies it when it selects the mailbox, not when the mailbox is already open */
|
|
714
|
+
changedSince?: bigint | number | string | undefined;
|
|
715
|
+
/** QRESYNC (RFC 7162): the UIDVALIDITY known from the previous session, required with `changedSince` */
|
|
716
|
+
uidValidity?: bigint | number | string | undefined;
|
|
709
717
|
}
|
|
710
718
|
export interface MailboxLockOptions extends MailboxOpenOptions {
|
|
711
719
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.10",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/imap-flow.js",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"eslint": "10.12.0",
|
|
76
76
|
"eslint-config-prettier": "10.1.8",
|
|
77
77
|
"globals": "17.13.0",
|
|
78
|
-
"imapkit": "4.1.
|
|
78
|
+
"imapkit": "4.1.1",
|
|
79
79
|
"prettier": "3.9.9",
|
|
80
80
|
"tsx": "4.23.15",
|
|
81
81
|
"types-node-legacy": "npm:@types/node@20.0.0",
|