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,667 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Tests for memory leak detection in socket, TLS, and event handling
5
- */
6
-
7
- const net = require('net');
8
- const zlib = require('zlib');
9
- const { PassThrough } = require('stream');
10
- const { ImapFlow } = require('../lib/imap-flow');
11
-
12
- // Helper to get listener counts for key objects
13
- function getListenerReport(client) {
14
- return {
15
- streamer: client.streamer
16
- ? {
17
- error: client.streamer.listenerCount('error'),
18
- readable: client.streamer.listenerCount('readable')
19
- }
20
- : null,
21
- socket: client.socket
22
- ? {
23
- error: client.socket.listenerCount('error'),
24
- close: client.socket.listenerCount('close'),
25
- end: client.socket.listenerCount('end'),
26
- timeout: client.socket.listenerCount('timeout'),
27
- tlsClientError: client.socket.listenerCount('tlsClientError')
28
- }
29
- : null,
30
- client: {
31
- error: client.listenerCount('error'),
32
- close: client.listenerCount('close')
33
- },
34
- inflate: client._inflate ? client._inflate.listenerCount('error') : 0,
35
- deflate: client._deflate ? client._deflate.listenerCount('error') : 0
36
- };
37
- }
38
-
39
- // Helper to measure memory (requires --expose-gc flag)
40
- // Usage: node --expose-gc -e "require('./test/memory-leak-test.js')"
41
- // eslint-disable-next-line no-unused-vars
42
- async function measureMemory(label) {
43
- if (global.gc) {
44
- global.gc();
45
- await new Promise(r => setTimeout(r, 50));
46
- global.gc();
47
- }
48
- const mem = process.memoryUsage();
49
- return {
50
- label,
51
- heapUsedMB: Math.round((mem.heapUsed / 1024 / 1024) * 100) / 100,
52
- externalMB: Math.round((mem.external / 1024 / 1024) * 100) / 100
53
- };
54
- }
55
-
56
- // Create a simple mock IMAP server for testing
57
- function createMockServer() {
58
- const server = net.createServer(socket => {
59
- // Send greeting
60
- socket.write('* OK Mock IMAP Server ready\r\n');
61
-
62
- socket.on('data', data => {
63
- const line = data.toString().trim();
64
- const parts = line.split(' ');
65
- const tag = parts[0];
66
- const command = parts[1] ? parts[1].toUpperCase() : '';
67
-
68
- if (command === 'CAPABILITY') {
69
- socket.write('* CAPABILITY IMAP4rev1 AUTH=PLAIN\r\n');
70
- socket.write(`${tag} OK CAPABILITY completed\r\n`);
71
- } else if (command === 'LOGIN') {
72
- socket.write(`${tag} OK LOGIN completed\r\n`);
73
- } else if (command === 'LOGOUT') {
74
- socket.write('* BYE Server logging out\r\n');
75
- socket.write(`${tag} OK LOGOUT completed\r\n`);
76
- socket.end();
77
- } else if (command === 'NOOP') {
78
- socket.write(`${tag} OK NOOP completed\r\n`);
79
- } else if (command === 'NAMESPACE') {
80
- socket.write('* NAMESPACE (("" "/")) NIL NIL\r\n');
81
- socket.write(`${tag} OK NAMESPACE completed\r\n`);
82
- } else if (command === 'COMPRESS') {
83
- socket.write(`${tag} NO COMPRESS not supported\r\n`);
84
- } else if (command === 'ENABLE') {
85
- socket.write(`${tag} OK ENABLE completed\r\n`);
86
- } else if (command === 'ID') {
87
- socket.write('* ID NIL\r\n');
88
- socket.write(`${tag} OK ID completed\r\n`);
89
- } else if (tag && command) {
90
- socket.write(`${tag} OK Command completed\r\n`);
91
- }
92
- });
93
-
94
- socket.on('error', () => {
95
- // Ignore errors
96
- });
97
- });
98
-
99
- return server;
100
- }
101
-
102
- exports['Memory Leak Tests'] = {
103
- 'should not leak listeners after multiple client creations without connect'(test) {
104
- const clients = [];
105
-
106
- // Create multiple clients without connecting
107
- for (let i = 0; i < 50; i++) {
108
- const client = new ImapFlow({
109
- host: '127.0.0.1',
110
- port: 1,
111
- secure: false,
112
- logger: false
113
- });
114
- clients.push(client);
115
- }
116
-
117
- // Close all clients
118
- for (const client of clients) {
119
- client.close();
120
- }
121
-
122
- // Verify all clients are cleaned up
123
- for (const client of clients) {
124
- const report = getListenerReport(client);
125
- test.equal(report.streamer.error, 0, 'streamer error listeners should be 0');
126
- test.equal(report.streamer.readable, 0, 'streamer readable listeners should be 0');
127
- test.ok(client.isClosed, 'client should be closed');
128
- }
129
-
130
- test.done();
131
- },
132
-
133
- 'should clean up listeners after connect error'(test) {
134
- test.expect(4);
135
-
136
- const client = new ImapFlow({
137
- host: '127.0.0.1',
138
- port: 1, // Invalid port, will fail to connect
139
- secure: false,
140
- logger: false,
141
- connectionTimeout: 500
142
- });
143
-
144
- client.connect().catch(() => {
145
- // Wait a bit for cleanup
146
- setTimeout(() => {
147
- const report = getListenerReport(client);
148
- test.equal(report.streamer.error, 0, 'streamer error listeners should be 0 after error');
149
- test.equal(report.socket, null, 'socket should be null after error');
150
- test.ok(client.isClosed, 'client should be closed after error');
151
- test.equal(client.state, client.states.LOGOUT, 'state should be LOGOUT');
152
- test.done();
153
- }, 100);
154
- });
155
- },
156
-
157
- async 'should clean up listeners after successful connect and close'(test) {
158
- test.expect(6);
159
-
160
- const server = createMockServer();
161
-
162
- server.listen(0, '127.0.0.1', async () => {
163
- const port = server.address().port;
164
-
165
- const client = new ImapFlow({
166
- host: '127.0.0.1',
167
- port,
168
- secure: false,
169
- logger: false,
170
- auth: {
171
- user: 'test',
172
- pass: 'test'
173
- }
174
- });
175
-
176
- try {
177
- await client.connect();
178
-
179
- // Verify we're connected
180
- test.ok(client.usable, 'client should be usable after connect');
181
-
182
- // Get listener report while connected
183
- const connectedReport = getListenerReport(client);
184
- test.ok(connectedReport.socket !== null, 'socket should exist while connected');
185
-
186
- await client.logout();
187
-
188
- // Wait for cleanup
189
- await new Promise(r => setTimeout(r, 100));
190
-
191
- const closedReport = getListenerReport(client);
192
- test.equal(closedReport.streamer.error, 0, 'streamer error listeners should be 0');
193
- test.equal(closedReport.streamer.readable, 0, 'streamer readable listeners should be 0');
194
- test.ok(client.isClosed, 'client should be closed');
195
- test.equal(client.state, client.states.LOGOUT, 'state should be LOGOUT');
196
- } catch (err) {
197
- test.ok(false, 'should not throw: ' + err.message);
198
- } finally {
199
- server.close();
200
- test.done();
201
- }
202
- });
203
- },
204
-
205
- async 'should not accumulate listeners over multiple connect/disconnect cycles'(test) {
206
- const server = createMockServer();
207
-
208
- server.listen(0, '127.0.0.1', async () => {
209
- const port = server.address().port;
210
- const cycles = 10;
211
-
212
- for (let i = 0; i < cycles; i++) {
213
- const client = new ImapFlow({
214
- host: '127.0.0.1',
215
- port,
216
- secure: false,
217
- logger: false,
218
- auth: {
219
- user: 'test',
220
- pass: 'test'
221
- }
222
- });
223
-
224
- try {
225
- await client.connect();
226
- await client.logout();
227
- } catch {
228
- // Ignore connection errors
229
- }
230
-
231
- // Wait for cleanup
232
- await new Promise(r => setTimeout(r, 50));
233
-
234
- const report = getListenerReport(client);
235
- test.equal(report.streamer.error, 0, `cycle ${i + 1}: streamer error listeners should be 0`);
236
- test.equal(report.streamer.readable, 0, `cycle ${i + 1}: streamer readable listeners should be 0`);
237
- }
238
-
239
- server.close();
240
- test.done();
241
- });
242
- },
243
-
244
- 'should clean up socket handlers after close'(test) {
245
- const client = new ImapFlow({
246
- host: '127.0.0.1',
247
- port: 1,
248
- secure: false,
249
- logger: false
250
- });
251
-
252
- // Simulate having socket handlers set
253
- const mockSocket = new net.Socket();
254
- client.socket = mockSocket;
255
- client.writeSocket = mockSocket;
256
-
257
- // Set up handlers like connect() would
258
- client._socketError = () => {};
259
- client._socketClose = () => {};
260
- client._socketEnd = () => {};
261
- client._socketTimeout = () => {};
262
-
263
- mockSocket.on('error', client._socketError);
264
- mockSocket.on('close', client._socketClose);
265
- mockSocket.on('end', client._socketEnd);
266
- mockSocket.on('timeout', client._socketTimeout);
267
-
268
- // Verify listeners are set
269
- test.equal(mockSocket.listenerCount('error'), 1, 'error listener should be set');
270
- test.equal(mockSocket.listenerCount('close'), 1, 'close listener should be set');
271
-
272
- // Close the client
273
- client.close();
274
-
275
- // Verify cleanup (socket is destroyed, so listeners are removed)
276
- test.ok(client.isClosed, 'client should be closed');
277
-
278
- test.done();
279
- },
280
-
281
- 'should not leak memory on repeated streamer error handler registrations'(test) {
282
- // This test verifies that the streamer error handler pattern doesn't leak
283
- const clients = [];
284
-
285
- for (let i = 0; i < 20; i++) {
286
- const client = new ImapFlow({
287
- host: '127.0.0.1',
288
- port: 1,
289
- secure: false,
290
- logger: false
291
- });
292
-
293
- // Initially, streamer should have error listener from constructor
294
- test.ok(client.streamer.listenerCount('error') >= 1, 'streamer should have error listener');
295
-
296
- clients.push(client);
297
- }
298
-
299
- // Close all and verify
300
- for (const client of clients) {
301
- client.close();
302
- test.equal(client.streamer.listenerCount('error'), 0, 'streamer error listeners should be removed');
303
- }
304
-
305
- test.done();
306
- },
307
-
308
- 'should handle rapid create/destroy cycles'(test) {
309
- const iterations = 100;
310
- let completed = 0;
311
-
312
- for (let i = 0; i < iterations; i++) {
313
- const client = new ImapFlow({
314
- host: '127.0.0.1',
315
- port: 1,
316
- secure: false,
317
- logger: false
318
- });
319
-
320
- client.close();
321
- completed++;
322
-
323
- // Spot check listener cleanup
324
- if (i % 10 === 0) {
325
- test.equal(client.streamer.listenerCount('error'), 0, `iteration ${i}: error listeners should be 0`);
326
- }
327
- }
328
-
329
- test.equal(completed, iterations, 'all iterations should complete');
330
- test.done();
331
- },
332
-
333
- 'should export helper functions for external memory testing'(test) {
334
- // Verify helpers are working correctly
335
- const client = new ImapFlow({
336
- host: '127.0.0.1',
337
- port: 1,
338
- secure: false,
339
- logger: false
340
- });
341
-
342
- const report = getListenerReport(client);
343
-
344
- test.ok(report.streamer !== null, 'report should include streamer');
345
- test.ok(typeof report.streamer.error === 'number', 'error count should be a number');
346
- test.ok(typeof report.streamer.readable === 'number', 'readable count should be a number');
347
- test.ok(report.client !== null, 'report should include client');
348
-
349
- client.close();
350
- test.done();
351
- }
352
- };
353
-
354
- exports['Compression Stream Tests'] = {
355
- 'should clean up compression streams on close'(test) {
356
- const client = new ImapFlow({
357
- host: '127.0.0.1',
358
- port: 1,
359
- secure: false,
360
- logger: false
361
- });
362
-
363
- // Simulate compression being enabled by manually setting up streams
364
- client._deflate = zlib.createDeflateRaw();
365
- client._inflate = zlib.createInflateRaw();
366
-
367
- // Add error handlers like compress() does
368
- client._deflate.on('error', () => {});
369
- client._inflate.on('error', () => {});
370
-
371
- // Create writeSocket like compress() does
372
- const writeSocket = new PassThrough();
373
- writeSocket.on('readable', () => {});
374
- writeSocket.on('error', () => {});
375
- client.writeSocket = writeSocket;
376
-
377
- // Verify streams exist
378
- test.ok(client._deflate, 'deflate should exist');
379
- test.ok(client._inflate, 'inflate should exist');
380
- test.ok(client.writeSocket, 'writeSocket should exist');
381
-
382
- // Close the client
383
- client.close();
384
-
385
- // Verify compression streams are cleaned up
386
- test.equal(client._deflate, null, 'deflate should be null after close');
387
- test.equal(client._inflate, null, 'inflate should be null after close');
388
- test.equal(client.writeSocket, null, 'writeSocket should be null after close');
389
- test.ok(client.isClosed, 'client should be closed');
390
-
391
- test.done();
392
- },
393
-
394
- 'should not leak compression stream listeners over multiple cycles'(test) {
395
- // Create multiple deflate/inflate streams to verify they don't accumulate
396
- const streams = [];
397
-
398
- for (let i = 0; i < 10; i++) {
399
- const client = new ImapFlow({
400
- host: '127.0.0.1',
401
- port: 1,
402
- secure: false,
403
- logger: false
404
- });
405
-
406
- // Simulate compression setup
407
- client._deflate = zlib.createDeflateRaw();
408
- client._inflate = zlib.createInflateRaw();
409
- client._deflate.on('error', () => {});
410
- client._inflate.on('error', () => {});
411
-
412
- streams.push({
413
- deflate: client._deflate,
414
- inflate: client._inflate
415
- });
416
-
417
- // Close immediately
418
- client.close();
419
-
420
- // Verify cleanup
421
- test.equal(client._deflate, null, `cycle ${i + 1}: deflate should be null`);
422
- test.equal(client._inflate, null, `cycle ${i + 1}: inflate should be null`);
423
- }
424
-
425
- // Verify all streams are destroyed
426
- for (let i = 0; i < streams.length; i++) {
427
- test.ok(streams[i].deflate.destroyed, `cycle ${i + 1}: deflate should be destroyed`);
428
- test.ok(streams[i].inflate.destroyed, `cycle ${i + 1}: inflate should be destroyed`);
429
- }
430
-
431
- test.done();
432
- },
433
-
434
- 'should handle compression stream errors without leaking'(test) {
435
- const client = new ImapFlow({
436
- host: '127.0.0.1',
437
- port: 1,
438
- secure: false,
439
- logger: false
440
- });
441
-
442
- // Setup compression streams with error handlers
443
- client._deflate = zlib.createDeflateRaw();
444
- client._inflate = zlib.createInflateRaw();
445
-
446
- // Add error handlers (like compress() does)
447
- client._deflate.on('error', () => {});
448
- client._inflate.on('error', () => {});
449
-
450
- // Verify initial listener counts
451
- test.equal(client._deflate.listenerCount('error'), 1, 'deflate should have 1 error listener');
452
- test.equal(client._inflate.listenerCount('error'), 1, 'inflate should have 1 error listener');
453
-
454
- // Close and verify cleanup
455
- client.close();
456
-
457
- test.equal(client._deflate, null, 'deflate should be null');
458
- test.equal(client._inflate, null, 'inflate should be null');
459
-
460
- test.done();
461
- }
462
- };
463
-
464
- exports['Fetch Stream Tests'] = {
465
- 'should clean up internal fetch state on close'(test) {
466
- const client = new ImapFlow({
467
- host: '127.0.0.1',
468
- port: 1,
469
- secure: false,
470
- logger: false
471
- });
472
-
473
- // Simulate some internal state that would exist during fetch
474
- client.requestTagMap.set('A001', { tag: 'A001', command: 'FETCH' });
475
- client.requestTagMap.set('A002', { tag: 'A002', command: 'FETCH' });
476
-
477
- test.equal(client.requestTagMap.size, 2, 'should have 2 pending requests');
478
-
479
- // Close the client
480
- client.close();
481
-
482
- // Verify request map is cleared
483
- test.equal(client.requestTagMap.size, 0, 'requestTagMap should be cleared after close');
484
- test.ok(client.isClosed, 'client should be closed');
485
-
486
- test.done();
487
- },
488
-
489
- 'should clean up mailbox state on close'(test) {
490
- const client = new ImapFlow({
491
- host: '127.0.0.1',
492
- port: 1,
493
- secure: false,
494
- logger: false
495
- });
496
-
497
- // Simulate mailbox state
498
- client.folders.set('INBOX', { path: 'INBOX', exists: 100 });
499
- client.folders.set('Sent', { path: 'Sent', exists: 50 });
500
- client.folders.set('Drafts', { path: 'Drafts', exists: 10 });
501
-
502
- test.equal(client.folders.size, 3, 'should have 3 folders cached');
503
-
504
- // Close the client
505
- client.close();
506
-
507
- // Verify folders are cleared
508
- test.equal(client.folders.size, 0, 'folders should be cleared after close');
509
-
510
- test.done();
511
- },
512
-
513
- async 'should clean up after fetch with mock server'(test) {
514
- const server = createMockServerWithFetch();
515
-
516
- server.listen(0, '127.0.0.1', async () => {
517
- const port = server.address().port;
518
-
519
- const client = new ImapFlow({
520
- host: '127.0.0.1',
521
- port,
522
- secure: false,
523
- logger: false,
524
- auth: {
525
- user: 'test',
526
- pass: 'test'
527
- }
528
- });
529
-
530
- try {
531
- await client.connect();
532
-
533
- // Select INBOX
534
- await client.mailboxOpen('INBOX');
535
-
536
- // Verify we're in selected state
537
- test.ok(client.mailbox, 'mailbox should be selected');
538
-
539
- await client.logout();
540
-
541
- // Wait for cleanup
542
- await new Promise(r => setTimeout(r, 100));
543
-
544
- // Verify cleanup
545
- const report = getListenerReport(client);
546
- test.equal(report.streamer.error, 0, 'streamer error listeners should be 0');
547
- test.equal(report.streamer.readable, 0, 'streamer readable listeners should be 0');
548
- test.ok(client.isClosed, 'client should be closed');
549
- } catch (err) {
550
- test.ok(false, 'should not throw: ' + err.message);
551
- } finally {
552
- server.close();
553
- test.done();
554
- }
555
- });
556
- },
557
-
558
- async 'should not leak listeners during multiple mailbox operations'(test) {
559
- const server = createMockServerWithFetch();
560
-
561
- server.listen(0, '127.0.0.1', async () => {
562
- const port = server.address().port;
563
-
564
- const client = new ImapFlow({
565
- host: '127.0.0.1',
566
- port,
567
- secure: false,
568
- logger: false,
569
- auth: {
570
- user: 'test',
571
- pass: 'test'
572
- }
573
- });
574
-
575
- try {
576
- await client.connect();
577
-
578
- // Perform multiple mailbox operations
579
- for (let i = 0; i < 5; i++) {
580
- await client.mailboxOpen('INBOX');
581
- await client.mailboxClose();
582
- }
583
-
584
- await client.logout();
585
-
586
- // Wait for cleanup
587
- await new Promise(r => setTimeout(r, 100));
588
-
589
- // Verify no listener accumulation
590
- const report = getListenerReport(client);
591
- test.equal(report.streamer.error, 0, 'streamer error listeners should be 0');
592
- test.equal(report.streamer.readable, 0, 'streamer readable listeners should be 0');
593
- } catch (err) {
594
- test.ok(false, 'should not throw: ' + err.message);
595
- } finally {
596
- server.close();
597
- test.done();
598
- }
599
- });
600
- }
601
- };
602
-
603
- // Create a mock server that supports SELECT/FETCH operations
604
- function createMockServerWithFetch() {
605
- const server = net.createServer(socket => {
606
- socket.write('* OK Mock IMAP Server ready\r\n');
607
-
608
- socket.on('data', data => {
609
- const lines = data
610
- .toString()
611
- .split('\r\n')
612
- .filter(l => l.trim());
613
-
614
- for (const line of lines) {
615
- const parts = line.split(' ');
616
- const tag = parts[0];
617
- const command = parts[1] ? parts[1].toUpperCase() : '';
618
-
619
- if (command === 'CAPABILITY') {
620
- socket.write('* CAPABILITY IMAP4rev1 AUTH=PLAIN\r\n');
621
- socket.write(`${tag} OK CAPABILITY completed\r\n`);
622
- } else if (command === 'LOGIN') {
623
- socket.write(`${tag} OK LOGIN completed\r\n`);
624
- } else if (command === 'LOGOUT') {
625
- socket.write('* BYE Server logging out\r\n');
626
- socket.write(`${tag} OK LOGOUT completed\r\n`);
627
- socket.end();
628
- } else if (command === 'NAMESPACE') {
629
- socket.write('* NAMESPACE (("" "/")) NIL NIL\r\n');
630
- socket.write(`${tag} OK NAMESPACE completed\r\n`);
631
- } else if (command === 'COMPRESS') {
632
- socket.write(`${tag} NO COMPRESS not supported\r\n`);
633
- } else if (command === 'ENABLE') {
634
- socket.write(`${tag} OK ENABLE completed\r\n`);
635
- } else if (command === 'ID') {
636
- socket.write('* ID NIL\r\n');
637
- socket.write(`${tag} OK ID completed\r\n`);
638
- } else if (command === 'SELECT' || command === 'EXAMINE') {
639
- socket.write('* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n');
640
- socket.write('* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\*)] Flags permitted\r\n');
641
- socket.write('* 10 EXISTS\r\n');
642
- socket.write('* 0 RECENT\r\n');
643
- socket.write('* OK [UIDVALIDITY 1234567890] UIDs valid\r\n');
644
- socket.write('* OK [UIDNEXT 11] Predicted next UID\r\n');
645
- socket.write(`${tag} OK [READ-WRITE] SELECT completed\r\n`);
646
- } else if (command === 'CLOSE') {
647
- socket.write(`${tag} OK CLOSE completed\r\n`);
648
- } else if (command === 'FETCH') {
649
- // Simple fetch response
650
- socket.write('* 1 FETCH (UID 1 FLAGS (\\Seen))\r\n');
651
- socket.write(`${tag} OK FETCH completed\r\n`);
652
- } else if (command === 'NOOP') {
653
- socket.write(`${tag} OK NOOP completed\r\n`);
654
- } else if (tag && command) {
655
- socket.write(`${tag} OK Command completed\r\n`);
656
- }
657
- }
658
- });
659
-
660
- socket.on('error', () => {});
661
- });
662
-
663
- return server;
664
- }
665
-
666
- // Note: helpers (getListenerReport, measureMemory, createMockServer) are available
667
- // within this module for testing purposes