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.
Files changed (78) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/commands/append.js +12 -12
  3. package/dist/cjs/commands/authenticate.d.ts +3 -8
  4. package/dist/cjs/commands/close.js +2 -1
  5. package/dist/cjs/commands/copy.js +4 -4
  6. package/dist/cjs/commands/create.js +2 -3
  7. package/dist/cjs/commands/delete.js +4 -4
  8. package/dist/cjs/commands/expunge.js +8 -5
  9. package/dist/cjs/commands/fetch.js +12 -10
  10. package/dist/cjs/commands/idle.js +6 -2
  11. package/dist/cjs/commands/list.js +2 -2
  12. package/dist/cjs/commands/move.js +11 -6
  13. package/dist/cjs/commands/namespace.js +1 -1
  14. package/dist/cjs/commands/quota.js +10 -9
  15. package/dist/cjs/commands/rename.js +4 -4
  16. package/dist/cjs/commands/search.js +7 -8
  17. package/dist/cjs/commands/select.js +5 -6
  18. package/dist/cjs/commands/status.js +11 -11
  19. package/dist/cjs/commands/store.js +5 -5
  20. package/dist/cjs/commands/subscribe.js +2 -17
  21. package/dist/cjs/commands/subscription.d.ts +10 -0
  22. package/dist/cjs/commands/subscription.js +29 -0
  23. package/dist/cjs/commands/unsubscribe.js +2 -17
  24. package/dist/cjs/download.d.ts +22 -0
  25. package/dist/cjs/download.js +588 -0
  26. package/dist/cjs/errors.d.ts +2 -0
  27. package/dist/cjs/handler/imap-compiler.js +1 -1
  28. package/dist/cjs/handler/imap-stream.js +4 -4
  29. package/dist/cjs/handler/parser-instance.js +2 -2
  30. package/dist/cjs/handler/token-parser.js +1 -1
  31. package/dist/cjs/imap-flow.d.ts +16 -7
  32. package/dist/cjs/imap-flow.js +247 -730
  33. package/dist/cjs/jp-decoder.js +1 -1
  34. package/dist/cjs/package-info.d.ts +1 -1
  35. package/dist/cjs/package-info.js +3 -3
  36. package/dist/cjs/search-compiler.js +5 -12
  37. package/dist/cjs/tools.d.ts +51 -10
  38. package/dist/cjs/tools.js +83 -15
  39. package/dist/cjs/types.d.ts +30 -16
  40. package/dist/esm/commands/append.js +13 -13
  41. package/dist/esm/commands/authenticate.d.ts +3 -8
  42. package/dist/esm/commands/close.js +2 -1
  43. package/dist/esm/commands/copy.js +5 -5
  44. package/dist/esm/commands/create.js +3 -4
  45. package/dist/esm/commands/delete.js +5 -5
  46. package/dist/esm/commands/expunge.js +9 -6
  47. package/dist/esm/commands/fetch.js +13 -11
  48. package/dist/esm/commands/idle.js +7 -3
  49. package/dist/esm/commands/list.js +2 -2
  50. package/dist/esm/commands/move.js +12 -7
  51. package/dist/esm/commands/namespace.js +2 -2
  52. package/dist/esm/commands/quota.js +11 -10
  53. package/dist/esm/commands/rename.js +5 -5
  54. package/dist/esm/commands/search.js +8 -9
  55. package/dist/esm/commands/select.js +6 -7
  56. package/dist/esm/commands/status.js +12 -12
  57. package/dist/esm/commands/store.js +6 -6
  58. package/dist/esm/commands/subscribe.js +2 -17
  59. package/dist/esm/commands/subscription.d.ts +10 -0
  60. package/dist/esm/commands/subscription.js +26 -0
  61. package/dist/esm/commands/unsubscribe.js +2 -17
  62. package/dist/esm/download.d.ts +22 -0
  63. package/dist/esm/download.js +581 -0
  64. package/dist/esm/errors.d.ts +2 -0
  65. package/dist/esm/handler/imap-compiler.js +1 -1
  66. package/dist/esm/handler/imap-stream.js +4 -4
  67. package/dist/esm/handler/parser-instance.js +2 -2
  68. package/dist/esm/handler/token-parser.js +1 -1
  69. package/dist/esm/imap-flow.d.ts +16 -7
  70. package/dist/esm/imap-flow.js +248 -731
  71. package/dist/esm/jp-decoder.js +1 -1
  72. package/dist/esm/package-info.d.ts +1 -1
  73. package/dist/esm/package-info.js +3 -3
  74. package/dist/esm/search-compiler.js +5 -12
  75. package/dist/esm/tools.d.ts +51 -10
  76. package/dist/esm/tools.js +78 -15
  77. package/dist/esm/types.d.ts +30 -16
  78. package/package.json +4 -4
@@ -37,7 +37,7 @@ class JPDecoder extends node_stream_1.Transform {
37
37
  chunk = Buffer.from(chunk, encoding);
38
38
  }
39
39
  if (this.chunklen + chunk.length > this.maxBytes) {
40
- chunk = chunk.slice(0, Math.max(0, this.maxBytes - this.chunklen));
40
+ chunk = chunk.subarray(0, Math.max(0, this.maxBytes - this.chunklen));
41
41
  }
42
42
  if (chunk.length) {
43
43
  this.chunks.push(chunk);
@@ -1,3 +1,3 @@
1
1
  export declare const name = "imapflow";
2
- export declare const version = "2.0.6";
2
+ export declare const version = "2.0.8";
3
3
  export declare const homepage = "https://imapflow.com/";
@@ -2,6 +2,6 @@
2
2
  // Generated by scripts/build.js from package.json. Do not edit by hand.
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.homepage = exports.version = exports.name = void 0;
5
- exports.name = 'imapflow';
6
- exports.version = '2.0.6';
7
- exports.homepage = 'https://imapflow.com/';
5
+ exports.name = "imapflow";
6
+ exports.version = "2.0.8";
7
+ exports.homepage = "https://imapflow.com/";
@@ -406,21 +406,14 @@ const searchCompiler = (connection, query) => {
406
406
  * @returns Binary tree structure
407
407
  */
408
408
  let genOrTree = (list) => {
409
- let group = false;
410
409
  let groups = [];
411
410
  // Group items in pairs
412
- list.forEach((entry, i) => {
413
- if (i % 2 === 0) {
414
- group = [entry];
415
- }
416
- else {
417
- group.push(entry);
418
- groups.push(group);
419
- group = false;
420
- }
421
- });
411
+ for (let i = 0; i + 1 < list.length; i += 2) {
412
+ groups.push([list[i], list[i + 1]]);
413
+ }
422
414
  // Handle odd number of items
423
- if (group && group.length) {
415
+ if (list.length % 2) {
416
+ let group = [list[list.length - 1]];
424
417
  while (group.length === 1 && Array.isArray(group[0])) {
425
418
  group = group[0];
426
419
  }
@@ -2,7 +2,7 @@ import type { Transform } from 'node:stream';
2
2
  import type { ImapFlow } from './imap-flow.js';
3
3
  import type { ConnectionErrorSite, ImapFlowError } from './errors.js';
4
4
  import type { ImapAttributeList, ImapAttributeNode, ImapResponse } from './handler/types.js';
5
- import type { FetchMessageObject, ListResponse, ListTreeResponse, MailboxObject, MessageEnvelopeObject, MessageStructureObject, StatusQuery } from './types.js';
5
+ import type { FetchMessageObject, ListResponse, ListTreeResponse, MailboxObject, MessageEnvelopeObject, MessageStructureObject, ImapFlowEvents, StatusQuery } from './types.js';
6
6
  export { AuthenticationFailure } from './errors.js';
7
7
  export declare const EXPANDED_RANGE_LIMIT = 16777216;
8
8
  export declare const MAX_UINT32_DIGITS = 10;
@@ -80,6 +80,13 @@ export declare function guardedPromise<T>(executor: (resolve: (value: T | Promis
80
80
  * @returns Rejected promise, with its rejection already observed
81
81
  */
82
82
  export declare function guardedReject(error: Error): Promise<never>;
83
+ /**
84
+ * Clears a timer that may already have been dropped. `clearTimeout()` accepts undefined but not
85
+ * null, and the connection nulls its timer fields once cleared, so every site clears through here.
86
+ *
87
+ * @param timer - Timer handle returned by setTimeout, or null/undefined when none is armed
88
+ */
89
+ export declare function clearTimer(timer: NodeJS.Timeout | null | undefined): void;
83
90
  /**
84
91
  * Detaches a background timer from the event loop, so it cannot keep the process alive on its
85
92
  * own. Applied to every background timer (auto-IDLE, IDLE restart, fallback polling, throttle
@@ -89,13 +96,6 @@ export declare function guardedReject(error: Error): Promise<never>;
89
96
  * @param timer - Timer handle returned by setTimeout
90
97
  * @returns The same timer handle
91
98
  */
92
- /**
93
- * Clears a timer that may already have been dropped. `clearTimeout()` accepts undefined but not
94
- * null, and the connection nulls its timer fields once cleared, so every site clears through here.
95
- *
96
- * @param timer - Timer handle returned by setTimeout, or null/undefined when none is armed
97
- */
98
- export declare function clearTimer(timer: NodeJS.Timeout | null | undefined): void;
99
99
  export declare function unrefTimer<T extends NodeJS.Timeout | null | undefined>(timer: T): T;
100
100
  /**
101
101
  * Logs a failure from background connection work at the level its cause deserves.
@@ -200,6 +200,25 @@ export declare function updateCapabilities(list: ImapAttributeList | null | unde
200
200
  * @returns Uppercase status code string, or false if not found
201
201
  */
202
202
  export declare function getStatusCode(response: ImapResponse | string | false | undefined): string | false;
203
+ /**
204
+ * Emits a state-change event from inside the command pipeline. A listener that throws must not
205
+ * abort the code that emitted it: select() emits before it releases the response, so the throw
206
+ * would leave the reader loop waiting forever, and close() would never get to emit 'close'. The
207
+ * error is logged instead, the same contract untagged handlers and the 'response' event get.
208
+ *
209
+ * @param connection - IMAP connection instance
210
+ * @param event - Event name
211
+ * @param args - Event arguments
212
+ */
213
+ export declare function emitSafe<K extends keyof ImapFlowEvents>(connection: ImapFlow, event: K, ...args: ImapFlowEvents[K]): void;
214
+ /**
215
+ * Collects the values of the TEXT tokens of a parsed response (the human-readable
216
+ * part of a status response, a greeting or a BYE).
217
+ *
218
+ * @param attributes - Attributes of a parsed IMAP response
219
+ * @returns Values of the TEXT tokens, in order
220
+ */
221
+ export declare function getTextValues(attributes: ImapAttributeList | undefined): string[];
203
222
  /**
204
223
  * Compiles an IMAP response object back into a human-readable string.
205
224
  *
@@ -214,6 +233,28 @@ export declare function getErrorText(response: ImapResponse | string | false | u
214
233
  * @returns The enhanced error with `serverResponseCode` and string `response`
215
234
  */
216
235
  export declare function enhanceCommandError(err: ImapFlowError): Promise<ImapFlowError>;
236
+ /**
237
+ * Enhances a failed command's error (see enhanceCommandError()) and logs it, the shared first
238
+ * step of every command's failure path. The caller decides whether to throw or return.
239
+ *
240
+ * @param connection - IMAP connection instance
241
+ * @param err - The command error
242
+ */
243
+ export declare function reportCommandError(connection: ImapFlow, err: ImapFlowError): Promise<void>;
244
+ /**
245
+ * Whether the session is authenticated, that is in the AUTHENTICATED or SELECTED state, which
246
+ * every mailbox-level command requires.
247
+ *
248
+ * @param connection - IMAP connection instance
249
+ */
250
+ export declare function isAuthenticatedState(connection: ImapFlow): boolean;
251
+ /**
252
+ * Returns the selected mailbox, or false when the connection is not in the SELECTED state.
253
+ * Message-level commands use it as their precondition, which also narrows the mailbox type.
254
+ *
255
+ * @param connection - IMAP connection instance
256
+ */
257
+ export declare function getSelectedMailbox(connection: ImapFlow): MailboxObject | false;
217
258
  /**
218
259
  * Converts a flat list of mailbox folders into a tree structure.
219
260
  *
@@ -305,14 +346,14 @@ export declare function toValidDate(value: unknown): Date | null;
305
346
  * @param value - Date to format
306
347
  * @returns Formatted date string, or undefined if invalid
307
348
  */
308
- export declare function formatDate(value: Date | string | null | undefined): string | undefined;
349
+ export declare function formatDate(value: unknown): string | undefined;
309
350
  /**
310
351
  * Formats a date value into IMAP date-time format (DD-Mon-YYYY HH:MM:SS +0000).
311
352
  *
312
353
  * @param value - Date to format
313
354
  * @returns Formatted date-time string, or undefined if invalid
314
355
  */
315
- export declare function formatDateTime(value: Date | string | null | undefined): string | undefined;
356
+ export declare function formatDateTime(value: unknown): string | undefined;
316
357
  /**
317
358
  * Normalizes a flag string. Returns false for non-settable flags (e.g. \Recent),
318
359
  * and capitalizes system flags properly.
package/dist/cjs/tools.js CHANGED
@@ -21,8 +21,13 @@ exports.normalizePath = normalizePath;
21
21
  exports.comparePaths = comparePaths;
22
22
  exports.updateCapabilities = updateCapabilities;
23
23
  exports.getStatusCode = getStatusCode;
24
+ exports.emitSafe = emitSafe;
25
+ exports.getTextValues = getTextValues;
24
26
  exports.getErrorText = getErrorText;
25
27
  exports.enhanceCommandError = enhanceCommandError;
28
+ exports.reportCommandError = reportCommandError;
29
+ exports.isAuthenticatedState = isAuthenticatedState;
30
+ exports.getSelectedMailbox = getSelectedMailbox;
26
31
  exports.getFolderTree = getFolderTree;
27
32
  exports.getFlagColor = getFlagColor;
28
33
  exports.getColorFlags = getColorFlags;
@@ -208,15 +213,6 @@ function guardedReject(error) {
208
213
  promise.catch(exports.noop);
209
214
  return promise;
210
215
  }
211
- /**
212
- * Detaches a background timer from the event loop, so it cannot keep the process alive on its
213
- * own. Applied to every background timer (auto-IDLE, IDLE restart, fallback polling, throttle
214
- * back-off, held-lock diagnostics); connection and greeting deadlines are deliberately left
215
- * attached, because a caller is waiting for connect() to settle.
216
- *
217
- * @param timer - Timer handle returned by setTimeout
218
- * @returns The same timer handle
219
- */
220
216
  /**
221
217
  * Clears a timer that may already have been dropped. `clearTimeout()` accepts undefined but not
222
218
  * null, and the connection nulls its timer fields once cleared, so every site clears through here.
@@ -228,6 +224,15 @@ function clearTimer(timer) {
228
224
  clearTimeout(timer);
229
225
  }
230
226
  }
227
+ /**
228
+ * Detaches a background timer from the event loop, so it cannot keep the process alive on its
229
+ * own. Applied to every background timer (auto-IDLE, IDLE restart, fallback polling, throttle
230
+ * back-off, held-lock diagnostics); connection and greeting deadlines are deliberately left
231
+ * attached, because a caller is waiting for connect() to settle.
232
+ *
233
+ * @param timer - Timer handle returned by setTimeout
234
+ * @returns The same timer handle
235
+ */
231
236
  function unrefTimer(timer) {
232
237
  /* c8 ignore next 3 */ // node timers always expose unref(); the guard covers replaced globals in tests
233
238
  if (timer && typeof timer.unref === 'function') {
@@ -443,7 +448,7 @@ function updateCapabilities(list) {
443
448
  }
444
449
  if (capability.startsWith('APPENDLIMIT=')) {
445
450
  let splitPos = capability.indexOf('=');
446
- map.set('APPENDLIMIT', parseUintValue(capability.substr(splitPos + 1)) || 0);
451
+ map.set('APPENDLIMIT', parseUintValue(capability.slice(splitPos + 1)) || 0);
447
452
  return;
448
453
  }
449
454
  map.set(capability, true);
@@ -469,6 +474,34 @@ function getStatusCode(response) {
469
474
  ? response.attributes[0].section[0].value.toUpperCase().trim()
470
475
  : false;
471
476
  }
477
+ /**
478
+ * Emits a state-change event from inside the command pipeline. A listener that throws must not
479
+ * abort the code that emitted it: select() emits before it releases the response, so the throw
480
+ * would leave the reader loop waiting forever, and close() would never get to emit 'close'. The
481
+ * error is logged instead, the same contract untagged handlers and the 'response' event get.
482
+ *
483
+ * @param connection - IMAP connection instance
484
+ * @param event - Event name
485
+ * @param args - Event arguments
486
+ */
487
+ function emitSafe(connection, event, ...args) {
488
+ try {
489
+ connection.emit(event, ...args);
490
+ }
491
+ catch (err) {
492
+ connection.log.warn({ msg: 'Event listener failed', event, err, cid: connection.id });
493
+ }
494
+ }
495
+ /**
496
+ * Collects the values of the TEXT tokens of a parsed response (the human-readable
497
+ * part of a status response, a greeting or a BYE).
498
+ *
499
+ * @param attributes - Attributes of a parsed IMAP response
500
+ * @returns Values of the TEXT tokens, in order
501
+ */
502
+ function getTextValues(attributes) {
503
+ return (attributes || []).filter(attr => attr?.type === 'TEXT').map(attr => String(attr?.value ?? ''));
504
+ }
472
505
  /**
473
506
  * Compiles an IMAP response object back into a human-readable string.
474
507
  *
@@ -506,6 +539,35 @@ async function enhanceCommandError(err) {
506
539
  err.response = await getErrorText(err.response);
507
540
  return err;
508
541
  }
542
+ /**
543
+ * Enhances a failed command's error (see enhanceCommandError()) and logs it, the shared first
544
+ * step of every command's failure path. The caller decides whether to throw or return.
545
+ *
546
+ * @param connection - IMAP connection instance
547
+ * @param err - The command error
548
+ */
549
+ async function reportCommandError(connection, err) {
550
+ await enhanceCommandError(err);
551
+ connection.log.warn({ err, cid: connection.id });
552
+ }
553
+ /**
554
+ * Whether the session is authenticated, that is in the AUTHENTICATED or SELECTED state, which
555
+ * every mailbox-level command requires.
556
+ *
557
+ * @param connection - IMAP connection instance
558
+ */
559
+ function isAuthenticatedState(connection) {
560
+ return connection.state === connection.states.AUTHENTICATED || connection.state === connection.states.SELECTED;
561
+ }
562
+ /**
563
+ * Returns the selected mailbox, or false when the connection is not in the SELECTED state.
564
+ * Message-level commands use it as their precondition, which also narrows the mailbox type.
565
+ *
566
+ * @param connection - IMAP connection instance
567
+ */
568
+ function getSelectedMailbox(connection) {
569
+ return connection.state === connection.states.SELECTED && connection.mailbox ? connection.mailbox : false;
570
+ }
509
571
  /**
510
572
  * Converts a flat list of mailbox folders into a tree structure.
511
573
  *
@@ -681,6 +743,12 @@ async function formatMessageResponse(untagged, mailbox) {
681
743
  if (Buffer.isBuffer(attribute.value)) {
682
744
  return attribute.value;
683
745
  }
746
+ // A section is an nstring, so a server may answer with a quoted string
747
+ // instead of a literal (Yahoo does for small parts). The tokenizer decoded
748
+ // the line as UTF-8, so encoding the same way restores the bytes it sent.
749
+ if (typeof attribute.value === 'string') {
750
+ return Buffer.from(attribute.value);
751
+ }
684
752
  };
685
753
  // NIL (parsed as null) and other non-array values yield an empty array, so callers
686
754
  // can safely index into the result. RFC 8474 allows e.g. `THREADID NIL` when the
@@ -987,7 +1055,7 @@ function getStructuredParams(arr) {
987
1055
  // nothing to do here, does not seem like a continuation param
988
1056
  return;
989
1057
  }
990
- actualKey = key.substr(0, match.index).toLowerCase();
1058
+ actualKey = key.substring(0, match.index).toLowerCase();
991
1059
  nr = Number(match[2]) || 0;
992
1060
  if (isUnsafeKey(actualKey)) {
993
1061
  // A continuation key like "__proto__*0*" would group under "__proto__":
@@ -1239,7 +1307,7 @@ function formatDate(value) {
1239
1307
  if (!date) {
1240
1308
  return;
1241
1309
  }
1242
- let dateParts = date.toISOString().substr(0, 10).split('-');
1310
+ let dateParts = date.toISOString().substring(0, 10).split('-');
1243
1311
  dateParts.reverse();
1244
1312
  let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
1245
1313
  dateParts[1] = months[Number(dateParts[1]) - 1];
@@ -1257,7 +1325,7 @@ function formatDateTime(value) {
1257
1325
  return;
1258
1326
  }
1259
1327
  let dateStr = formatDate(date).replace(/^0/, ' '); //starts with date-day-fixed with leading 0 replaced by SP
1260
- let timeStr = date.toISOString().substr(11, 8);
1328
+ let timeStr = date.toISOString().substring(11, 19);
1261
1329
  return `${dateStr} ${timeStr} +0000`;
1262
1330
  }
1263
1331
  /**
@@ -1411,8 +1479,8 @@ function expandRange(range) {
1411
1479
  }
1412
1480
  continue;
1413
1481
  }
1414
- let first = Number(entry.substr(0, colon));
1415
- let second = Number(entry.substr(colon + 1));
1482
+ let first = Number(entry.substring(0, colon));
1483
+ let second = Number(entry.slice(colon + 1));
1416
1484
  if (!isValidSequenceValue(first) || !isValidSequenceValue(second)) {
1417
1485
  continue;
1418
1486
  }
@@ -207,26 +207,32 @@ export interface IdInfoObject {
207
207
  'support-url'?: string | false | undefined;
208
208
  /** Date program was released */
209
209
  date?: Date | string | false | undefined;
210
- [key: string]: any;
210
+ /** Any other field, sent as its string form */
211
+ [key: string]: unknown;
212
+ }
213
+ /**
214
+ * Usage and limit of one quota resource. The `storage` values are bytes (the wire format is
215
+ * kilobytes), the other resources are counts
216
+ */
217
+ export interface QuotaResource {
218
+ /** Current usage, missing when the server did not send a usable number */
219
+ usage?: number | undefined;
220
+ /** The limit, missing when the server did not send a usable number */
221
+ limit?: number | undefined;
222
+ /** Usage as a percentage of the limit, e.g. "50%", set once a limit above zero is known */
223
+ status?: string | undefined;
211
224
  }
212
225
  export interface QuotaResponse {
213
226
  /** Mailbox path this quota applies to */
214
227
  path: string;
215
- /** Storage quota if provided by server */
216
- storage?: {
217
- /** Used storage in bytes */
218
- used: number;
219
- /** Total storage available */
220
- limit: number;
221
- } | undefined;
222
- /** Message count quota if provided by server */
223
- messages?: {
224
- /** Stored messages */
225
- used: number;
226
- /** Maximum messages allowed */
227
- limit: number;
228
- } | undefined;
229
- [resource: string]: any;
228
+ /** The quota root the server reported for the mailbox, if any */
229
+ quotaRoot?: string | undefined;
230
+ /** The STORAGE resource, if the server reports one */
231
+ storage?: QuotaResource | undefined;
232
+ /** The MESSAGE resource, if the server reports one */
233
+ message?: QuotaResource | undefined;
234
+ /** Any other resource the server reports, under its lowercased name (e.g. "mailbox") */
235
+ [resource: string]: QuotaResource | string | undefined;
230
236
  }
231
237
  /**
232
238
  * Status data items to request with `status()` or the `statusQuery` listing option
@@ -616,6 +622,14 @@ export interface DownloadObject {
616
622
  /** Streamed content */
617
623
  content: Readable;
618
624
  }
625
+ /**
626
+ * What `download()` resolves with when there is nothing to download: no mailbox is selected, or
627
+ * the message or part was not found. Check `content` before using the result.
628
+ */
629
+ export interface DownloadNotFound {
630
+ meta?: undefined;
631
+ content?: undefined;
632
+ }
619
633
  export interface DownloadOptions {
620
634
  /** If true then uses UID number instead of sequence number for `range` */
621
635
  uid?: boolean | undefined;
@@ -1,4 +1,4 @@
1
- import { formatFlag, canUseFlag, formatDateTime, normalizePath, encodePath, comparePaths, enhanceCommandError, parseBigIntValue, parseUintValue, MAX_UINT32_DIGITS } from '../tools.js';
1
+ import { formatFlag, canUseFlag, formatDateTime, normalizePath, encodePath, comparePaths, parseBigIntValue, parseUintValue, MAX_UINT32_DIGITS, getSelectedMailbox, emitSafe, isAuthenticatedState, reportCommandError } from '../tools.js';
2
2
  /**
3
3
  * Appends a message to a mailbox.
4
4
  *
@@ -11,7 +11,7 @@ import { formatFlag, canUseFlag, formatDateTime, normalizePath, encodePath, comp
11
11
  * @throws {Error} If the APPEND command fails or message exceeds APPENDLIMIT
12
12
  */
13
13
  export default async function append(connection, destination, content, flags, idate) {
14
- if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state) || !destination) {
14
+ if (!isAuthenticatedState(connection) || !destination) {
15
15
  // nothing to do here
16
16
  return;
17
17
  }
@@ -31,7 +31,9 @@ export default async function append(connection, destination, content, flags, id
31
31
  destination = normalizePath(connection, destination);
32
32
  // If appending to the currently selected mailbox, we can listen for the
33
33
  // untagged EXISTS response to capture the new message's sequence number.
34
- let expectExists = comparePaths(connection, connection.mailbox.path, destination);
34
+ let selected = getSelectedMailbox(connection);
35
+ // The selected mailbox when appending to it, false otherwise
36
+ const targetMailbox = selected && comparePaths(connection, selected.path, destination) ? selected : false;
35
37
  // Validate and format flags. Only flags allowed by the mailbox's permanentFlags are included.
36
38
  flags = (Array.isArray(flags) ? flags : [].concat(flags || []))
37
39
  .map(flag => flag && formatFlag(flag.toString()))
@@ -74,13 +76,12 @@ export default async function append(connection, destination, content, flags, id
74
76
  map.seq = seq;
75
77
  // Update the connection's mailbox state and emit 'exists' event if the
76
78
  // count changed (notifies listeners about the new message).
77
- if (expectExists) {
78
- let mailbox = connection.mailbox;
79
- let prevCount = mailbox.exists;
79
+ if (targetMailbox) {
80
+ let prevCount = targetMailbox.exists;
80
81
  if (map.seq !== prevCount) {
81
- mailbox.exists = map.seq;
82
- connection.emit('exists', {
83
- path: mailbox.path,
82
+ targetMailbox.exists = map.seq;
83
+ emitSafe(connection, 'exists', {
84
+ path: targetMailbox.path,
84
85
  count: map.seq,
85
86
  prevCount
86
87
  });
@@ -91,7 +92,7 @@ export default async function append(connection, destination, content, flags, id
91
92
  try {
92
93
  response = await connection.exec('APPEND', attributes, {
93
94
  // Only listen for EXISTS if we're appending to the currently selected mailbox
94
- untagged: expectExists ? { EXISTS: handleExistsUpdate } : false
95
+ untagged: targetMailbox ? { EXISTS: handleExistsUpdate } : false
95
96
  });
96
97
  // UIDPLUS (RFC 4315): the server may include APPENDUID response code in
97
98
  // the tagged OK. Format: [APPENDUID <uidValidity> <uid>]
@@ -116,7 +117,7 @@ export default async function append(connection, destination, content, flags, id
116
117
  response.next();
117
118
  // If we didn't get an EXISTS during APPEND (some servers don't send it
118
119
  // until the next command), issue a NOOP to flush pending notifications.
119
- if (expectExists && !map.seq) {
120
+ if (targetMailbox && !map.seq) {
120
121
  try {
121
122
  response = await connection.exec('NOOP', false, {
122
123
  untagged: { EXISTS: handleExistsUpdate },
@@ -139,8 +140,7 @@ export default async function append(connection, destination, content, flags, id
139
140
  return map;
140
141
  }
141
142
  catch (err) {
142
- await enhanceCommandError(err);
143
- connection.log.warn({ err, cid: connection.id });
143
+ await reportCommandError(connection, err);
144
144
  throw err;
145
145
  }
146
146
  }
@@ -1,16 +1,11 @@
1
1
  import type { ImapFlow } from '../imap-flow.js';
2
+ import type { AuthOptions } from '../types.js';
2
3
  /**
3
- * Credentials for the AUTHENTICATE command
4
+ * Credentials for the AUTHENTICATE command: the auth options, with the password passed as `password`
4
5
  */
5
- export interface AuthenticateCredentials {
6
- /** OAuth2 access token for OAUTHBEARER/XOAUTH2 authentication */
7
- accessToken?: string | undefined;
6
+ export interface AuthenticateCredentials extends Omit<AuthOptions, 'user' | 'pass'> {
8
7
  /** Password for PLAIN or LOGIN authentication */
9
8
  password?: string | undefined;
10
- /** Force a specific login method (e.g., 'AUTH=PLAIN', 'AUTH=LOGIN') */
11
- loginMethod?: string | undefined;
12
- /** Authorization identity for PLAIN authentication */
13
- authzid?: string | undefined;
14
9
  }
15
10
  /**
16
11
  * Authenticates user using the best available method.
@@ -1,3 +1,4 @@
1
+ import { emitSafe } from '../tools.js';
1
2
  /**
2
3
  * Closes the currently selected mailbox.
3
4
  *
@@ -23,7 +24,7 @@ export default async function close(connection) {
23
24
  connection.currentSelectCommand = false;
24
25
  connection.state = connection.states.AUTHENTICATED;
25
26
  if (currentMailbox) {
26
- connection.emit('mailboxClose', currentMailbox);
27
+ emitSafe(connection, 'mailboxClose', currentMailbox);
27
28
  }
28
29
  return true;
29
30
  }
@@ -1,4 +1,4 @@
1
- import { normalizePath, encodePath, enhanceCommandError } from '../tools.js';
1
+ import { normalizePath, encodePath, reportCommandError, getSelectedMailbox } from '../tools.js';
2
2
  import { parseCopyUid } from './copyuid-parser.js';
3
3
  /**
4
4
  * Copies messages from the current mailbox to another mailbox.
@@ -11,7 +11,8 @@ import { parseCopyUid } from './copyuid-parser.js';
11
11
  * @returns Copy result with UID mapping if available, false on failure, or undefined if preconditions not met
12
12
  */
13
13
  export default async function copy(connection, range, destination, options) {
14
- if (connection.state !== connection.states.SELECTED || !range || !destination) {
14
+ let mailbox = getSelectedMailbox(connection);
15
+ if (!mailbox || !range || !destination) {
15
16
  // nothing to do here
16
17
  return;
17
18
  }
@@ -25,15 +26,14 @@ export default async function copy(connection, range, destination, options) {
25
26
  try {
26
27
  response = await connection.exec(options.uid ? 'UID COPY' : 'COPY', attributes);
27
28
  response.next();
28
- let map = { path: connection.mailbox.path, destination };
29
+ let map = { path: mailbox.path, destination };
29
30
  // UIDPLUS (RFC 4315): the server may include a COPYUID response code in the
30
31
  // tagged OK response, providing a mapping from source UIDs to destination UIDs.
31
32
  parseCopyUid(response.response, map);
32
33
  return map;
33
34
  }
34
35
  catch (err) {
35
- await enhanceCommandError(err);
36
- connection.log.warn({ err, cid: connection.id });
36
+ await reportCommandError(connection, err);
37
37
  return false;
38
38
  }
39
39
  }
@@ -1,4 +1,4 @@
1
- import { encodePath, normalizePath, getStatusCode, enhanceCommandError } from '../tools.js';
1
+ import { encodePath, normalizePath, getStatusCode, isAuthenticatedState, reportCommandError } from '../tools.js';
2
2
  /**
3
3
  * Creates a new mailbox and subscribes to it.
4
4
  *
@@ -8,7 +8,7 @@ import { encodePath, normalizePath, getStatusCode, enhanceCommandError } from '.
8
8
  * @throws If the CREATE command fails (except when mailbox already exists)
9
9
  */
10
10
  export default async function create(connection, path) {
11
- if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state)) {
11
+ if (!isAuthenticatedState(connection)) {
12
12
  // nothing to do here
13
13
  return;
14
14
  }
@@ -68,8 +68,7 @@ export default async function create(connection, path) {
68
68
  created: false
69
69
  };
70
70
  }
71
- await enhanceCommandError(err);
72
- connection.log.warn({ err, cid: connection.id });
71
+ await reportCommandError(connection, err);
73
72
  throw err;
74
73
  }
75
74
  }
@@ -1,4 +1,4 @@
1
- import { encodePath, normalizePath, enhanceCommandError } from '../tools.js';
1
+ import { encodePath, normalizePath, isAuthenticatedState, reportCommandError, getSelectedMailbox } from '../tools.js';
2
2
  /**
3
3
  * Deletes an existing mailbox.
4
4
  *
@@ -8,14 +8,15 @@ import { encodePath, normalizePath, enhanceCommandError } from '../tools.js';
8
8
  * @throws If the DELETE command fails
9
9
  */
10
10
  export default async function deleteMailbox(connection, path) {
11
- if (![connection.states.AUTHENTICATED, connection.states.SELECTED].includes(connection.state)) {
11
+ if (!isAuthenticatedState(connection)) {
12
12
  // nothing to do here
13
13
  return;
14
14
  }
15
15
  path = normalizePath(connection, path);
16
16
  // If the mailbox to delete is currently selected, we must close/deselect it first.
17
17
  // IMAP servers reject DELETE on the currently selected mailbox (RFC 3501 6.3.4).
18
- if (connection.state === connection.states.SELECTED && connection.mailbox.path === path) {
18
+ let selected = getSelectedMailbox(connection);
19
+ if (selected && selected.path === path) {
19
20
  await connection.run('CLOSE');
20
21
  }
21
22
  let response;
@@ -28,8 +29,7 @@ export default async function deleteMailbox(connection, path) {
28
29
  return map;
29
30
  }
30
31
  catch (err) {
31
- await enhanceCommandError(err);
32
- connection.log.warn({ err, cid: connection.id });
32
+ await reportCommandError(connection, err);
33
33
  throw err;
34
34
  }
35
35
  }
@@ -1,4 +1,4 @@
1
- import { enhanceCommandError, hasCapability, parseBigIntValue } from '../tools.js';
1
+ import { hasCapability, parseBigIntValue, reportCommandError, getSelectedMailbox } from '../tools.js';
2
2
  /**
3
3
  * Deletes specified messages by flagging them as Deleted and expunging.
4
4
  *
@@ -9,14 +9,19 @@ import { enhanceCommandError, hasCapability, parseBigIntValue } from '../tools.j
9
9
  * @returns True on success, false on failure, or undefined if preconditions not met
10
10
  */
11
11
  export default async function expunge(connection, range, options) {
12
- if (connection.state !== connection.states.SELECTED || !range) {
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
18
  // Two-step deletion process per IMAP protocol:
18
19
  // Step 1: Mark the target messages with the \Deleted flag.
19
- await connection.messageFlagsAdd(range, ['\\Deleted'], options);
20
+ // If that failed, EXPUNGE would not remove the target messages (and without
21
+ // UIDPLUS it would remove unrelated \Deleted messages), so report the failure instead.
22
+ if (!(await connection.messageFlagsAdd(range, ['\\Deleted'], options))) {
23
+ return false;
24
+ }
20
25
  // Step 2: Issue EXPUNGE to permanently remove \Deleted messages.
21
26
  // With UIDPLUS (RFC 4315): "UID EXPUNGE <uids>" removes only the specified UIDs,
22
27
  // leaving other \Deleted messages untouched, important for concurrent access.
@@ -35,7 +40,6 @@ export default async function expunge(connection, range, options) {
35
40
  if (responseCode.toUpperCase() === 'HIGHESTMODSEQ') {
36
41
  // A response code always comes with its section, see responseCode above
37
42
  let codeSection = section;
38
- let mailbox = connection.mailbox;
39
43
  // Bounded digit runs only: isNaN() also passes '1e5', which BigInt() rejects with
40
44
  // a throw that the catch below would swallow, making messageDelete() report false
41
45
  // even though the server expunged the messages.
@@ -48,8 +52,7 @@ export default async function expunge(connection, range, options) {
48
52
  return true;
49
53
  }
50
54
  catch (err) {
51
- await enhanceCommandError(err);
52
- connection.log.warn({ err, cid: connection.id });
55
+ await reportCommandError(connection, err);
53
56
  return false;
54
57
  }
55
58
  }