imapflow 1.2.3 → 1.2.4

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.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "1.2.3"
2
+ ".": "1.2.4"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.4](https://github.com/postalsys/imapflow/compare/v1.2.3...v1.2.4) (2025-12-28)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * Improve resource cleanup on connection close ([7c3244c](https://github.com/postalsys/imapflow/commit/7c3244cd2d6c7c3d7be54ba26ad2ce5d8b9ddae9))
9
+
3
10
  ## [1.2.3](https://github.com/postalsys/imapflow/compare/v1.2.2...v1.2.3) (2025-12-22)
4
11
 
5
12
 
package/lib/imap-flow.js CHANGED
@@ -1788,6 +1788,29 @@ class ImapFlow extends EventEmitter {
1788
1788
  }
1789
1789
  }
1790
1790
 
1791
+ // cleanup streamer
1792
+ if (this.streamer) {
1793
+ try {
1794
+ // remove our listeners
1795
+ if (this.socketReadable) {
1796
+ this.streamer.removeListener('readable', this.socketReadable);
1797
+ }
1798
+ this.streamer.removeAllListeners('error');
1799
+ if (!this.streamer.destroyed) {
1800
+ this.streamer.destroy();
1801
+ }
1802
+ } catch (err) {
1803
+ this.log.error({ err, info: 'Failed to cleanup streamer', cid: this.id });
1804
+ }
1805
+ }
1806
+
1807
+ // clear socket handlers
1808
+ this.clearSocketHandlers();
1809
+
1810
+ // clear cached data
1811
+ this.folders.clear();
1812
+ this.requestTagMap.clear();
1813
+
1791
1814
  this.state = this.states.LOGOUT;
1792
1815
  if (this.isClosed) {
1793
1816
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "lib/imap-flow.js",
6
6
  "types": "lib/imap-flow.d.ts",
@@ -35,9 +35,9 @@
35
35
  "@babel/plugin-syntax-class-properties": "7.12.13",
36
36
  "@babel/preset-env": "7.28.5",
37
37
  "@eslint/eslintrc": "3.3.3",
38
- "@eslint/js": "9.39.1",
39
- "@types/node": "25.0.0",
40
- "eslint": "9.39.1",
38
+ "@eslint/js": "9.39.2",
39
+ "@types/node": "25.0.3",
40
+ "eslint": "9.39.2",
41
41
  "eslint-config-nodemailer": "1.2.0",
42
42
  "eslint-config-prettier": "10.1.8",
43
43
  "grunt": "1.6.1",
@@ -52,12 +52,12 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "encoding-japanese": "2.2.0",
55
- "iconv-lite": "0.7.0",
55
+ "iconv-lite": "0.7.1",
56
56
  "libbase64": "1.3.0",
57
57
  "libmime": "5.3.7",
58
58
  "libqp": "2.1.1",
59
59
  "@zone-eu/mailsplit": "5.4.8",
60
- "nodemailer": "7.0.11",
60
+ "nodemailer": "7.0.12",
61
61
  "pino": "10.1.0",
62
62
  "socks": "2.8.7"
63
63
  }
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Tests for memory cleanup on connection close
5
+ */
6
+
7
+ const { ImapFlow } = require('../lib/imap-flow');
8
+
9
+ exports['Memory Cleanup Tests'] = {
10
+ 'should clean up streamer on close without connection'(test) {
11
+ const client = new ImapFlow({
12
+ host: '127.0.0.1',
13
+ port: 1,
14
+ secure: false,
15
+ logger: false
16
+ });
17
+
18
+ // Check initial state
19
+ test.ok(client.streamer, 'streamer should exist');
20
+ test.ok(!client.streamer.destroyed, 'streamer should not be destroyed initially');
21
+
22
+ // Close without connecting
23
+ client.close();
24
+
25
+ // Verify cleanup
26
+ test.ok(client.streamer.destroyed, 'streamer should be destroyed after close');
27
+ test.equal(client.streamer.listenerCount('error'), 0, 'error listeners should be removed');
28
+ test.equal(client.folders.size, 0, 'folders should be cleared');
29
+ test.equal(client.requestTagMap.size, 0, 'requestTagMap should be cleared');
30
+ test.ok(client.isClosed, 'client should be marked as closed');
31
+
32
+ test.done();
33
+ },
34
+
35
+ 'should remove event listeners on close'(test) {
36
+ const client = new ImapFlow({
37
+ host: '127.0.0.1',
38
+ port: 1,
39
+ secure: false,
40
+ logger: false
41
+ });
42
+
43
+ // Add a readable listener as if connect was called
44
+ client.socketReadable = () => {};
45
+ client.streamer.on('readable', client.socketReadable);
46
+
47
+ // Verify listener was added
48
+ test.equal(client.streamer.listenerCount('readable'), 1, 'readable listener should be present');
49
+
50
+ client.close();
51
+
52
+ // Check listeners after close
53
+ test.equal(client.streamer.listenerCount('readable'), 0, 'readable listener should be removed');
54
+ test.equal(client.streamer.listenerCount('error'), 0, 'error listeners should be removed');
55
+
56
+ test.done();
57
+ },
58
+
59
+ 'should clear internal structures on close'(test) {
60
+ const client = new ImapFlow({
61
+ host: '127.0.0.1',
62
+ port: 1,
63
+ secure: false,
64
+ logger: false
65
+ });
66
+
67
+ // Add some data to internal structures
68
+ client.folders.set('INBOX', { path: 'INBOX' });
69
+ client.folders.set('Sent', { path: 'Sent' });
70
+ client.requestTagMap.set('A001', { tag: 'A001' });
71
+ client.requestTagMap.set('A002', { tag: 'A002' });
72
+
73
+ test.equal(client.folders.size, 2, 'folders should have entries');
74
+ test.equal(client.requestTagMap.size, 2, 'requestTagMap should have entries');
75
+
76
+ client.close();
77
+
78
+ test.equal(client.folders.size, 0, 'folders should be cleared after close');
79
+ test.equal(client.requestTagMap.size, 0, 'requestTagMap should be cleared after close');
80
+
81
+ test.done();
82
+ },
83
+
84
+ 'should handle multiple close calls gracefully'(test) {
85
+ const client = new ImapFlow({
86
+ host: '127.0.0.1',
87
+ port: 1,
88
+ secure: false,
89
+ logger: false
90
+ });
91
+
92
+ // Call close multiple times
93
+ test.doesNotThrow(() => {
94
+ client.close();
95
+ client.close();
96
+ client.close();
97
+ }, 'multiple close calls should not throw');
98
+
99
+ test.ok(client.isClosed, 'client should be marked as closed');
100
+
101
+ test.done();
102
+ },
103
+
104
+ 'should properly set state on close'(test) {
105
+ const client = new ImapFlow({
106
+ host: '127.0.0.1',
107
+ port: 1,
108
+ secure: false,
109
+ logger: false
110
+ });
111
+
112
+ test.equal(client.state, client.states.NOT_AUTHENTICATED, 'initial state should be NOT_AUTHENTICATED');
113
+ test.equal(client.usable, false, 'usable should be false initially');
114
+ test.equal(client.isClosed, false, 'isClosed should be false initially');
115
+
116
+ client.close();
117
+
118
+ test.equal(client.state, client.states.LOGOUT, 'state should be LOGOUT after close');
119
+ test.equal(client.usable, false, 'usable should be false after close');
120
+ test.equal(client.isClosed, true, 'isClosed should be true after close');
121
+
122
+ test.done();
123
+ },
124
+
125
+ 'should emit close event'(test) {
126
+ const client = new ImapFlow({
127
+ host: '127.0.0.1',
128
+ port: 1,
129
+ secure: false,
130
+ logger: false
131
+ });
132
+
133
+ let closeEmitted = false;
134
+ client.on('close', () => {
135
+ closeEmitted = true;
136
+ });
137
+
138
+ client.close();
139
+
140
+ test.ok(closeEmitted, 'close event should be emitted');
141
+
142
+ test.done();
143
+ }
144
+ };