imapflow 1.7.8 → 2.0.1

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 (296) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +8 -2
  3. package/dist/cjs/charsets.d.ts +1 -0
  4. package/dist/cjs/charsets.js +294 -0
  5. package/dist/cjs/commands/append.d.ts +22 -0
  6. package/dist/cjs/commands/append.js +151 -0
  7. package/dist/cjs/commands/authenticate.d.ts +24 -0
  8. package/dist/cjs/commands/authenticate.js +223 -0
  9. package/dist/cjs/commands/capability.d.ts +8 -0
  10. package/dist/cjs/commands/capability.js +32 -0
  11. package/dist/cjs/commands/close.d.ts +8 -0
  12. package/dist/cjs/commands/close.js +39 -0
  13. package/dist/cjs/commands/compress.d.ts +8 -0
  14. package/dist/cjs/commands/compress.js +56 -0
  15. package/dist/cjs/commands/copy.d.ts +13 -0
  16. package/dist/cjs/commands/copy.js +44 -0
  17. package/dist/cjs/commands/copyuid-parser.d.ts +11 -0
  18. package/dist/cjs/commands/copyuid-parser.js +32 -0
  19. package/dist/cjs/commands/create.d.ts +11 -0
  20. package/dist/cjs/commands/create.js +80 -0
  21. package/dist/cjs/commands/delete.d.ts +11 -0
  22. package/dist/cjs/commands/delete.js +40 -0
  23. package/dist/cjs/commands/enable.d.ts +9 -0
  24. package/dist/cjs/commands/enable.js +61 -0
  25. package/dist/cjs/commands/esearch-parser.d.ts +17 -0
  26. package/dist/cjs/commands/esearch-parser.js +91 -0
  27. package/dist/cjs/commands/expunge.d.ts +12 -0
  28. package/dist/cjs/commands/expunge.js +60 -0
  29. package/dist/cjs/commands/fetch.d.ts +30 -0
  30. package/dist/cjs/commands/fetch.js +241 -0
  31. package/dist/cjs/commands/id.d.ts +10 -0
  32. package/dist/cjs/commands/id.js +80 -0
  33. package/dist/cjs/commands/idle.d.ts +9 -0
  34. package/dist/cjs/commands/idle.js +347 -0
  35. package/dist/cjs/commands/list.d.ts +16 -0
  36. package/dist/cjs/commands/list.js +520 -0
  37. package/dist/cjs/commands/login.d.ts +11 -0
  38. package/dist/cjs/commands/login.js +42 -0
  39. package/dist/cjs/commands/logout.d.ts +8 -0
  40. package/dist/cjs/commands/logout.js +47 -0
  41. package/dist/cjs/commands/move.d.ts +13 -0
  42. package/dist/cjs/commands/move.js +57 -0
  43. package/dist/cjs/commands/namespace.d.ts +25 -0
  44. package/dist/cjs/commands/namespace.js +139 -0
  45. package/dist/cjs/commands/noop.d.ts +8 -0
  46. package/dist/cjs/commands/noop.js +22 -0
  47. package/dist/cjs/commands/quota.d.ts +10 -0
  48. package/dist/cjs/commands/quota.js +119 -0
  49. package/dist/cjs/commands/rename.d.ts +12 -0
  50. package/dist/cjs/commands/rename.js +48 -0
  51. package/dist/cjs/commands/search.d.ts +15 -0
  52. package/dist/cjs/commands/search.js +228 -0
  53. package/dist/cjs/commands/select.d.ts +25 -0
  54. package/dist/cjs/commands/select.js +250 -0
  55. package/dist/cjs/commands/starttls.d.ts +8 -0
  56. package/dist/cjs/commands/starttls.js +30 -0
  57. package/dist/cjs/commands/status-fields.d.ts +14 -0
  58. package/dist/cjs/commands/status-fields.js +61 -0
  59. package/dist/cjs/commands/status.d.ts +12 -0
  60. package/dist/cjs/commands/status.js +108 -0
  61. package/dist/cjs/commands/store.d.ts +19 -0
  62. package/dist/cjs/commands/store.js +93 -0
  63. package/dist/cjs/commands/subscribe.d.ts +9 -0
  64. package/dist/cjs/commands/subscribe.js +31 -0
  65. package/dist/cjs/commands/unsubscribe.d.ts +9 -0
  66. package/dist/cjs/commands/unsubscribe.js +31 -0
  67. package/dist/cjs/connection-deadline.d.ts +49 -0
  68. package/dist/cjs/connection-deadline.js +91 -0
  69. package/dist/cjs/errors.d.ts +83 -0
  70. package/dist/cjs/errors.js +13 -0
  71. package/dist/cjs/handler/imap-compiler.d.ts +24 -0
  72. package/dist/cjs/handler/imap-compiler.js +285 -0
  73. package/dist/cjs/handler/imap-formal-syntax.d.ts +28 -0
  74. package/dist/cjs/handler/imap-formal-syntax.js +121 -0
  75. package/dist/cjs/handler/imap-handler.d.ts +9 -0
  76. package/dist/cjs/handler/imap-handler.js +10 -0
  77. package/dist/cjs/handler/imap-parser.d.ts +16 -0
  78. package/dist/cjs/handler/imap-parser.js +90 -0
  79. package/dist/cjs/handler/imap-stream.d.ts +181 -0
  80. package/dist/cjs/handler/imap-stream.js +446 -0
  81. package/dist/cjs/handler/limits.d.ts +25 -0
  82. package/dist/cjs/handler/limits.js +51 -0
  83. package/dist/cjs/handler/parser-instance.d.ts +68 -0
  84. package/dist/cjs/handler/parser-instance.js +223 -0
  85. package/dist/cjs/handler/token-parser.d.ts +91 -0
  86. package/dist/cjs/handler/token-parser.js +673 -0
  87. package/dist/cjs/handler/types.d.ts +91 -0
  88. package/dist/cjs/handler/types.js +4 -0
  89. package/dist/cjs/imap-commands.d.ts +16 -0
  90. package/dist/cjs/imap-commands.js +74 -0
  91. package/dist/cjs/imap-flow.d.ts +676 -0
  92. package/dist/cjs/imap-flow.js +3956 -0
  93. package/dist/cjs/jp-decoder.d.ts +12 -0
  94. package/dist/cjs/jp-decoder.js +79 -0
  95. package/dist/cjs/limited-passthrough.d.ts +25 -0
  96. package/dist/cjs/limited-passthrough.js +54 -0
  97. package/dist/cjs/logger.d.ts +3 -0
  98. package/dist/cjs/logger.js +11 -0
  99. package/dist/cjs/package-info.d.ts +3 -0
  100. package/dist/cjs/package-info.js +7 -0
  101. package/dist/cjs/package.json +3 -0
  102. package/dist/cjs/proxy-connection.d.ts +33 -0
  103. package/dist/cjs/proxy-connection.js +392 -0
  104. package/dist/cjs/search-compiler.d.ts +34 -0
  105. package/dist/cjs/search-compiler.js +476 -0
  106. package/dist/cjs/special-use.d.ts +22 -0
  107. package/dist/cjs/special-use.js +911 -0
  108. package/dist/cjs/tools.d.ts +427 -0
  109. package/dist/cjs/tools.js +1496 -0
  110. package/{lib/imap-flow.d.ts → dist/cjs/types.d.ts} +387 -517
  111. package/dist/cjs/types.js +5 -0
  112. package/dist/esm/charsets.d.ts +1 -0
  113. package/{lib → dist/esm}/charsets.js +1 -6
  114. package/dist/esm/commands/append.d.ts +22 -0
  115. package/{lib → dist/esm}/commands/append.js +22 -52
  116. package/dist/esm/commands/authenticate.d.ts +24 -0
  117. package/{lib → dist/esm}/commands/authenticate.js +62 -87
  118. package/dist/esm/commands/capability.d.ts +8 -0
  119. package/{lib → dist/esm}/commands/capability.js +6 -9
  120. package/dist/esm/commands/close.d.ts +8 -0
  121. package/{lib → dist/esm}/commands/close.js +6 -10
  122. package/dist/esm/commands/compress.d.ts +8 -0
  123. package/{lib → dist/esm}/commands/compress.js +7 -11
  124. package/dist/esm/commands/copy.d.ts +13 -0
  125. package/{lib → dist/esm}/commands/copy.js +12 -20
  126. package/dist/esm/commands/copyuid-parser.d.ts +11 -0
  127. package/{lib → dist/esm}/commands/copyuid-parser.js +9 -15
  128. package/dist/esm/commands/create.d.ts +11 -0
  129. package/{lib → dist/esm}/commands/create.js +13 -27
  130. package/dist/esm/commands/delete.d.ts +11 -0
  131. package/{lib → dist/esm}/commands/delete.js +9 -14
  132. package/dist/esm/commands/enable.d.ts +9 -0
  133. package/{lib → dist/esm}/commands/enable.js +23 -30
  134. package/dist/esm/commands/esearch-parser.d.ts +17 -0
  135. package/dist/esm/commands/esearch-parser.js +88 -0
  136. package/dist/esm/commands/expunge.d.ts +12 -0
  137. package/{lib → dist/esm}/commands/expunge.js +17 -22
  138. package/dist/esm/commands/fetch.d.ts +30 -0
  139. package/{lib → dist/esm}/commands/fetch.js +32 -64
  140. package/dist/esm/commands/id.d.ts +10 -0
  141. package/{lib → dist/esm}/commands/id.js +17 -23
  142. package/dist/esm/commands/idle.d.ts +9 -0
  143. package/{lib → dist/esm}/commands/idle.js +47 -81
  144. package/dist/esm/commands/list.d.ts +16 -0
  145. package/{lib → dist/esm}/commands/list.js +60 -123
  146. package/dist/esm/commands/login.d.ts +11 -0
  147. package/{lib → dist/esm}/commands/login.js +10 -15
  148. package/dist/esm/commands/logout.d.ts +8 -0
  149. package/{lib → dist/esm}/commands/logout.js +9 -11
  150. package/dist/esm/commands/move.d.ts +13 -0
  151. package/{lib → dist/esm}/commands/move.js +13 -21
  152. package/dist/esm/commands/namespace.d.ts +25 -0
  153. package/{lib → dist/esm}/commands/namespace.js +34 -44
  154. package/dist/esm/commands/noop.d.ts +8 -0
  155. package/{lib → dist/esm}/commands/noop.js +6 -7
  156. package/dist/esm/commands/quota.d.ts +10 -0
  157. package/{lib → dist/esm}/commands/quota.js +18 -36
  158. package/dist/esm/commands/rename.d.ts +12 -0
  159. package/{lib → dist/esm}/commands/rename.js +10 -15
  160. package/dist/esm/commands/search.d.ts +15 -0
  161. package/{lib → dist/esm}/commands/search.js +36 -135
  162. package/dist/esm/commands/select.d.ts +25 -0
  163. package/{lib → dist/esm}/commands/select.js +33 -64
  164. package/dist/esm/commands/starttls.d.ts +8 -0
  165. package/{lib → dist/esm}/commands/starttls.js +6 -8
  166. package/dist/esm/commands/status-fields.d.ts +14 -0
  167. package/{lib → dist/esm}/commands/status-fields.js +5 -16
  168. package/dist/esm/commands/status.d.ts +12 -0
  169. package/{lib → dist/esm}/commands/status.js +18 -29
  170. package/dist/esm/commands/store.d.ts +19 -0
  171. package/{lib → dist/esm}/commands/store.js +24 -37
  172. package/dist/esm/commands/subscribe.d.ts +9 -0
  173. package/{lib → dist/esm}/commands/subscribe.js +8 -12
  174. package/dist/esm/commands/unsubscribe.d.ts +9 -0
  175. package/{lib → dist/esm}/commands/unsubscribe.js +8 -12
  176. package/dist/esm/connection-deadline.d.ts +49 -0
  177. package/{lib → dist/esm}/connection-deadline.js +14 -25
  178. package/dist/esm/errors.d.ts +83 -0
  179. package/dist/esm/errors.js +9 -0
  180. package/dist/esm/handler/imap-compiler.d.ts +24 -0
  181. package/{lib → dist/esm}/handler/imap-compiler.js +22 -80
  182. package/dist/esm/handler/imap-formal-syntax.d.ts +28 -0
  183. package/dist/esm/handler/imap-formal-syntax.js +117 -0
  184. package/dist/esm/handler/imap-handler.d.ts +9 -0
  185. package/dist/esm/handler/imap-handler.js +9 -0
  186. package/dist/esm/handler/imap-parser.d.ts +16 -0
  187. package/{lib → dist/esm}/handler/imap-parser.js +31 -44
  188. package/dist/esm/handler/imap-stream.d.ts +181 -0
  189. package/{lib → dist/esm}/handler/imap-stream.js +29 -121
  190. package/dist/esm/handler/limits.d.ts +25 -0
  191. package/{lib → dist/esm}/handler/limits.js +13 -22
  192. package/dist/esm/handler/parser-instance.d.ts +68 -0
  193. package/{lib → dist/esm}/handler/parser-instance.js +19 -47
  194. package/dist/esm/handler/token-parser.d.ts +91 -0
  195. package/{lib → dist/esm}/handler/token-parser.js +71 -155
  196. package/dist/esm/handler/types.d.ts +91 -0
  197. package/dist/esm/handler/types.js +3 -0
  198. package/dist/esm/imap-commands.d.ts +16 -0
  199. package/dist/esm/imap-commands.js +67 -0
  200. package/dist/esm/imap-flow.d.ts +676 -0
  201. package/{lib → dist/esm}/imap-flow.js +769 -1790
  202. package/dist/esm/jp-decoder.d.ts +12 -0
  203. package/{lib → dist/esm}/jp-decoder.js +6 -21
  204. package/dist/esm/limited-passthrough.d.ts +25 -0
  205. package/{lib → dist/esm}/limited-passthrough.js +7 -20
  206. package/dist/esm/logger.d.ts +3 -0
  207. package/dist/esm/logger.js +4 -0
  208. package/dist/esm/package-info.d.ts +3 -0
  209. package/dist/esm/package-info.js +4 -0
  210. package/dist/esm/package.json +3 -0
  211. package/dist/esm/proxy-connection.d.ts +33 -0
  212. package/{lib → dist/esm}/proxy-connection.js +56 -127
  213. package/dist/esm/search-compiler.d.ts +34 -0
  214. package/{lib → dist/esm}/search-compiler.js +54 -110
  215. package/dist/esm/special-use.d.ts +22 -0
  216. package/dist/esm/special-use.js +907 -0
  217. package/dist/esm/tools.d.ts +427 -0
  218. package/dist/esm/tools.js +1446 -0
  219. package/dist/esm/types.d.ts +828 -0
  220. package/dist/esm/types.js +4 -0
  221. package/package.json +60 -20
  222. package/.gitattributes +0 -1
  223. package/.github/CODE_OF_CONDUCT.md +0 -76
  224. package/.github/FUNDING.yml +0 -4
  225. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  226. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  227. package/.github/contributing.md +0 -17
  228. package/.github/workflows/release.yaml +0 -36
  229. package/.github/workflows/stale.yml +0 -29
  230. package/.github/workflows/test.yml +0 -51
  231. package/.ncurc.js +0 -4
  232. package/.prettierignore +0 -4
  233. package/.prettierrc.js +0 -8
  234. package/.release-please-manifest.json +0 -3
  235. package/CLAUDE.md +0 -104
  236. package/Gruntfile.js +0 -23
  237. package/eslint.config.js +0 -45
  238. package/lib/handler/imap-formal-syntax.js +0 -189
  239. package/lib/handler/imap-handler.js +0 -17
  240. package/lib/imap-commands.js +0 -45
  241. package/lib/logger.js +0 -5
  242. package/lib/special-use.js +0 -923
  243. package/lib/tools.js +0 -1612
  244. package/release-please-config.json +0 -10
  245. package/test/authentication-test.js +0 -101
  246. package/test/auto-idle-test.js +0 -470
  247. package/test/bodystructure-test.js +0 -899
  248. package/test/charsets-test.js +0 -161
  249. package/test/commands-branches-test.js +0 -1095
  250. package/test/commands-integration-test.js +0 -11124
  251. package/test/commands-test.js +0 -73
  252. package/test/connection-edge-cases-test.js +0 -1828
  253. package/test/connection-test.js +0 -162
  254. package/test/copyuid-parser-test.js +0 -173
  255. package/test/fetch-generator-test.js +0 -218
  256. package/test/fixtures/fake-timers.js +0 -115
  257. package/test/fixtures/serialized-mimetorture.js +0 -2738
  258. package/test/fixtures/test-client.js +0 -101
  259. package/test/fixtures/test-tls.js +0 -8
  260. package/test/handler-branches-test.js +0 -310
  261. package/test/idle-polling-test.js +0 -518
  262. package/test/imap-compiler-test.js +0 -809
  263. package/test/imap-flow-compress-test.js +0 -166
  264. package/test/imap-flow-coverage-test.js +0 -612
  265. package/test/imap-flow-fetch-download-test.js +0 -909
  266. package/test/imap-flow-internals-test.js +0 -725
  267. package/test/imap-flow-methods-test.js +0 -889
  268. package/test/imap-flow-proxy-paths-test.js +0 -366
  269. package/test/imap-flow-secure-test.js +0 -573
  270. package/test/imap-flow-server-test.js +0 -1474
  271. package/test/imap-formal-syntax-test.js +0 -293
  272. package/test/imap-parser-test.js +0 -1474
  273. package/test/imap-stream-edge-cases-test.js +0 -666
  274. package/test/imap-stream-test.js +0 -177
  275. package/test/imapflow-test.js +0 -258
  276. package/test/integration/README.md +0 -52
  277. package/test/integration/dovecot-test.conf +0 -27
  278. package/test/integration/rev2-live-test.js +0 -431
  279. package/test/integration/run-rev2-tests.sh +0 -75
  280. package/test/integration-test.js +0 -83
  281. package/test/jp-decoder-test.js +0 -304
  282. package/test/limited-passthrough-test.js +0 -299
  283. package/test/memory-cleanup-test.js +0 -144
  284. package/test/memory-leak-test.js +0 -667
  285. package/test/parser-limits-test.js +0 -292
  286. package/test/proxy-connection-test.js +0 -738
  287. package/test/reliability-improvements-test.js +0 -548
  288. package/test/search-compiler-test.js +0 -1300
  289. package/test/search-test.js +0 -329
  290. package/test/special-use-test.js +0 -418
  291. package/test/starttls-injection-test.js +0 -181
  292. package/test/tag-correlation-test.js +0 -333
  293. package/test/timer-policy-test.js +0 -227
  294. package/test/token-parser-test.js +0 -456
  295. package/test/tools-test.js +0 -2013
  296. package/test/unhandled-rejection-test.js +0 -661
@@ -1,34 +1,21 @@
1
- 'use strict';
2
-
3
- const {
4
- decodePath,
5
- encodePath,
6
- normalizePath,
7
- enhanceCommandError,
8
- hasCapability,
9
- isRev2Active,
10
- buildStatusQueryAttributes,
11
- getStringList
12
- } = require('../tools.js');
13
- const { parseStatusList } = require('./status-fields.js');
14
- const { specialUse } = require('../special-use');
15
-
1
+ import { decodePath, encodePath, normalizePath, enhanceCommandError, hasCapability, isRev2Active, buildStatusQueryAttributes, getStringList } from '../tools.js';
2
+ import { parseStatusList } from './status-fields.js';
3
+ import { specialUse } from '../special-use.js';
16
4
  /**
17
5
  * Lists mailboxes from the server, including subscription status and special-use flags.
18
6
  *
19
- * @param {Object} connection - IMAP connection instance
20
- * @param {string} reference - Reference name (namespace prefix)
21
- * @param {string} mailbox - Mailbox name pattern with possible wildcards
22
- * @param {Object} [options] - List options
23
- * @param {boolean} [options.listOnly] - If true, return entries after LIST without LSUB or status queries
24
- * @param {Object} [options.statusQuery] - Status data items to query for each listed mailbox
25
- * @param {Object} [options.specialUseHints] - Hints mapping mailbox paths to special-use types (sent, junk, trash, drafts, archive)
26
- * @returns {Promise<Object[]>} Array of mailbox entries sorted by special-use flags and name
27
- * @throws {Error} If the LIST command fails
7
+ * @param connection - IMAP connection instance
8
+ * @param reference - Reference name (namespace prefix)
9
+ * @param mailbox - Mailbox name pattern with possible wildcards
10
+ * @param options - List options
11
+ * @param options.listOnly - If true, return entries after LIST without LSUB or status queries
12
+ * @param options.statusQuery - Status data items to query for each listed mailbox
13
+ * @param options.specialUseHints - Hints mapping mailbox paths to special-use types (sent, junk, trash, drafts, archive)
14
+ * @returns Array of mailbox entries sorted by special-use flags and name
15
+ * @throws If the LIST command fails
28
16
  */
29
- module.exports = async (connection, reference, mailbox, options) => {
17
+ export default async function list(connection, reference, mailbox, options) {
30
18
  options = options || {};
31
-
32
19
  // Special-use flags sorted by display priority (INBOX first, Trash last).
33
20
  // Used in the final sort to group special-use mailboxes at the top of the list.
34
21
  const FLAG_SORT_ORDER = ['\\Inbox', '\\Flagged', '\\Sent', '\\Drafts', '\\All', '\\Archive', '\\Junk', '\\Trash'];
@@ -41,61 +28,53 @@ module.exports = async (connection, reference, mailbox, options) => {
41
28
  // "name-guess" is an internal precedence tier only. It is reported as "name" so
42
29
  // that specialUseSource keeps its documented set of values for consumers.
43
30
  const PUBLIC_SOURCE = { 'name-guess': 'name' };
44
- const isNameSource = source => source === 'name' || source === 'name-guess';
45
-
31
+ const isNameSource = (source) => source === 'name' || source === 'name-guess';
46
32
  // Prefer XLIST (legacy Gmail extension) only if SPECIAL-USE (RFC 6154) is unavailable.
47
33
  // Both provide special-use flags, but SPECIAL-USE is the standardized approach.
48
34
  // SPECIAL-USE is checked with rev2 folding - a rev2 session implies SPECIAL-USE,
49
35
  // so LIST is preferred even if a rev2 server also advertised legacy XLIST.
50
36
  let listCommand = connection.capabilities.has('XLIST') && !hasCapability(connection, 'SPECIAL-USE') ? 'XLIST' : 'LIST';
51
-
52
37
  try {
53
38
  // Accumulators filled by the untagged LIST/STATUS handlers below. statusMap
54
39
  // caches STATUS responses received inline via LIST-STATUS extension, keyed by
55
40
  // normalized mailbox path (avoids separate STATUS commands per mailbox), and
56
41
  // specialUseMatches tracks candidate mailboxes for each special-use type.
57
42
  // (Re)initialized at the start of each retry stage of the main listing.
58
- let entries;
59
- let statusMap;
60
- let specialUseMatches;
61
-
43
+ let entries = [];
44
+ let statusMap = new Map();
45
+ let specialUseMatches = {};
62
46
  // STATUS data items to request (MESSAGES, UIDNEXT, etc.)
63
47
  let statusQueryAttributes = buildStatusQueryAttributes(connection, options.statusQuery);
64
-
65
48
  // Extended LIST syntax (RETURN options) is understood by servers advertising
66
49
  // LIST-EXTENDED (RFC 5258) or IMAP4rev2 (RFC 9051). Deliberately keyed on the
67
50
  // advertisement alone (not hasCapability/isRev2Active): the staged retry below
68
51
  // handles servers that advertise but reject RETURN options, so the wider gate
69
52
  // is safe for anything it covers, while gates without a retry ladder stay
70
- // conservative.
71
- let supportsExtendedList = connection.capabilities.has('LIST-EXTENDED') || connection.capabilities.has('IMAP4rev2');
72
-
53
+ // conservative. The rev2 advertisement stops counting once the session has
54
+ // been told not to act on it (skipRev2), because the ladder costs one rejected
55
+ // command per stage and a server that has disowned rev2 may not grant that many.
56
+ let supportsExtendedList = connection.capabilities.has('LIST-EXTENDED') || (connection.capabilities.has('IMAP4rev2') && !connection.skipRev2);
73
57
  // RETURN options for the LIST command. Servers occasionally advertise the
74
58
  // extensions but still reject RETURN options - the staged retry below then
75
59
  // re-runs the LIST with fewer options and latches a skip flag for the option
76
60
  // group the server proved to reject, keeping later listings efficient.
77
-
78
61
  // LIST-STATUS (RFC 5819, folded into base IMAP4rev2): request STATUS data
79
62
  // inline with LIST, avoiding a separate STATUS command for each mailbox.
80
- let canRequestStatus =
81
- listCommand === 'LIST' && !connection.skipListStatusArgs && hasCapability(connection, 'LIST-STATUS') && !!statusQueryAttributes.length;
82
-
63
+ let canRequestStatus = listCommand === 'LIST' && !connection.skipListStatusArgs && hasCapability(connection, 'LIST-STATUS') && !!statusQueryAttributes.length;
83
64
  // RETURN (SUBSCRIBED): request subscription state inline instead of a separate
84
65
  // LSUB command. IMAP4rev2 removed LSUB entirely, and some servers (e.g.
85
66
  // Exchange in IMAP4rev2 mode) reject it with BAD even while still advertising
86
67
  // IMAP4rev1.
87
68
  let canRequestSubscribed = listCommand === 'LIST' && !options.listOnly && !connection.skipListSubscribedArg && supportsExtendedList;
88
-
89
69
  // Auxiliary RETURN options (SPECIAL-USE/CHILDREN) that ride along with the
90
70
  // STATUS/SUBSCRIBED option groups. When RETURN options are present, servers
91
71
  // may report only what was explicitly requested (verified against Dovecot
92
72
  // 2.4: special-use and child attributes disappear from such responses), so
93
73
  // request everything a plain LIST would have provided.
94
74
  let auxArgsAvailable = hasCapability(connection, 'SPECIAL-USE') || connection.capabilities.has('CHILDREN') || supportsExtendedList;
95
- let stageHasAuxArgs = stage => (stage.status || stage.subscribed) && stage.aux !== false && !connection.skipListAuxArgs && auxArgsAvailable;
96
-
75
+ let stageHasAuxArgs = (stage) => (stage.status || stage.subscribed) && stage.aux !== false && !connection.skipListAuxArgs && auxArgsAvailable;
97
76
  // Builds the RETURN (...) argument list for one retry stage
98
- let buildListArgs = stage => {
77
+ let buildListArgs = (stage) => {
99
78
  let args = [];
100
79
  if (stage.status) {
101
80
  args.push({ type: 'ATOM', value: 'STATUS' }, statusQueryAttributes);
@@ -113,7 +92,6 @@ module.exports = async (connection, reference, mailbox, options) => {
113
92
  }
114
93
  return args;
115
94
  };
116
-
117
95
  // Multiple mailboxes may claim the same special-use type (e.g., \\Sent) via
118
96
  // different sources (user hint, server extension, name match). After listing,
119
97
  // the best match wins.
@@ -123,14 +101,13 @@ module.exports = async (connection, reference, mailbox, options) => {
123
101
  }
124
102
  specialUseMatches[type].push({ entry, source });
125
103
  };
126
-
127
104
  // RFC 5258: the \NonExistent attribute implies \Noselect. Some servers only
128
105
  // return \NonExistent for phantom folders, so add \Noselect as well to keep
129
106
  // the flags consistent for consumers that only check \Noselect.
130
107
  // RETURN (SUBSCRIBED) - and some LSUB implementations - report subscription
131
108
  // state as a \Subscribed attribute. Move it to the subscribed property so the
132
109
  // output shape is the same however the state was delivered.
133
- let normalizeFlags = entry => {
110
+ let normalizeFlags = (entry) => {
134
111
  if (entry.flags.has('\\NonExistent')) {
135
112
  entry.flags.add('\\Noselect');
136
113
  }
@@ -139,7 +116,6 @@ module.exports = async (connection, reference, mailbox, options) => {
139
116
  entry.subscribed = true;
140
117
  }
141
118
  };
142
-
143
119
  // User-provided hints map mailbox paths to special-use types (e.g., {sent: "Sent Items"}).
144
120
  // These override server-reported flags and name-based guesses. Converted to a
145
121
  // path-keyed lookup: { "Sent Items" => "\\Sent" }
@@ -148,52 +124,43 @@ module.exports = async (connection, reference, mailbox, options) => {
148
124
  let specialUseHints = Object.create(null);
149
125
  if (options.specialUseHints && typeof options.specialUseHints === 'object') {
150
126
  for (let type of Object.keys(options.specialUseHints)) {
151
- if (
152
- ['sent', 'junk', 'trash', 'drafts', 'archive'].includes(type) &&
127
+ if (['sent', 'junk', 'trash', 'drafts', 'archive'].includes(type) &&
153
128
  options.specialUseHints[type] &&
154
- typeof options.specialUseHints[type] === 'string'
155
- ) {
129
+ typeof options.specialUseHints[type] === 'string') {
156
130
  // Capitalize first letter: "sent" -> "\\Sent"
157
131
  specialUseHints[normalizePath(connection, options.specialUseHints[type])] = `\\${type.replace(/^./, c => c.toUpperCase())}`;
158
132
  }
159
133
  }
160
134
  }
161
-
162
135
  // Executes a LIST (or XLIST) command and collects mailbox entries.
163
136
  // Called once for the main listing and optionally again for INBOX if a
164
137
  // namespace prefix was used (INBOX may live outside the namespace).
165
138
  let runList = async (reference, mailbox, returnArgs) => {
166
139
  const cmdArgs = [encodePath(connection, reference), encodePath(connection, mailbox)];
167
-
168
140
  if (returnArgs.length) {
169
141
  cmdArgs.push({ type: 'ATOM', value: 'RETURN' }, returnArgs);
170
142
  }
171
-
172
143
  let response = await connection.exec(listCommand, cmdArgs, {
173
144
  untagged: {
174
145
  // Each untagged LIST response: * LIST (<flags>) "<delimiter>" "<mailbox name>"
175
146
  // attributes[0] = flags array, attributes[1] = delimiter, attributes[2] = mailbox name
176
- [listCommand]: async untagged => {
147
+ [listCommand]: async (untagged) => {
177
148
  if (!untagged.attributes || !untagged.attributes.length) {
178
149
  return;
179
150
  }
180
-
181
151
  let entry = {
182
152
  // Decode from modified UTF-7 wire format and normalize the path
183
- path: normalizePath(connection, decodePath(connection, (untagged.attributes[2] && untagged.attributes[2].value) || '')),
184
- pathAsListed: (untagged.attributes[2] && untagged.attributes[2].value) || '',
153
+ path: normalizePath(connection, decodePath(connection, ((untagged.attributes[2] && untagged.attributes[2].value) || ''))),
154
+ pathAsListed: ((untagged.attributes[2] && untagged.attributes[2].value) || ''),
185
155
  flags: new Set(getStringList(untagged.attributes[0])),
186
- delimiter: untagged.attributes[1] && untagged.attributes[1].value,
156
+ delimiter: (untagged.attributes[1] && untagged.attributes[1].value),
187
157
  listed: true
188
158
  };
189
-
190
159
  normalizeFlags(entry);
191
-
192
160
  // Check user-provided hints first (highest priority)
193
161
  if (specialUseHints[entry.path]) {
194
162
  addSpecialUseMatch(entry, specialUseHints[entry.path], 'user');
195
163
  }
196
-
197
164
  // XLIST marks INBOX with a \\Inbox flag. Remove it from flags
198
165
  // (it's not a standard flag) and register as special-use match.
199
166
  // XLIST may also use a localised name (e.g., "Posteingang" for German INBOX).
@@ -203,31 +170,23 @@ module.exports = async (connection, reference, mailbox, options) => {
203
170
  addSpecialUseMatch(entry, '\\Inbox', 'extension');
204
171
  }
205
172
  }
206
-
207
173
  // Name-based INBOX detection: any mailbox named "INBOX" (case-insensitive)
208
174
  // is the inbox per RFC 3501. Phantom \NonExistent entries (subscribed
209
175
  // leftovers of deleted mailboxes) must not claim the slot by name.
210
176
  if (entry.path.toUpperCase() === 'INBOX' && !entry.flags.has('\\NonExistent')) {
211
177
  addSpecialUseMatch(entry, '\\Inbox', 'name');
212
178
  }
213
-
214
179
  // Strip leading delimiter (some servers prepend it to paths)
215
180
  if (entry.delimiter && entry.path.charAt(0) === entry.delimiter) {
216
181
  entry.path = entry.path.slice(1);
217
182
  }
218
-
219
183
  // Build parent path hierarchy for tree construction and sorting
220
184
  entry.parentPath = entry.delimiter && entry.path ? entry.path.substr(0, entry.path.lastIndexOf(entry.delimiter)) : '';
221
185
  entry.parent = entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path];
222
186
  entry.name = entry.parent.pop();
223
-
224
187
  // Try to detect special-use from server flags or well-known names
225
188
  // (e.g., "Sent", "Drafts", "Junk", "Trash")
226
- let { flag: specialUseFlag, source: flagSource } = specialUse(
227
- connection.capabilities.has('XLIST') || hasCapability(connection, 'SPECIAL-USE'),
228
- entry
229
- );
230
-
189
+ let { flag: specialUseFlag, source: flagSource } = specialUse(connection.capabilities.has('XLIST') || hasCapability(connection, 'SPECIAL-USE'), entry);
231
190
  // A name-based match for a \NonExistent phantom entry could win the
232
191
  // special-use slot over the real folder - only server-provided flags
233
192
  // are trusted for nonexistent entries. Covers every name-derived
@@ -235,34 +194,28 @@ module.exports = async (connection, reference, mailbox, options) => {
235
194
  if (specialUseFlag && (!isNameSource(flagSource) || !entry.flags.has('\\NonExistent'))) {
236
195
  addSpecialUseMatch(entry, specialUseFlag, flagSource);
237
196
  }
238
-
239
197
  entries.push(entry);
240
198
  },
241
-
242
199
  // Inline STATUS response from LIST-STATUS extension (RFC 5819).
243
200
  // Parses alternating key-value pairs (i % 2 pattern).
244
- STATUS: async untagged => {
245
- let statusPath = normalizePath(connection, decodePath(connection, (untagged.attributes[0] && untagged.attributes[0].value) || ''));
201
+ STATUS: async (untagged) => {
202
+ let statusPath = normalizePath(connection, decodePath(connection, ((untagged.attributes[0] && untagged.attributes[0].value) || '')));
246
203
  let statusList = untagged.attributes && Array.isArray(untagged.attributes[1]) ? untagged.attributes[1] : false;
247
204
  if (!statusList || !statusPath) {
248
205
  return;
249
206
  }
250
-
251
207
  let map = { path: statusPath };
252
208
  parseStatusList(statusList, (key, value) => {
253
209
  map[key] = value;
254
210
  });
255
-
256
211
  statusMap.set(statusPath, map);
257
212
  }
258
213
  }
259
214
  });
260
215
  response.next();
261
216
  };
262
-
263
217
  let normalizedReference = normalizePath(connection, reference || '');
264
218
  let normalizedMailbox = normalizePath(connection, mailbox || '', true);
265
-
266
219
  // Retry stages for the main listing: start with all applicable RETURN options
267
220
  // and drop one option group per retry. Consecutive stages differ by exactly one
268
221
  // group, so a success right after a rejection identifies the offending group
@@ -276,29 +229,25 @@ module.exports = async (connection, reference, mailbox, options) => {
276
229
  }
277
230
  if (canRequestStatus) {
278
231
  stages.push({ status: true, subscribed: false });
279
- } else if (canRequestSubscribed) {
232
+ }
233
+ else if (canRequestSubscribed) {
280
234
  stages.push({ status: false, subscribed: true });
281
235
  }
282
236
  stages.push({ status: false, subscribed: false });
283
-
284
237
  // A tagged BAD is how servers reject unrecognized RETURN options (RFC 9051
285
238
  // section 6.3.9). A tagged NO is an operational failure, and throttling
286
239
  // errors (code ETHROTTLE) also surface with a BAD status - neither says
287
240
  // anything about the RETURN options, so they propagate to the caller.
288
- let isRejectedCommand = err => err.responseStatus === 'BAD' && err.code !== 'ETHROTTLE';
289
-
241
+ let isRejectedCommand = (err) => err.responseStatus === 'BAD' && err.code !== 'ETHROTTLE';
290
242
  // Stage of the successful attempt - reused by the INBOX fixup and the LSUB
291
243
  // decision below
292
244
  let successStage = null;
293
-
294
245
  // Whether any source actually reported subscription state. RETURN (SUBSCRIBED)
295
246
  // and LSUB are the only two, and a server can refuse both
296
247
  let subscriptionStateKnown = false;
297
-
298
248
  // A server may also volunteer \Subscribed on a plain LIST, which normalizeFlags
299
249
  // folds into the entry - that counts as the state having been reported
300
250
  let anyEntrySubscribed = () => entries.some(entry => entry.subscribed);
301
-
302
251
  let lastRejectedStage = null;
303
252
  let auxRetryInserted = false;
304
253
  for (let i = 0; i < stages.length; i++) {
@@ -322,12 +271,10 @@ module.exports = async (connection, reference, mailbox, options) => {
322
271
  if (lastRejectedStage.status && !stage.status) {
323
272
  connection.skipListStatusArgs = true;
324
273
  }
325
- if (
326
- stageHasAuxArgs(lastRejectedStage) &&
274
+ if (stageHasAuxArgs(lastRejectedStage) &&
327
275
  stage.aux === false &&
328
276
  lastRejectedStage.status === stage.status &&
329
- lastRejectedStage.subscribed === stage.subscribed
330
- ) {
277
+ lastRejectedStage.subscribed === stage.subscribed) {
331
278
  // Same option groups, only the auxiliary args dropped - the
332
279
  // auxiliaries are proven to be what the server rejects
333
280
  connection.skipListAuxArgs = true;
@@ -336,7 +283,8 @@ module.exports = async (connection, reference, mailbox, options) => {
336
283
  successStage = stage;
337
284
  subscriptionStateKnown = !!stage.subscribed;
338
285
  break;
339
- } catch (err) {
286
+ }
287
+ catch (err) {
340
288
  if (i === stages.length - 1 || !isRejectedCommand(err)) {
341
289
  throw err;
342
290
  }
@@ -351,11 +299,9 @@ module.exports = async (connection, reference, mailbox, options) => {
351
299
  connection.log.warn({ msg: 'LIST RETURN options rejected, retrying with reduced options', err, cid: connection.id });
352
300
  }
353
301
  }
354
-
355
302
  if (options.listOnly) {
356
303
  return entries;
357
304
  }
358
-
359
305
  // When listing with a namespace prefix (e.g., "INBOX."), INBOX itself may
360
306
  // not appear in results. Run a separate LIST for INBOX to ensure it's included.
361
307
  if (normalizedReference && !specialUseMatches['\\Inbox']) {
@@ -371,7 +317,8 @@ module.exports = async (connection, reference, mailbox, options) => {
371
317
  }
372
318
  try {
373
319
  await runList('', 'INBOX', returnArgs);
374
- } catch (err) {
320
+ }
321
+ catch (err) {
375
322
  // The main listing just succeeded with the same RETURN options, so a
376
323
  // rejection here says nothing about the options themselves - retry
377
324
  // this one call plain without latching any skip flags. Accepted edge:
@@ -384,7 +331,8 @@ module.exports = async (connection, reference, mailbox, options) => {
384
331
  for (let type of Object.keys(specialUseMatches)) {
385
332
  if (!(type in specialUseCountsBefore)) {
386
333
  delete specialUseMatches[type];
387
- } else {
334
+ }
335
+ else {
388
336
  specialUseMatches[type].length = specialUseCountsBefore[type];
389
337
  }
390
338
  }
@@ -392,7 +340,6 @@ module.exports = async (connection, reference, mailbox, options) => {
392
340
  await runList('', 'INBOX', []);
393
341
  }
394
342
  }
395
-
396
343
  // Attach STATUS data to each selectable mailbox. If LIST-STATUS was used,
397
344
  // data is already in statusMap; otherwise, fall back to individual STATUS commands.
398
345
  if (options.statusQuery) {
@@ -408,18 +355,19 @@ module.exports = async (connection, reference, mailbox, options) => {
408
355
  if (syntheticRecent) {
409
356
  entry.status.recent = 0;
410
357
  }
411
- } else if (!statusMap.size) {
358
+ }
359
+ else if (!statusMap.size) {
412
360
  // Server didn't support LIST-STATUS; fall back to per-mailbox STATUS
413
361
  try {
414
362
  entry.status = await connection.run('STATUS', entry.path, options.statusQuery);
415
- } catch (err) {
363
+ }
364
+ catch (err) {
416
365
  entry.status = { error: err };
417
366
  }
418
367
  }
419
368
  }
420
369
  }
421
370
  }
422
-
423
371
  // LSUB (RFC 3501 6.3.9): queries which mailboxes the user is subscribed to.
424
372
  // We merge subscription info into the entries already collected from LIST.
425
373
  // Subscribed-only mailboxes that weren't in LIST are intentionally ignored
@@ -427,31 +375,26 @@ module.exports = async (connection, reference, mailbox, options) => {
427
375
  let runLsub = async () => {
428
376
  let response = await connection.exec('LSUB', [encodePath(connection, normalizedReference), encodePath(connection, normalizedMailbox)], {
429
377
  untagged: {
430
- LSUB: async untagged => {
378
+ LSUB: async (untagged) => {
431
379
  if (!untagged.attributes || !untagged.attributes.length) {
432
380
  return;
433
381
  }
434
-
435
382
  let entry = {
436
- path: normalizePath(connection, decodePath(connection, (untagged.attributes[2] && untagged.attributes[2].value) || '')),
437
- pathAsListed: (untagged.attributes[2] && untagged.attributes[2].value) || '',
383
+ path: normalizePath(connection, decodePath(connection, ((untagged.attributes[2] && untagged.attributes[2].value) || ''))),
384
+ pathAsListed: ((untagged.attributes[2] && untagged.attributes[2].value) || ''),
438
385
  flags: new Set(getStringList(untagged.attributes[0])),
439
- delimiter: untagged.attributes[1] && untagged.attributes[1].value,
386
+ delimiter: (untagged.attributes[1] && untagged.attributes[1].value),
440
387
  subscribed: true
441
388
  };
442
-
443
389
  if (entry.path.toUpperCase() === 'INBOX') {
444
390
  addSpecialUseMatch(entry, '\\Inbox', 'name');
445
391
  }
446
-
447
392
  if (entry.delimiter && entry.path.charAt(0) === entry.delimiter) {
448
393
  entry.path = entry.path.slice(1);
449
394
  }
450
-
451
395
  entry.parentPath = entry.delimiter && entry.path ? entry.path.substr(0, entry.path.lastIndexOf(entry.delimiter)) : '';
452
396
  entry.parent = entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path];
453
397
  entry.name = entry.parent.pop();
454
-
455
398
  // Merge LSUB data into existing LIST entry if found
456
399
  let existing = entries.find(existing => existing.path === entry.path);
457
400
  if (existing) {
@@ -466,7 +409,6 @@ module.exports = async (connection, reference, mailbox, options) => {
466
409
  });
467
410
  response.next();
468
411
  };
469
-
470
412
  // Never sent on a rev2 session - LSUB is not part of that protocol version, and
471
413
  // some servers break the rest of the session over the rejection, so this is
472
414
  // decided up front rather than left to the skipLsub latch below. On rev1 it is
@@ -485,12 +427,14 @@ module.exports = async (connection, reference, mailbox, options) => {
485
427
  try {
486
428
  await runLsub();
487
429
  subscriptionStateKnown = true;
488
- } catch (err) {
430
+ }
431
+ catch (err) {
489
432
  if (isRejectedCommand(err)) {
490
433
  // Tagged BAD: the server does not implement LSUB despite advertising
491
434
  // rev1 - skip it for the rest of this connection
492
435
  connection.skipLsub = true;
493
- } else if (err.responseStatus !== 'NO' || err.code === 'ETHROTTLE') {
436
+ }
437
+ else if (err.responseStatus !== 'NO' || err.code === 'ETHROTTLE') {
494
438
  // Transport failures and throttling: rethrow, every follow-up
495
439
  // command would fail too or the caller needs to back off
496
440
  throw err;
@@ -500,7 +444,6 @@ module.exports = async (connection, reference, mailbox, options) => {
500
444
  connection.log.warn({ msg: 'Failed to request subscription info', err, cid: connection.id });
501
445
  }
502
446
  }
503
-
504
447
  // Resolve special-use conflicts: for each type, pick the best candidate
505
448
  // based on source priority (user > extension > name), then alphabetically.
506
449
  // Only the winning entry gets the specialUse property set.
@@ -513,14 +456,12 @@ module.exports = async (connection, reference, mailbox, options) => {
513
456
  }
514
457
  return aSource - bSource;
515
458
  });
516
-
517
459
  if (!sortedEntries[0].entry.specialUse) {
518
460
  let source = sortedEntries[0].source;
519
461
  sortedEntries[0].entry.specialUse = type;
520
462
  sortedEntries[0].entry.specialUseSource = PUBLIC_SOURCE[source] || source;
521
463
  }
522
464
  }
523
-
524
465
  // No source answered, so "not subscribed" was never actually reported for any of
525
466
  // these folders - the state is unknown, not false. Reporting the whole listing as
526
467
  // unsubscribed would hide every folder from a client that filters on subscription
@@ -535,13 +476,11 @@ module.exports = async (connection, reference, mailbox, options) => {
535
476
  }
536
477
  }
537
478
  }
538
-
539
479
  // INBOX should always appear as subscribed regardless of LSUB results
540
480
  let inboxEntry = entries.find(entry => entry.specialUse === '\\Inbox');
541
481
  if (inboxEntry && !inboxEntry.subscribed) {
542
482
  inboxEntry.subscribed = true;
543
483
  }
544
-
545
484
  // Sort: special-use mailboxes first (in FLAG_SORT_ORDER), then alphabetically
546
485
  // by path segments for a natural folder hierarchy ordering.
547
486
  return entries.sort((a, b) => {
@@ -554,10 +493,8 @@ module.exports = async (connection, reference, mailbox, options) => {
554
493
  if (a.specialUse && b.specialUse) {
555
494
  return FLAG_SORT_ORDER.indexOf(a.specialUse) - FLAG_SORT_ORDER.indexOf(b.specialUse);
556
495
  }
557
-
558
496
  let aList = [].concat(a.parent).concat(a.name);
559
497
  let bList = [].concat(b.parent).concat(b.name);
560
-
561
498
  for (let i = 0; i < aList.length; i++) {
562
499
  let aPart = aList[i];
563
500
  let bPart = bList[i];
@@ -565,14 +502,14 @@ module.exports = async (connection, reference, mailbox, options) => {
565
502
  return aPart.localeCompare(bPart || '');
566
503
  }
567
504
  }
568
-
569
505
  return a.path.localeCompare(b.path);
570
506
  });
571
- } catch (err) {
507
+ }
508
+ catch (err) {
572
509
  // Rewrite the parsed err.response into the response text and set
573
510
  // serverResponseCode, same as the other command modules
574
511
  await enhanceCommandError(err);
575
512
  connection.log.warn({ msg: 'Failed to list folders', err, cid: connection.id });
576
513
  throw err;
577
514
  }
578
- };
515
+ }
@@ -0,0 +1,11 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ /**
3
+ * Authenticates user using the IMAP LOGIN command.
4
+ *
5
+ * @param connection - IMAP connection instance
6
+ * @param username - The username to authenticate with
7
+ * @param password - The password to authenticate with
8
+ * @returns The authenticated username, or undefined if already authenticated
9
+ * @throws If authentication fails, with authenticationFailed and serverResponseCode properties set
10
+ */
11
+ export default function login(connection: ImapFlow, username: string, password: string): Promise<string | undefined>;
@@ -1,22 +1,18 @@
1
- 'use strict';
2
-
3
- const { getStatusCode, getErrorText } = require('../tools.js');
4
-
1
+ import { getStatusCode, getErrorText } from '../tools.js';
5
2
  /**
6
3
  * Authenticates user using the IMAP LOGIN command.
7
4
  *
8
- * @param {Object} connection - IMAP connection instance
9
- * @param {string} username - The username to authenticate with
10
- * @param {string} password - The password to authenticate with
11
- * @returns {Promise<string|undefined>} The authenticated username, or undefined if already authenticated
12
- * @throws {Error} If authentication fails, with authenticationFailed and serverResponseCode properties set
5
+ * @param connection - IMAP connection instance
6
+ * @param username - The username to authenticate with
7
+ * @param password - The password to authenticate with
8
+ * @returns The authenticated username, or undefined if already authenticated
9
+ * @throws If authentication fails, with authenticationFailed and serverResponseCode properties set
13
10
  */
14
- module.exports = async (connection, username, password) => {
11
+ export default async function login(connection, username, password) {
15
12
  if (connection.state !== connection.states.NOT_AUTHENTICATED) {
16
13
  // nothing to do here
17
14
  return;
18
15
  }
19
-
20
16
  try {
21
17
  let response = await connection.exec('LOGIN', [
22
18
  { type: 'STRING', value: username },
@@ -24,13 +20,12 @@ module.exports = async (connection, username, password) => {
24
20
  { type: 'STRING', value: password, sensitive: true }
25
21
  ]);
26
22
  response.next();
27
-
28
23
  // Record that LOGIN was the method used, so the connection knows which
29
24
  // auth mechanism succeeded (used for reconnection and diagnostics).
30
25
  connection.authCapabilities.set('LOGIN', true);
31
-
32
26
  return username;
33
- } catch (err) {
27
+ }
28
+ catch (err) {
34
29
  let errorCode = getStatusCode(err.response);
35
30
  if (errorCode) {
36
31
  err.serverResponseCode = errorCode;
@@ -39,4 +34,4 @@ module.exports = async (connection, username, password) => {
39
34
  err.response = await getErrorText(err.response);
40
35
  throw err;
41
36
  }
42
- };
37
+ }
@@ -0,0 +1,8 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ /**
3
+ * Logs out the user and closes the connection.
4
+ *
5
+ * @param connection - IMAP connection instance
6
+ * @returns True if logout command succeeded, false otherwise
7
+ */
8
+ export default function logout(connection: ImapFlow): Promise<boolean>;
@@ -1,29 +1,26 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Logs out the user and closes the connection.
5
3
  *
6
- * @param {Object} connection - IMAP connection instance
7
- * @returns {Promise<boolean>} True if logout command succeeded, false otherwise
4
+ * @param connection - IMAP connection instance
5
+ * @returns True if logout command succeeded, false otherwise
8
6
  */
9
- module.exports = async connection => {
7
+ export default async function logout(connection) {
10
8
  if (connection.state === connection.states.LOGOUT) {
11
9
  // nothing to do here
12
10
  return false;
13
11
  }
14
-
15
12
  if (connection.state === connection.states.NOT_AUTHENTICATED) {
16
- // Not yet authenticated -- no LOGOUT command needed; just close the socket.
13
+ // Not yet authenticated, no LOGOUT command needed; just close the socket.
17
14
  connection.state = connection.states.LOGOUT;
18
15
  connection.close();
19
16
  return false;
20
17
  }
21
-
22
18
  let response;
23
19
  try {
24
20
  response = await connection.exec('LOGOUT');
25
21
  return true;
26
- } catch (err) {
22
+ }
23
+ catch (err) {
27
24
  // If the connection is already gone, treat as successful logout
28
25
  if (err.code === 'NoConnection') {
29
26
  return true;
@@ -31,7 +28,8 @@ module.exports = async connection => {
31
28
  connection.log.warn({ err, cid: connection.id });
32
29
  return false;
33
30
  /* c8 ignore next */ // the catch above is exhaustive (never re-throws), so finally is only ever reached via normal completion
34
- } finally {
31
+ }
32
+ finally {
35
33
  // Set state to LOGOUT before closing to prevent any further commands from
36
34
  // being queued. The socket is closed unconditionally in this finally block
37
35
  // regardless of whether the LOGOUT command succeeded or failed.
@@ -41,4 +39,4 @@ module.exports = async connection => {
41
39
  }
42
40
  connection.close();
43
41
  }
44
- };
42
+ }