imapflow 1.7.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/CHANGELOG.md +20 -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 +518 -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 +3949 -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} +386 -516
  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 +56 -121
  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 +761 -1789
  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
@@ -0,0 +1,347 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = idle;
4
+ const tools_js_1 = require("../tools.js");
5
+ const NOOP_INTERVAL = 2 * 60 * 1000;
6
+ /**
7
+ * Marks the connection as idling on behalf of one session and returns a release function.
8
+ *
9
+ * Both IDLE modes use this so ownership of the shared `idling` flag is explicit: a session that
10
+ * finishes late (a poll that completes after cancellation, an IDLE that unwinds after a restart)
11
+ * cannot clear the flag of the session that has since taken over.
12
+ *
13
+ * @param connection - IMAP connection instance
14
+ * @returns Release function, safe to call more than once
15
+ */
16
+ function claimIdling(connection) {
17
+ let token = {};
18
+ connection._idleSession = token;
19
+ connection.idling = true;
20
+ return () => {
21
+ if (connection._idleSession === token) {
22
+ connection._idleSession = null;
23
+ connection.idling = false;
24
+ }
25
+ };
26
+ }
27
+ /**
28
+ * Runs a single IDLE session on the connection.
29
+ *
30
+ * @param connection - IMAP connection instance
31
+ * @returns Void on success, false on failure
32
+ */
33
+ async function runIdle(connection) {
34
+ let response;
35
+ // Queue of promises waiting for IDLE to break. When another command needs to run,
36
+ // it calls connection.preCheck() which queues a promise here and sends DONE to break IDLE.
37
+ let preCheckWaitQueue = [];
38
+ // The preCheck function this session owns. Only this session may clear it from the
39
+ // connection, so a newer IDLE session that already installed its own is left alone.
40
+ let ownPreCheck = null;
41
+ let releaseIdling = claimIdling(connection);
42
+ try {
43
+ // State flags for the IDLE lifecycle:
44
+ // - doneRequested: someone wants to break IDLE (e.g., to run another command)
45
+ // - doneSent: we've already sent the DONE command to server
46
+ // - canEnd: server has acknowledged IDLE with "+" continuation, so DONE can be sent
47
+ let doneRequested = false;
48
+ let doneSent = false;
49
+ let canEnd = false;
50
+ // preCheck sends DONE to break out of IDLE. Called when another command
51
+ // needs to run on this connection (e.g., a FETCH or STORE from user code).
52
+ let preCheck = async () => {
53
+ doneRequested = true;
54
+ if (canEnd && !doneSent) {
55
+ connection.log.debug({
56
+ src: 'c',
57
+ msg: `DONE`,
58
+ comment: `breaking IDLE`,
59
+ lockId: connection.currentLock ? connection.currentLock.lockId : undefined,
60
+ path: connection.mailbox && connection.mailbox.path,
61
+ cid: connection.id
62
+ });
63
+ connection.write('DONE');
64
+ doneSent = true;
65
+ releaseIdling();
66
+ if (connection.preCheck === ownPreCheck) {
67
+ connection.preCheck = false; // unset itself
68
+ }
69
+ while (preCheckWaitQueue.length) {
70
+ let { resolve } = preCheckWaitQueue.shift();
71
+ resolve();
72
+ }
73
+ }
74
+ };
75
+ // Public interface for breaking IDLE. Returns a promise that resolves when
76
+ // IDLE is actually broken and the connection is free for other commands.
77
+ let connectionPreCheck = () => {
78
+ // Guarded: the catch block below rejects every queued waiter synchronously while
79
+ // close() tears the connection down. See guardedPromise().
80
+ let handler = (0, tools_js_1.guardedPromise)((resolve, reject) => {
81
+ preCheckWaitQueue.push({ resolve, reject });
82
+ });
83
+ connection.log.trace({
84
+ msg: 'Requesting IDLE break',
85
+ lockId: connection.currentLock ? connection.currentLock.lockId : undefined,
86
+ path: connection.mailbox && connection.mailbox.path,
87
+ queued: preCheckWaitQueue.length,
88
+ doneRequested,
89
+ canEnd,
90
+ doneSent,
91
+ cid: connection.id
92
+ });
93
+ preCheck().catch((err) => (0, tools_js_1.logConnectionError)(connection, 'Failed to break IDLE', err));
94
+ return handler;
95
+ };
96
+ // Register preCheck on the connection so other code (e.g., getMailboxLock) can break IDLE
97
+ ownPreCheck = connectionPreCheck;
98
+ connection.preCheck = connectionPreCheck;
99
+ response = await connection.exec('IDLE', false, {
100
+ // Server responds with "+" continuation to acknowledge IDLE mode.
101
+ // After this, the server will push untagged responses for mailbox changes.
102
+ // We can now safely send DONE if a break was already requested.
103
+ onPlusTag: async () => {
104
+ connection.log.debug({
105
+ msg: `Initiated IDLE, waiting for server input`,
106
+ lockId: connection.currentLock ? connection.currentLock.lockId : undefined,
107
+ doneRequested,
108
+ cid: connection.id
109
+ });
110
+ canEnd = true;
111
+ if (doneRequested) {
112
+ try {
113
+ await preCheck();
114
+ }
115
+ catch (err) {
116
+ (0, tools_js_1.logConnectionError)(connection, 'Failed to break IDLE', err);
117
+ }
118
+ }
119
+ },
120
+ onSend: () => { }
121
+ });
122
+ response.next();
123
+ return;
124
+ }
125
+ catch (err) {
126
+ (0, tools_js_1.logConnectionError)(connection, 'IDLE session failed', err);
127
+ if (preCheckWaitQueue.length) {
128
+ // One error for the whole queue: every waiter failed at the same site, for the same
129
+ // reason. Built inside the guard so a teardown with nothing queued - the common case -
130
+ // does not pay for an Error and its stack capture.
131
+ let waiterError = (0, tools_js_1.restampConnectionError)(err, { rejectedFrom: 'preCheckWaiter' });
132
+ while (preCheckWaitQueue.length) {
133
+ let { reject } = preCheckWaitQueue.shift();
134
+ reject(waiterError);
135
+ }
136
+ }
137
+ return false;
138
+ }
139
+ finally {
140
+ // Single ownership cleanup for every outcome: explicit break, tagged completion
141
+ // (including a server-terminated IDLE, where preCheck never ran), rejected command,
142
+ // parser failure and connection close. `idling` drives socket-timeout handling, so it
143
+ // must always describe reality rather than being left over from a previous state.
144
+ releaseIdling();
145
+ if (connection.preCheck === ownPreCheck) {
146
+ connection.preCheck = false;
147
+ }
148
+ while (preCheckWaitQueue.length) {
149
+ let { resolve } = preCheckWaitQueue.shift();
150
+ resolve();
151
+ }
152
+ }
153
+ }
154
+ /**
155
+ * Runs one fallback poll. The real SELECT and STATUS commands are reused instead of replaying the
156
+ * saved wire arguments, so a poll applies exactly the same mailbox state transitions, events and
157
+ * failure handling as a caller-issued command. They go through connection.runInternal() rather
158
+ * than connection.run(), because run() awaits preCheck() - and the preCheck it would await is the
159
+ * one this very polling session installed, so the session would cancel itself.
160
+ *
161
+ * @param connection - IMAP connection instance
162
+ * @param session - Polling session state
163
+ */
164
+ async function pollOnce(connection, session) {
165
+ let path = connection.mailbox && connection.mailbox.path;
166
+ switch (connection.missingIdleCommand) {
167
+ case 'SELECT':
168
+ connection.log.debug({ msg: `Running SELECT to detect changes in folder`, cid: connection.id });
169
+ await connection.runInternal('SELECT', path, { readOnly: session.selectCommand.command === 'EXAMINE' });
170
+ break;
171
+ case 'STATUS': {
172
+ connection.log.debug({ msg: `Running STATUS to detect changes in folder`, cid: connection.id });
173
+ // HIGHESTMODSEQ is filtered out again unless the server advertises CONDSTORE, so a
174
+ // CONDSTORE session keeps mailbox.highestModseq current without asking a plain
175
+ // server for an item it does not know.
176
+ let status = await connection.runInternal('STATUS', path, {
177
+ messages: true,
178
+ uidNext: true,
179
+ uidValidity: true,
180
+ unseen: true,
181
+ highestModseq: true
182
+ });
183
+ if (!status) {
184
+ let err = new Error('STATUS poll failed');
185
+ err.code = 'PollFailed';
186
+ throw err;
187
+ }
188
+ break;
189
+ }
190
+ case 'NOOP':
191
+ default: {
192
+ let response = await connection.exec('NOOP', false, { comment: 'IDLE not supported' });
193
+ response.next();
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ /**
199
+ * Polls the selected mailbox at a fixed interval, for servers without IDLE support.
200
+ *
201
+ * The loop is one explicitly identified session: cancellation is idempotent, is checked before
202
+ * a poll starts and again before the next timer is scheduled, and a poll that completes after
203
+ * cancellation can neither run again nor take ownership away from a newer IDLE session.
204
+ *
205
+ * @param connection - IMAP connection instance
206
+ * @param maxIdleTime - Upper bound for the polling interval
207
+ */
208
+ async function runPollingFallback(connection, maxIdleTime) {
209
+ if (!connection.currentSelectCommand) {
210
+ return;
211
+ }
212
+ let session = {
213
+ cancelled: false,
214
+ timer: null,
215
+ preCheck: null,
216
+ selectCommand: connection.currentSelectCommand
217
+ };
218
+ let interval = maxIdleTime ? Math.min(NOOP_INTERVAL, maxIdleTime) : NOOP_INTERVAL;
219
+ let releaseIdling = claimIdling(connection);
220
+ try {
221
+ await new Promise(resolve => {
222
+ // Idempotent cancellation. Never keyed off connection.preCheck, because a newer IDLE
223
+ // session may already own that property by the time an old poll settles.
224
+ const cancel = () => {
225
+ if (session.cancelled) {
226
+ return;
227
+ }
228
+ session.cancelled = true;
229
+ (0, tools_js_1.clearTimer)(session.timer);
230
+ session.timer = null;
231
+ resolve();
232
+ };
233
+ session.preCheck = async () => {
234
+ connection.log.debug({ msg: `Breaking NOOP loop`, cid: connection.id });
235
+ cancel();
236
+ };
237
+ connection.preCheck = session.preCheck;
238
+ const runPoll = () => {
239
+ if (session.cancelled) {
240
+ return;
241
+ }
242
+ // The transport or the mailbox may be gone by the time the timer fires
243
+ if (!connection.socket || connection.socket.destroyed || connection.state !== connection.states.SELECTED || !connection.mailbox) {
244
+ return cancel();
245
+ }
246
+ pollOnce(connection, session)
247
+ .then(() => {
248
+ // Stamped only after a poll actually completed: a failed poll must not
249
+ // satisfy the resumed schedule below, or the next session would defer
250
+ // its first poll a full interval past an attempt that checked nothing.
251
+ connection._lastPollAt = Date.now();
252
+ // Cancellation is re-checked here: the session may have been broken while
253
+ // this poll was in flight, and an orphaned poller must not schedule again.
254
+ if (session.cancelled) {
255
+ return;
256
+ }
257
+ scheduleNextPoll(interval);
258
+ })
259
+ .catch((err) => {
260
+ (0, tools_js_1.logConnectionError)(connection, 'Failed to poll for mailbox changes', err);
261
+ cancel();
262
+ });
263
+ };
264
+ function scheduleNextPoll(delay) {
265
+ session.timer = setTimeout(runPoll, delay);
266
+ // Background polling must not keep the process alive
267
+ (0, tools_js_1.unrefTimer)(session.timer);
268
+ }
269
+ connection.log.debug({ msg: `Initiated NOOP loop`, cid: connection.id });
270
+ // Every auto-IDLE restart begins a fresh polling session, so an unconditional first
271
+ // poll would tie the poll rate to how often the caller runs commands rather than to
272
+ // `interval`: with a short autoIdleDelay, a command every few seconds turns into a
273
+ // poll every few seconds. The last poll timestamp lives on the connection, so a new
274
+ // session resumes the previous one's schedule instead of restarting it.
275
+ // Clamped at zero because a backward wall-clock step (NTP, VM resume) leaves the
276
+ // stamp in the future; however large the jump, the next poll must never be more
277
+ // than one full interval away.
278
+ let sinceLastPoll = Math.max(0, Date.now() - (connection._lastPollAt || 0));
279
+ if (sinceLastPoll >= interval) {
280
+ runPoll();
281
+ }
282
+ else {
283
+ scheduleNextPoll(interval - sinceLastPoll);
284
+ }
285
+ });
286
+ }
287
+ finally {
288
+ session.cancelled = true;
289
+ (0, tools_js_1.clearTimer)(session.timer);
290
+ session.timer = null;
291
+ releaseIdling();
292
+ if (connection.preCheck === session.preCheck) {
293
+ connection.preCheck = false;
294
+ }
295
+ }
296
+ }
297
+ /**
298
+ * Listens for changes in the selected mailbox using IDLE or NOOP polling fallback.
299
+ *
300
+ * @param connection - IMAP connection instance
301
+ * @param maxIdleTime - Maximum time in milliseconds to stay in IDLE before restarting
302
+ * @returns Void on success, false on failure, or undefined if not in SELECTED state
303
+ */
304
+ async function idle(connection, maxIdleTime) {
305
+ if (connection.state !== connection.states.SELECTED) {
306
+ // nothing to do here
307
+ return;
308
+ }
309
+ // If server supports IDLE (RFC 2177, folded into base IMAP4rev2), use it for
310
+ // real-time push notifications. Otherwise, fall back to periodic polling with
311
+ // NOOP/STATUS/SELECT.
312
+ if ((0, tools_js_1.hasCapability)(connection, 'IDLE')) {
313
+ let idleTimer;
314
+ let stillIdling = false;
315
+ // IDLE loop: runs IDLE, and if maxIdleTime is reached, breaks and restarts to keep the
316
+ // connection alive (some servers drop long-running IDLEs). Iterative rather than
317
+ // recursive, so a long-lived idling connection does not retain one pending frame per
318
+ // restart.
319
+ for (;;) {
320
+ if (maxIdleTime) {
321
+ idleTimer = setTimeout(() => {
322
+ if (connection.idling) {
323
+ if (typeof connection.preCheck === 'function') {
324
+ stillIdling = true;
325
+ // request IDLE break if IDLE has been running for allowed time
326
+ connection.log.trace({ msg: 'Max allowed IDLE time reached', cid: connection.id });
327
+ connection.preCheck().catch((err) => (0, tools_js_1.logConnectionError)(connection, 'Failed to break IDLE for restart', err));
328
+ }
329
+ }
330
+ }, maxIdleTime);
331
+ // Background IDLE restart timer must not keep the process alive
332
+ (0, tools_js_1.unrefTimer)(idleTimer);
333
+ }
334
+ let resp = await runIdle(connection);
335
+ clearTimeout(idleTimer);
336
+ if (!stillIdling) {
337
+ return resp;
338
+ }
339
+ stillIdling = false;
340
+ }
341
+ }
342
+ // Fallback for servers without IDLE support: poll at regular intervals using
343
+ // NOOP (default), STATUS, or SELECT depending on missingIdleCommand config.
344
+ return runPollingFallback(connection, maxIdleTime);
345
+ }
346
+ module.exports = exports.default;
347
+ Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -0,0 +1,16 @@
1
+ import type { ImapFlow } from '../imap-flow.js';
2
+ import type { ListOptions, ListResponse } from '../types.js';
3
+ /**
4
+ * Lists mailboxes from the server, including subscription status and special-use flags.
5
+ *
6
+ * @param connection - IMAP connection instance
7
+ * @param reference - Reference name (namespace prefix)
8
+ * @param mailbox - Mailbox name pattern with possible wildcards
9
+ * @param options - List options
10
+ * @param options.listOnly - If true, return entries after LIST without LSUB or status queries
11
+ * @param options.statusQuery - Status data items to query for each listed mailbox
12
+ * @param options.specialUseHints - Hints mapping mailbox paths to special-use types (sent, junk, trash, drafts, archive)
13
+ * @returns Array of mailbox entries sorted by special-use flags and name
14
+ * @throws If the LIST command fails
15
+ */
16
+ export default function list(connection: ImapFlow, reference: string | undefined, mailbox: string | undefined, options?: ListOptions | undefined): Promise<ListResponse[]>;