imapflow 2.0.8 → 2.1.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.
@@ -17,7 +17,7 @@ import { downloadMessage, downloadMessageParts } from './download.js';
17
17
  import { AuthenticationFailure } from './errors.js';
18
18
  import imapCommands from './imap-commands.js';
19
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';
20
- export { AuthenticationFailure } from './errors.js';
20
+ export { AuthenticationFailure, ImapFlowErrorCode } from './errors.js';
21
21
  const GREETING_TIMEOUT = 16 * 1000;
22
22
  const UPGRADE_TIMEOUT = 10 * 1000;
23
23
  const SOCKET_TIMEOUT = 5 * 60 * 1000;
@@ -1561,6 +1561,7 @@ export class ImapFlow extends EventEmitter {
1561
1561
  /** @internal */
1562
1562
  beginSession(onUnhandledError) {
1563
1563
  clearTimer(this.greetingTimeout);
1564
+ this.greetingReceived = true;
1564
1565
  this.untaggedHandlers.OK = null;
1565
1566
  this.untaggedHandlers.PREAUTH = null;
1566
1567
  if (this.isClosed) {
@@ -1620,6 +1621,12 @@ export class ImapFlow extends EventEmitter {
1620
1621
  this.byeReason = reason || 'Server closed connection';
1621
1622
  this.untaggedHandlers.BYE = null;
1622
1623
  this.state = this.states.LOGOUT;
1624
+ // A BYE greeting rejects the connection outright. Do not wait for the server to close
1625
+ // the socket: one that keeps it open would leave connect() pending until the greeting
1626
+ // timeout.
1627
+ if (!this.greetingReceived) {
1628
+ this.closeAfter();
1629
+ }
1623
1630
  }
1624
1631
  // Drops every capability-derived field together - the counterpart of
1625
1632
  // updateCapabilitiesFromRaw() below, which sets them together. rawCapabilities is
@@ -1852,7 +1859,7 @@ export class ImapFlow extends EventEmitter {
1852
1859
  /** @internal */
1853
1860
  autoidle() {
1854
1861
  clearTimer(this.idleStartTimer);
1855
- if (this.options.disableAutoIdle || this.state !== this.states.SELECTED) {
1862
+ if (this.options.disableAutoIdle || !this.usable || this.state !== this.states.SELECTED) {
1856
1863
  return;
1857
1864
  }
1858
1865
  if (this.connectionBusy()) {
@@ -1864,7 +1871,7 @@ export class ImapFlow extends EventEmitter {
1864
1871
  // missed clearTimeout would inject IDLE between a caller's own commands. Declining
1865
1872
  // postpones rather than cancels: whatever made the connection busy calls autoidle()
1866
1873
  // again when it finishes.
1867
- if (this.state !== this.states.SELECTED || this.connectionBusy()) {
1874
+ if (!this.usable || this.state !== this.states.SELECTED || this.connectionBusy()) {
1868
1875
  return;
1869
1876
  }
1870
1877
  this.idle().catch(err => logConnectionError(this, 'Auto-IDLE failed', err));
@@ -3349,7 +3356,8 @@ export class ImapFlow extends EventEmitter {
3349
3356
  }
3350
3357
  /**
3351
3358
  * Detaches sockets from the IMAP pipeline. Useful for upgrading the connection
3352
- * (e.g., STARTTLS) or transferring socket ownership.
3359
+ * (e.g., STARTTLS) or transferring socket ownership. Call it while the connection is not
3360
+ * idling: an IDLE in progress is not broken first, so the server still expects `DONE`.
3353
3361
  *
3354
3362
  * @returns Socket objects: `readSocket` is the read socket (inflated socket if compression is enabled, raw socket otherwise),
3355
3363
  * `writeSocket` the write socket and `socket` the raw underlying socket (same as readSocket/writeSocket when compression is disabled)
@@ -3364,6 +3372,11 @@ export class ImapFlow extends EventEmitter {
3364
3372
  // compression is active, the PassThrough writeSocket - so the connection
3365
3373
  // is fully released to the caller.
3366
3374
  this.clearSocketHandlers();
3375
+ // The socket now belongs to the caller. Marking the client unusable keeps
3376
+ // auto-IDLE (armed now, or re-armed by a lock release or a finished download)
3377
+ // and a later dispose from writing IDLE or LOGOUT onto it.
3378
+ this.usable = false;
3379
+ clearTimer(this.idleStartTimer);
3367
3380
  const readSocket = this._inflate || socket;
3368
3381
  const writeSocket = this.writeSocket || socket;
3369
3382
  // Defense-in-depth: when compression is active the raw socket is orphaned
@@ -3461,6 +3474,23 @@ export class ImapFlow extends EventEmitter {
3461
3474
  * console.log(`${entry.cid} ${entry.msg}`);
3462
3475
  * });
3463
3476
  */
3477
+ // Installed outside the class body: a computed `[Symbol.asyncDispose]` key would turn into a
3478
+ // method named "undefined" on Node.js 20.0-20.3, which predate the symbol
3479
+ if (typeof Symbol.asyncDispose === 'symbol') {
3480
+ ImapFlow.prototype[Symbol.asyncDispose] = async function () {
3481
+ if (this.usable) {
3482
+ try {
3483
+ await this.logout();
3484
+ }
3485
+ catch (err) {
3486
+ logConnectionError(this, 'LOGOUT failed while disposing', err);
3487
+ }
3488
+ }
3489
+ // logout() already closes the connection; close() is idempotent and covers a client
3490
+ // that never connected or whose logout was skipped
3491
+ this.close();
3492
+ };
3493
+ }
3464
3494
  // Both `import { ImapFlow } from 'imapflow'` and `import imapflow from 'imapflow'` work, the
3465
3495
  // latter matching the shape `require('imapflow')` has always had
3466
3496
  const imapflow = { ImapFlow, AuthenticationFailure };
@@ -1,3 +1,3 @@
1
1
  export declare const name = "imapflow";
2
- export declare const version = "2.0.8";
2
+ export declare const version = "2.1.0";
3
3
  export declare const homepage = "https://imapflow.com/";
@@ -1,4 +1,4 @@
1
1
  // Generated by scripts/build.js from package.json. Do not edit by hand.
2
2
  export const name = "imapflow";
3
- export const version = "2.0.8";
3
+ export const version = "2.1.0";
4
4
  export const homepage = "https://imapflow.com/";
@@ -1,6 +1,6 @@
1
1
  import type { Transform } from 'node:stream';
2
2
  import type { ImapFlow } from './imap-flow.js';
3
- import type { ConnectionErrorSite, ImapFlowError } from './errors.js';
3
+ import { type ConnectionErrorSite, type ImapFlowError } from './errors.js';
4
4
  import type { ImapAttributeList, ImapAttributeNode, ImapResponse } from './handler/types.js';
5
5
  import type { FetchMessageObject, ListResponse, ListTreeResponse, MailboxObject, MessageEnvelopeObject, MessageStructureObject, ImapFlowEvents, StatusQuery } from './types.js';
6
6
  export { AuthenticationFailure } from './errors.js';
package/dist/esm/tools.js CHANGED
@@ -5,10 +5,11 @@ import { compiler } from './handler/imap-handler.js';
5
5
  import { createHash } from 'node:crypto';
6
6
  import { JPDecoder } from './jp-decoder.js';
7
7
  import iconv from 'iconv-lite';
8
+ import { ImapFlowErrorCode } from './errors.js';
8
9
  export { AuthenticationFailure } from './errors.js';
9
10
  const FLAG_COLORS = ['red', 'orange', 'yellow', 'green', 'blue', 'purple', 'grey'];
10
11
  // Error codes that only mean the connection is no longer usable. See logConnectionError().
11
- const CONNECTION_GONE_CODES = new Set(['NoConnection', 'EConnectionClosed', 'StateLogout']);
12
+ const CONNECTION_GONE_CODES = new Set([ImapFlowErrorCode.NoConnection, ImapFlowErrorCode.EConnectionClosed, ImapFlowErrorCode.StateLogout]);
12
13
  // Upper bound for expanding server-supplied sequence ranges (see expandRange). 2^24
13
14
  // entries in total is far beyond any legitimate mailbox while keeping the worst-case
14
15
  // expansion of a hostile range set bounded.
@@ -1092,7 +1093,7 @@ export function parseBodystructure(entry) {
1092
1093
  curNode.type = 'multipart/' + ((node[i++] || {}).value || '').toString().toLowerCase();
1093
1094
  // extension data (not available for BODY requests)
1094
1095
  // body parameter parenthesized list
1095
- if (i < node.length - 1) {
1096
+ if (i < node.length) {
1096
1097
  if (node[i]) {
1097
1098
  curNode.parameters = getStructuredParams(node[i]);
1098
1099
  }
@@ -1174,7 +1175,7 @@ export function parseBodystructure(entry) {
1174
1175
  }
1175
1176
  // extension data (not available for BODY requests)
1176
1177
  // md5
1177
- if (i < node.length - 1) {
1178
+ if (i < node.length) {
1178
1179
  if (node[i]) {
1179
1180
  curNode.md5 = (node[i].value || '').toString().toLowerCase();
1180
1181
  }
@@ -1184,7 +1185,7 @@ export function parseBodystructure(entry) {
1184
1185
  // the following are shared extension values (for both multipart and non-multipart parts)
1185
1186
  // not available for BODY requests
1186
1187
  // body disposition
1187
- if (i < node.length - 1) {
1188
+ if (i < node.length) {
1188
1189
  let disposition = node[i];
1189
1190
  if (Array.isArray(disposition) && disposition.length) {
1190
1191
  curNode.disposition = ((disposition[0] && disposition[0].value) || '').toString().toLowerCase();
@@ -1195,7 +1196,7 @@ export function parseBodystructure(entry) {
1195
1196
  i++;
1196
1197
  }
1197
1198
  // body language
1198
- if (i < node.length - 1) {
1199
+ if (i < node.length) {
1199
1200
  if (node[i]) {
1200
1201
  /* c8 ignore next */ // node[i] is truthy inside this guard, so the [] fallback is unreachable
1201
1202
  curNode.language = [].concat(node[i] || []).map(val => ((val && val.value) || '').toString().toLowerCase());
@@ -1205,7 +1206,7 @@ export function parseBodystructure(entry) {
1205
1206
  // body location
1206
1207
  // NB! defined as a "string list" in RFC3501 but replaced in errata document with "string"
1207
1208
  // Errata: http://www.rfc-editor.org/errata_search.php?rfc=3501
1208
- if (i < node.length - 1) {
1209
+ if (i < node.length) {
1209
1210
  if (node[i]) {
1210
1211
  curNode.location = (node[i].value || '').toString();
1211
1212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "2.0.8",
3
+ "version": "2.1.0",
4
4
  "description": "IMAP Client for Node",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/imap-flow.js",
@@ -78,7 +78,7 @@
78
78
  "types-node-legacy": "npm:@types/node@20.0.0",
79
79
  "typescript": "6.0.3",
80
80
  "typescript-eslint": "8.70.1",
81
- "wrangler": "4.141.0"
81
+ "wrangler": "4.142.0"
82
82
  },
83
83
  "dependencies": {
84
84
  "@zone-eu/mailsplit": "5.4.17",