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
@@ -1,518 +0,0 @@
1
- 'use strict';
2
-
3
- // Fallback polling lifecycle (servers without IDLE): one explicitly identified polling session
4
- // that cannot run or reschedule after cancellation, reuses the real STATUS/SELECT state
5
- // handling, and always leaves `idling` describing reality.
6
-
7
- const idleCommand = require('../lib/commands/idle.js');
8
- const imapCommands = require('../lib/imap-commands.js');
9
- const { withFakeTimers } = require('./fixtures/fake-timers');
10
-
11
- const createConnection = (overrides = {}) => {
12
- const states = { NOT_AUTHENTICATED: 1, AUTHENTICATED: 2, SELECTED: 3, LOGOUT: 4 };
13
-
14
- const connection = {
15
- states,
16
- state: states.SELECTED,
17
- id: 'polling-test',
18
- capabilities: new Map(overrides.capabilities || []), // no IDLE -> polling fallback
19
- enabled: new Set(overrides.enabled || []),
20
- folders: new Map([['INBOX', { path: 'INBOX', delimiter: '/' }]]),
21
- mailbox: {
22
- path: 'INBOX',
23
- exists: 3,
24
- uidNext: 10,
25
- uidValidity: BigInt(1),
26
- highestModseq: BigInt(1)
27
- },
28
- namespace: { delimiter: '/', prefix: '' },
29
- socket: { destroyed: false },
30
- currentSelectCommand: { command: 'SELECT', arguments: [{ type: 'ATOM', value: 'INBOX' }] },
31
- missingIdleCommand: 'NOOP',
32
- idling: false,
33
- preCheck: false,
34
- events: [],
35
- commands: [],
36
- log: { warn: () => {}, info: () => {}, error: () => {}, debug: () => {}, trace: () => {} },
37
- emit(event, payload) {
38
- connection.events.push({ event, payload });
39
- },
40
- run: async () => false,
41
- // Fallback polling dispatches through runInternal(), which ImapFlow resolves against the
42
- // command registry - so a poll runs the real SELECT/STATUS implementation.
43
- runInternal: async (command, ...args) => {
44
- let handler = imapCommands.get(command.toUpperCase());
45
- return handler ? await handler(connection, ...args) : false;
46
- },
47
- exec: async (command, attributes, options) => {
48
- connection.commands.push(command);
49
- return connection.respond(command, attributes, options);
50
- },
51
- respond: async () => ({ next: () => {}, response: { attributes: [{ value: 'OK' }] } })
52
- };
53
-
54
- // capabilities/enabled are already converted into a Map/Set above
55
- let rest = Object.assign({}, overrides);
56
- delete rest.capabilities;
57
- delete rest.enabled;
58
-
59
- return Object.assign(connection, rest);
60
- };
61
-
62
- module.exports['Polling: break before the first poll stops the loop'] = async test => {
63
- await withFakeTimers(async timers => {
64
- let connection = createConnection({
65
- exec: async command => {
66
- connection.commands.push(command);
67
- // break while the very first poll is still in flight
68
- await connection.preCheck();
69
- return { next: () => {} };
70
- }
71
- });
72
-
73
- let idlePromise = idleCommand(connection, 60000);
74
- await timers.drain();
75
- await idlePromise;
76
-
77
- test.deepEqual(connection.commands, ['NOOP'], 'only the immediate first poll ran');
78
- test.equal(connection.idling, false, 'idling reset after the break');
79
- test.equal(connection.preCheck, false, 'the session released preCheck');
80
- test.equal(timers.count(), 0, 'no polling timer is left armed');
81
-
82
- // Advancing several intervals must not produce another command
83
- await timers.fire();
84
- await timers.fire();
85
- test.deepEqual(connection.commands, ['NOOP'], 'a cancelled session never polls again');
86
- test.done();
87
- });
88
- };
89
-
90
- module.exports['Polling: break during an in-flight poll cannot reschedule'] = async test => {
91
- await withFakeTimers(async timers => {
92
- let releasePoll;
93
- let pollStarted;
94
- let connection = createConnection({
95
- exec: async command => {
96
- connection.commands.push(command);
97
- await new Promise(resolve => {
98
- releasePoll = resolve;
99
- if (pollStarted) {
100
- pollStarted();
101
- }
102
- });
103
- return { next: () => {} };
104
- }
105
- });
106
-
107
- let started = new Promise(resolve => (pollStarted = resolve));
108
- let idlePromise = idleCommand(connection, 60000);
109
- await started;
110
-
111
- // Cancel while the poll is still awaiting its response, then let it finish
112
- await connection.preCheck();
113
- releasePoll();
114
- await idlePromise;
115
-
116
- test.deepEqual(connection.commands, ['NOOP'], 'the in-flight poll completed but scheduled nothing');
117
- test.equal(timers.count(), 0, 'the completing poll did not arm a new timer');
118
- test.equal(connection.idling, false, 'idling reset');
119
-
120
- await timers.fire();
121
- test.deepEqual(connection.commands, ['NOOP'], 'no later command is sent');
122
- test.done();
123
- });
124
- };
125
-
126
- module.exports['Polling: break after a poll but before the next timer fires stops the loop'] = async test => {
127
- await withFakeTimers(async timers => {
128
- let connection = createConnection();
129
-
130
- let idlePromise = idleCommand(connection, 60000);
131
- await timers.drain();
132
-
133
- test.deepEqual(connection.commands, ['NOOP'], 'the immediate first poll ran');
134
- test.equal(timers.count(), 1, 'the next poll is scheduled');
135
- test.ok(timers.pending()[0].unrefd, 'the background polling timer does not keep the process alive');
136
-
137
- await connection.preCheck();
138
- await idlePromise;
139
-
140
- test.equal(timers.count(), 0, 'the armed timer was cleared on cancellation');
141
-
142
- await timers.fire();
143
- await timers.fire();
144
- test.deepEqual(connection.commands, ['NOOP'], 'no command after cancellation');
145
- test.done();
146
- });
147
- };
148
-
149
- module.exports['Polling: an old session does not clear a newer session preCheck'] = async test => {
150
- await withFakeTimers(async timers => {
151
- let releasePoll;
152
- let pollStarted;
153
- let connection = createConnection({
154
- exec: async command => {
155
- connection.commands.push(command);
156
- if (connection.commands.length === 1) {
157
- await new Promise(resolve => {
158
- releasePoll = resolve;
159
- pollStarted();
160
- });
161
- }
162
- return { next: () => {} };
163
- }
164
- });
165
-
166
- let started = new Promise(resolve => (pollStarted = resolve));
167
- let firstSession = idleCommand(connection, 60000);
168
- await started;
169
-
170
- // Cancel the first session while its poll is in flight, then immediately re-enter IDLE
171
- await connection.preCheck();
172
- let secondSession = idleCommand(connection, 60000);
173
- await timers.drain();
174
-
175
- let newPreCheck = connection.preCheck;
176
- test.equal(typeof newPreCheck, 'function', 'the new session installed its own preCheck');
177
-
178
- // The stale poll now finishes: it must not touch the new session's ownership
179
- releasePoll();
180
- await firstSession;
181
-
182
- test.equal(connection.preCheck, newPreCheck, 'the stale session left the newer preCheck in place');
183
- test.equal(connection.idling, true, 'the stale session did not clear the newer idling state');
184
-
185
- await connection.preCheck();
186
- await secondSession;
187
- test.equal(connection.idling, false, 'the newer session cleaned up on its own break');
188
- test.done();
189
- });
190
- };
191
-
192
- module.exports['Polling: STATUS polling applies mailbox state and emits exists only on change'] = async test => {
193
- await withFakeTimers(async timers => {
194
- let messages = 3;
195
- let connection = createConnection({
196
- capabilities: [['CONDSTORE', true]],
197
- missingIdleCommand: 'STATUS',
198
- respond: async (command, attributes, options) => {
199
- if (command === 'STATUS') {
200
- let statusHandler = options.untagged.STATUS;
201
- await statusHandler({
202
- attributes: [
203
- { type: 'STRING', value: 'INBOX' },
204
- [
205
- { type: 'ATOM', value: 'MESSAGES' },
206
- { type: 'ATOM', value: String(messages) },
207
- { type: 'ATOM', value: 'UIDNEXT' },
208
- { type: 'ATOM', value: '11' },
209
- { type: 'ATOM', value: 'HIGHESTMODSEQ' },
210
- { type: 'ATOM', value: '42' }
211
- ]
212
- ]
213
- });
214
- }
215
- return { next: () => {}, response: { attributes: [{ value: 'OK' }] } };
216
- }
217
- });
218
-
219
- let idlePromise = idleCommand(connection, 60000);
220
- await timers.drain();
221
-
222
- test.deepEqual(connection.commands, ['STATUS'], 'STATUS was used for polling');
223
- test.equal(connection.mailbox.uidNext, 11, 'uidNext updated from the poll');
224
- test.equal(connection.mailbox.highestModseq, BigInt(42), 'highestModseq updated on a CONDSTORE session');
225
- test.deepEqual(connection.events, [], 'an unchanged message count emits no event');
226
-
227
- // Second poll reports a new count -> exactly one exists event
228
- messages = 5;
229
- await timers.fire();
230
-
231
- test.equal(connection.mailbox.exists, 5, 'exists updated from the poll');
232
- test.equal(connection.events.length, 1, 'one event for the changed count');
233
- test.equal(connection.events[0].event, 'exists');
234
- test.equal(connection.events[0].payload.count, 5);
235
- test.equal(connection.events[0].payload.prevCount, 3);
236
-
237
- await connection.preCheck();
238
- await idlePromise;
239
- test.done();
240
- });
241
- };
242
-
243
- module.exports['Polling: STATUS omits HIGHESTMODSEQ without CONDSTORE'] = async test => {
244
- await withFakeTimers(async timers => {
245
- let requestedItems = [];
246
- let connection = createConnection({
247
- missingIdleCommand: 'STATUS',
248
- respond: async (command, attributes) => {
249
- if (command === 'STATUS') {
250
- requestedItems = attributes[1].map(entry => entry.value);
251
- }
252
- return { next: () => {}, response: { attributes: [{ value: 'OK' }] } };
253
- }
254
- });
255
-
256
- let idlePromise = idleCommand(connection, 60000);
257
- await timers.drain();
258
-
259
- test.ok(requestedItems.includes('MESSAGES'), 'MESSAGES is polled');
260
- test.ok(requestedItems.includes('UIDNEXT'), 'UIDNEXT is polled');
261
- test.ok(!requestedItems.includes('HIGHESTMODSEQ'), 'HIGHESTMODSEQ is not requested without CONDSTORE');
262
-
263
- await connection.preCheck();
264
- await idlePromise;
265
- test.done();
266
- });
267
- };
268
-
269
- module.exports['Polling: failed SELECT polling deselects and stops the loop'] = async test => {
270
- await withFakeTimers(async timers => {
271
- let connection = createConnection({
272
- missingIdleCommand: 'SELECT',
273
- respond: async command => {
274
- if (command === 'SELECT') {
275
- let err = new Error('Command failed');
276
- err.responseStatus = 'NO';
277
- throw err;
278
- }
279
- return { next: () => {}, response: { attributes: [{ value: 'OK' }] } };
280
- }
281
- });
282
-
283
- let idlePromise = idleCommand(connection, 60000);
284
- await timers.drain();
285
- await idlePromise;
286
-
287
- test.equal(connection.state, connection.states.AUTHENTICATED, 'a failed reselect drops to AUTHENTICATED');
288
- test.equal(connection.mailbox, false, 'mailbox state cleared');
289
- test.equal(connection.currentSelectCommand, false, 'the saved select command is cleared');
290
- test.equal(connection.idling, false, 'idling reset');
291
- test.equal(connection.preCheck, false, 'preCheck released');
292
- test.equal(timers.count(), 0, 'polling stopped');
293
- test.ok(
294
- connection.events.some(entry => entry.event === 'mailboxClose'),
295
- 'the mailbox close transition was emitted, as with a caller-issued SELECT'
296
- );
297
- test.done();
298
- });
299
- };
300
-
301
- module.exports['Polling: a closed connection stops the loop instead of polling'] = async test => {
302
- await withFakeTimers(async timers => {
303
- let connection = createConnection();
304
-
305
- let idlePromise = idleCommand(connection, 60000);
306
- await timers.drain();
307
- test.deepEqual(connection.commands, ['NOOP'], 'first poll ran on a live connection');
308
-
309
- // Transport dies between polls
310
- connection.socket.destroyed = true;
311
- await timers.fire();
312
- await idlePromise;
313
-
314
- test.deepEqual(connection.commands, ['NOOP'], 'no command is sent on a dead transport');
315
- test.equal(connection.idling, false, 'idling reset when the loop stops');
316
- test.equal(connection.preCheck, false, 'preCheck released');
317
- test.done();
318
- });
319
- };
320
-
321
- module.exports['Polling: a rejected poll resets idling'] = async test => {
322
- await withFakeTimers(async timers => {
323
- let connection = createConnection({
324
- exec: async command => {
325
- connection.commands.push(command);
326
- let err = new Error('Connection not available');
327
- err.code = 'NoConnection';
328
- throw err;
329
- }
330
- });
331
-
332
- let idlePromise = idleCommand(connection, 60000);
333
- await timers.drain();
334
- await idlePromise;
335
-
336
- test.equal(connection.idling, false, 'idling reset after a rejected poll');
337
- test.equal(connection.preCheck, false, 'preCheck released after a rejected poll');
338
- test.equal(timers.count(), 0, 'nothing left scheduled');
339
- test.done();
340
- });
341
- };
342
-
343
- module.exports['Polling: no polling session without a saved select command'] = async test => {
344
- let connection = createConnection({ currentSelectCommand: false });
345
- await idleCommand(connection, 60000);
346
- test.deepEqual(connection.commands, [], 'nothing polled');
347
- test.equal(connection.idling, false, 'idling untouched');
348
- test.done();
349
- };
350
-
351
- module.exports['Polling: a falsy STATUS result stops the loop as PollFailed'] = async test => {
352
- await withFakeTimers(async timers => {
353
- let warnings = [];
354
- let connection = createConnection({
355
- missingIdleCommand: 'STATUS',
356
- // Only STATUS is ever polled; a failure without a NO status makes the
357
- // real STATUS implementation swallow the error and return false
358
- respond: async () => {
359
- throw new Error('Command failed');
360
- }
361
- });
362
- connection.log.warn = entry => warnings.push(entry);
363
-
364
- let idlePromise = idleCommand(connection, 60000);
365
- await timers.drain();
366
- await idlePromise;
367
-
368
- test.ok(
369
- warnings.some(entry => entry && entry.err && entry.err.code === 'PollFailed'),
370
- 'the falsy STATUS result surfaced as a PollFailed error'
371
- );
372
- test.equal(connection.idling, false, 'idling reset after the failed poll');
373
- test.equal(connection.preCheck, false, 'preCheck released');
374
- test.equal(timers.count(), 0, 'polling stopped');
375
- test.done();
376
- });
377
- };
378
-
379
- module.exports['Polling: a repeated break call is a no-op'] = async test => {
380
- await withFakeTimers(async timers => {
381
- let connection = createConnection();
382
-
383
- let idlePromise = idleCommand(connection, 60000);
384
- await timers.drain();
385
- test.deepEqual(connection.commands, ['NOOP'], 'the immediate first poll ran');
386
-
387
- // A caller may hold on to the break function and invoke it more than once
388
- let preCheck = connection.preCheck;
389
- await preCheck();
390
- await preCheck();
391
- await idlePromise;
392
-
393
- test.equal(connection.idling, false, 'idling reset');
394
- test.equal(connection.preCheck, false, 'preCheck released');
395
- test.equal(timers.count(), 0, 'no timer left armed');
396
-
397
- await timers.fire();
398
- test.deepEqual(connection.commands, ['NOOP'], 'no further poll after the duplicate break');
399
- test.done();
400
- });
401
- };
402
-
403
- module.exports['Polling: a break in the initiation tick prevents the first poll'] = async test => {
404
- await withFakeTimers(async timers => {
405
- let connection = createConnection();
406
- // Simulate a break request (e.g. a command being queued) landing in the same
407
- // tick the loop is initiated: trap the loop installing its own preCheck and
408
- // break through it immediately, before the first poll has started
409
- let installedPreCheck = connection.preCheck;
410
- Object.defineProperty(connection, 'preCheck', {
411
- get: () => installedPreCheck,
412
- set: value => {
413
- installedPreCheck = value;
414
- if (typeof value === 'function') {
415
- value();
416
- }
417
- }
418
- });
419
-
420
- let idlePromise = idleCommand(connection, 60000);
421
- await timers.drain();
422
- await idlePromise;
423
-
424
- test.deepEqual(connection.commands, [], 'the already-cancelled session never polled');
425
- test.equal(connection.idling, false, 'idling reset');
426
- test.equal(connection.preCheck, false, 'preCheck released');
427
- test.equal(timers.count(), 0, 'no timer left armed');
428
- test.done();
429
- });
430
- };
431
-
432
- module.exports['Polling: a restarted session resumes the schedule instead of polling again'] = async test => {
433
- await withFakeTimers(async timers => {
434
- let connection = createConnection();
435
-
436
- let first = idleCommand(connection, 60000);
437
- await timers.drain();
438
- test.deepEqual(connection.commands, ['NOOP'], 'the first session polls immediately');
439
-
440
- await connection.preCheck();
441
- await first;
442
-
443
- // Auto-IDLE restarts the loop after every caller command. An unconditional first poll here
444
- // would tie the poll rate to how often the caller runs commands instead of to the poll
445
- // interval - with a short autoIdleDelay, a command every few seconds means a poll every
446
- // few seconds.
447
- let second = idleCommand(connection, 60000);
448
- await timers.drain();
449
- test.deepEqual(connection.commands, ['NOOP'], 'the restarted session does not poll again');
450
- test.equal(timers.count(), 1, 'it waits out the remainder of the interval instead');
451
- test.ok(timers.pending()[0].delay <= 60000, 'and never longer than a full interval');
452
-
453
- await connection.preCheck();
454
- await second;
455
-
456
- // Once a full interval has elapsed, a fresh session polls at once again
457
- connection._lastPollAt = Date.now() - 61000;
458
- let third = idleCommand(connection, 60000);
459
- await timers.drain();
460
- test.deepEqual(connection.commands, ['NOOP', 'NOOP'], 'a session starting after the interval polls right away');
461
-
462
- await connection.preCheck();
463
- await third;
464
- test.done();
465
- });
466
- };
467
-
468
- module.exports['Polling: a failed poll does not defer the next session'] = async test => {
469
- await withFakeTimers(async timers => {
470
- let failNext = true;
471
- let connection = createConnection({
472
- exec: async command => {
473
- connection.commands.push(command);
474
- if (failNext) {
475
- failNext = false;
476
- throw new Error('poll failed');
477
- }
478
- return { next: () => {} };
479
- }
480
- });
481
-
482
- // The failing poll cancels its own session. The attempt checked nothing, so it must not
483
- // count as a poll: only a completed poll moves the schedule stamp forward.
484
- let first = idleCommand(connection, 60000);
485
- await timers.drain();
486
- await first;
487
- test.deepEqual(connection.commands, ['NOOP'], 'the first poll ran and failed');
488
-
489
- let second = idleCommand(connection, 60000);
490
- await timers.drain();
491
- test.deepEqual(connection.commands, ['NOOP', 'NOOP'], 'the next session retries immediately instead of waiting out an interval');
492
-
493
- await connection.preCheck();
494
- await second;
495
- test.done();
496
- });
497
- };
498
-
499
- module.exports['Polling: a backward clock step never defers the next poll past one interval'] = async test => {
500
- await withFakeTimers(async timers => {
501
- let connection = createConnection();
502
-
503
- // A last-poll stamp in the future is what an NTP step or a VM clock sync leaves behind.
504
- // Without the clamp the remainder math would schedule the next poll a full clock jump
505
- // plus one interval away.
506
- connection._lastPollAt = Date.now() + 60 * 60 * 1000;
507
-
508
- let idlePromise = idleCommand(connection, 60000);
509
- await timers.drain();
510
- test.deepEqual(connection.commands, [], 'no immediate poll - the schedule is resumed');
511
- test.equal(timers.count(), 1, 'a poll timer is armed');
512
- test.ok(timers.pending()[0].delay <= 60000, 'and it is never more than one interval away');
513
-
514
- await connection.preCheck();
515
- await idlePromise;
516
- test.done();
517
- });
518
- };