imapflow 1.0.192 → 1.0.193

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.193](https://github.com/postalsys/imapflow/compare/v1.0.192...v1.0.193) (2025-07-31)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * Improved exception handling ([0eb77ac](https://github.com/postalsys/imapflow/commit/0eb77ac28a0f36b085769b6f9a20fd89d9214060))
9
+
3
10
  ## [1.0.192](https://github.com/postalsys/imapflow/compare/v1.0.191...v1.0.192) (2025-07-31)
4
11
 
5
12
 
package/lib/imap-flow.js CHANGED
@@ -4,10 +4,6 @@
4
4
  * @module imapflow
5
5
  */
6
6
 
7
- // TODO:
8
- // * Use buffers for compiled commands
9
- // * OAuth2 authentication
10
-
11
7
  const tls = require('tls');
12
8
  const net = require('net');
13
9
  const crypto = require('crypto');
@@ -377,7 +373,6 @@ class ImapFlow extends EventEmitter {
377
373
  }
378
374
 
379
375
  this.log.error({ err, cid: this.id });
380
- setImmediate(() => this.close());
381
376
  this.emitError(err);
382
377
  });
383
378
 
@@ -386,6 +381,12 @@ class ImapFlow extends EventEmitter {
386
381
  }
387
382
 
388
383
  emitError(err) {
384
+ if (!err) {
385
+ return;
386
+ }
387
+ err._connId = err._connId || this.id;
388
+
389
+ setImmediate(() => this.close());
389
390
  this.emit('error', err);
390
391
  }
391
392
 
@@ -761,7 +762,6 @@ class ImapFlow extends EventEmitter {
761
762
  this._socketError ||
762
763
  (err => {
763
764
  this.log.error({ err, cid: this.id });
764
- setImmediate(() => this.close());
765
765
  this.emitError(err);
766
766
  });
767
767
  this._socketClose =
@@ -790,6 +790,10 @@ class ImapFlow extends EventEmitter {
790
790
  err.code = 'ETIMEOUT';
791
791
 
792
792
  if (this.idling) {
793
+ if (!this.usable || !this.socket || this.socket.destroyed) {
794
+ this.emitError(err);
795
+ return;
796
+ }
793
797
  // Attempt to recover IDLE connections
794
798
  this.run('NOOP')
795
799
  .then(() => this.idle())
@@ -797,8 +801,7 @@ class ImapFlow extends EventEmitter {
797
801
  } else {
798
802
  // Close immediately for non-IDLE operations
799
803
  this.log.debug({ msg: 'Socket timeout', cid: this.id });
800
- this.emit('error', err);
801
- this.close();
804
+ this.emitError(err);
802
805
  }
803
806
  });
804
807
 
@@ -1175,10 +1178,7 @@ class ImapFlow extends EventEmitter {
1175
1178
  }
1176
1179
 
1177
1180
  // ALWAYS emit the error so users can handle it
1178
- setImmediate(() => {
1179
- this.emit('error', err);
1180
- this.close();
1181
- });
1181
+ this.emitError(err);
1182
1182
  });
1183
1183
  }
1184
1184
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.0.192",
3
+ "version": "1.0.193",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "lib/imap-flow.js",
6
6
  "types": "lib/imap-flow.d.ts",
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ const { ImapFlow } = require('../lib/imap-flow');
4
+ const { AuthenticationFailure } = require('../lib/tools');
5
+
6
+ module.exports['Authentication: Password auth configuration'] = test => {
7
+ let client = new ImapFlow({
8
+ host: 'imap.example.com',
9
+ auth: {
10
+ user: 'testuser',
11
+ pass: 'testpass'
12
+ }
13
+ });
14
+
15
+ test.equal(client.options.auth.user, 'testuser');
16
+ test.equal(client.options.auth.pass, 'testpass');
17
+ test.done();
18
+ };
19
+
20
+ module.exports['Authentication: OAuth2 auth configuration'] = test => {
21
+ let client = new ImapFlow({
22
+ host: 'imap.example.com',
23
+ auth: {
24
+ user: 'testuser',
25
+ accessToken: 'oauth2_token_here'
26
+ }
27
+ });
28
+
29
+ test.equal(client.options.auth.user, 'testuser');
30
+ test.equal(client.options.auth.accessToken, 'oauth2_token_here');
31
+ test.done();
32
+ };
33
+
34
+ module.exports['Authentication: Login method specification'] = test => {
35
+ let client = new ImapFlow({
36
+ host: 'imap.example.com',
37
+ auth: {
38
+ user: 'testuser',
39
+ pass: 'testpass',
40
+ loginMethod: 'AUTH=PLAIN'
41
+ }
42
+ });
43
+
44
+ test.equal(client.options.auth.loginMethod, 'AUTH=PLAIN');
45
+ test.done();
46
+ };
47
+
48
+ module.exports['Authentication: AuthenticationFailure error structure'] = test => {
49
+ let error = new AuthenticationFailure('Invalid credentials');
50
+
51
+ test.ok(error instanceof Error);
52
+ test.equal(error.constructor.name, 'AuthenticationFailure');
53
+ test.equal(error.message, 'Invalid credentials');
54
+ test.done();
55
+ };
56
+
57
+ module.exports['Authentication: Verify-only mode'] = test => {
58
+ let client = new ImapFlow({
59
+ host: 'imap.example.com',
60
+ auth: {
61
+ user: 'testuser',
62
+ pass: 'testpass'
63
+ },
64
+ verifyOnly: true
65
+ });
66
+
67
+ test.equal(client.options.verifyOnly, true);
68
+ test.done();
69
+ };
70
+
71
+ module.exports['Authentication: Disable auto IDLE'] = test => {
72
+ let client = new ImapFlow({
73
+ host: 'imap.example.com',
74
+ auth: {
75
+ user: 'testuser',
76
+ pass: 'testpass'
77
+ },
78
+ disableAutoIdle: true
79
+ });
80
+
81
+ test.equal(client.options.disableAutoIdle, true);
82
+ test.done();
83
+ };
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ const { ImapFlow } = require('../lib/imap-flow');
4
+
5
+ module.exports['Commands: Client instantiation'] = test => {
6
+ let client = new ImapFlow({
7
+ host: 'imap.example.com',
8
+ auth: { user: 'test', pass: 'test' }
9
+ });
10
+
11
+ test.ok(client);
12
+ test.equal(typeof client.exec, 'function');
13
+ test.done();
14
+ };
15
+
16
+ module.exports['Commands: Method availability'] = test => {
17
+ let client = new ImapFlow({
18
+ host: 'imap.example.com',
19
+ auth: { user: 'test', pass: 'test' }
20
+ });
21
+
22
+ // Check that key IMAP methods exist
23
+ test.equal(typeof client.connect, 'function');
24
+ test.equal(typeof client.logout, 'function');
25
+ test.equal(typeof client.list, 'function');
26
+ test.equal(typeof client.mailboxOpen, 'function');
27
+ test.equal(typeof client.mailboxClose, 'function');
28
+ test.equal(typeof client.search, 'function');
29
+ test.equal(typeof client.fetch, 'function');
30
+ test.done();
31
+ };
32
+
33
+ module.exports['Commands: State management methods'] = test => {
34
+ let client = new ImapFlow({
35
+ host: 'imap.example.com',
36
+ auth: { user: 'test', pass: 'test' }
37
+ });
38
+
39
+ test.equal(typeof client.mailboxCreate, 'function');
40
+ test.equal(typeof client.mailboxDelete, 'function');
41
+ test.equal(typeof client.mailboxRename, 'function');
42
+ test.equal(typeof client.mailboxSubscribe, 'function');
43
+ test.equal(typeof client.mailboxUnsubscribe, 'function');
44
+ test.done();
45
+ };
46
+
47
+ module.exports['Commands: Message operation methods'] = test => {
48
+ let client = new ImapFlow({
49
+ host: 'imap.example.com',
50
+ auth: { user: 'test', pass: 'test' }
51
+ });
52
+
53
+ test.equal(typeof client.messageFlagsSet, 'function');
54
+ test.equal(typeof client.messageFlagsAdd, 'function');
55
+ test.equal(typeof client.messageFlagsRemove, 'function');
56
+ test.equal(typeof client.messageCopy, 'function');
57
+ test.equal(typeof client.messageMove, 'function');
58
+ test.equal(typeof client.messageDelete, 'function');
59
+ test.done();
60
+ };
61
+
62
+ module.exports['Commands: Utility methods'] = test => {
63
+ let client = new ImapFlow({
64
+ host: 'imap.example.com',
65
+ auth: { user: 'test', pass: 'test' }
66
+ });
67
+
68
+ test.equal(typeof client.noop, 'function');
69
+ test.equal(typeof client.getQuota, 'function');
70
+ test.equal(typeof client.stats, 'function');
71
+ test.equal(typeof client.getRandomId, 'function');
72
+ test.done();
73
+ };
@@ -0,0 +1,162 @@
1
+ 'use strict';
2
+
3
+ const { ImapFlow } = require('../lib/imap-flow');
4
+
5
+ module.exports['Connection: Basic connection options'] = test => {
6
+ let client = new ImapFlow({
7
+ host: 'imap.example.com',
8
+ port: 993,
9
+ auth: { user: 'test', pass: 'test' }
10
+ });
11
+
12
+ test.equal(client.host, 'imap.example.com');
13
+ test.equal(client.port, 993);
14
+ test.done();
15
+ };
16
+
17
+ module.exports['Connection: Default options'] = test => {
18
+ let client = new ImapFlow({
19
+ host: 'imap.example.com',
20
+ auth: { user: 'test', pass: 'test' }
21
+ });
22
+
23
+ test.equal(client.port, 110);
24
+ test.equal(client.secureConnection, false);
25
+ test.done();
26
+ };
27
+
28
+ module.exports['Connection: Secure connection defaults'] = test => {
29
+ let client = new ImapFlow({
30
+ host: 'imap.example.com',
31
+ port: 993,
32
+ auth: { user: 'test', pass: 'test' }
33
+ });
34
+
35
+ test.equal(client.secureConnection, true);
36
+ test.done();
37
+ };
38
+
39
+ module.exports['Connection: TLS options'] = test => {
40
+ let client = new ImapFlow({
41
+ host: 'imap.example.com',
42
+ auth: { user: 'test', pass: 'test' },
43
+ tls: {
44
+ rejectUnauthorized: false,
45
+ minVersion: 'TLSv1.3'
46
+ }
47
+ });
48
+
49
+ test.equal(client.options.tls.rejectUnauthorized, false);
50
+ test.equal(client.options.tls.minVersion, 'TLSv1.3');
51
+ test.done();
52
+ };
53
+
54
+ module.exports['Connection: Authentication options'] = test => {
55
+ let client = new ImapFlow({
56
+ host: 'imap.example.com',
57
+ auth: {
58
+ user: 'testuser',
59
+ pass: 'testpass',
60
+ accessToken: 'token123'
61
+ }
62
+ });
63
+
64
+ test.equal(client.options.auth.user, 'testuser');
65
+ test.equal(client.options.auth.pass, 'testpass');
66
+ test.equal(client.options.auth.accessToken, 'token123');
67
+ test.done();
68
+ };
69
+
70
+ module.exports['Connection: Proxy configuration'] = test => {
71
+ let client = new ImapFlow({
72
+ host: 'imap.example.com',
73
+ auth: { user: 'test', pass: 'test' },
74
+ proxy: 'socks5://proxy.example.com:1080'
75
+ });
76
+
77
+ test.equal(client.options.proxy, 'socks5://proxy.example.com:1080');
78
+ test.done();
79
+ };
80
+
81
+ module.exports['Connection: Client info'] = test => {
82
+ let clientInfo = {
83
+ name: 'Test Client',
84
+ version: '1.0.0',
85
+ vendor: 'Test Corp'
86
+ };
87
+
88
+ let client = new ImapFlow({
89
+ host: 'imap.example.com',
90
+ auth: { user: 'test', pass: 'test' },
91
+ clientInfo
92
+ });
93
+
94
+ test.equal(client.clientInfo.name, 'Test Client');
95
+ test.equal(client.clientInfo.version, '1.0.0');
96
+ test.equal(client.clientInfo.vendor, 'Test Corp');
97
+ test.done();
98
+ };
99
+
100
+ module.exports['Connection: Logger configuration'] = test => {
101
+ let client = new ImapFlow({
102
+ host: 'imap.example.com',
103
+ auth: { user: 'test', pass: 'test' },
104
+ logger: false
105
+ });
106
+
107
+ test.equal(client.options.logger, false);
108
+ test.done();
109
+ };
110
+
111
+ module.exports['Connection: Stats tracking'] = test => {
112
+ let client = new ImapFlow({
113
+ host: 'imap.example.com',
114
+ auth: { user: 'test', pass: 'test' }
115
+ });
116
+
117
+ let stats = client.stats();
118
+ test.ok(Object.prototype.hasOwnProperty.call(stats, 'sent'));
119
+ test.ok(Object.prototype.hasOwnProperty.call(stats, 'received'));
120
+ test.equal(typeof stats.sent, 'number');
121
+ test.equal(typeof stats.received, 'number');
122
+ test.done();
123
+ };
124
+
125
+ module.exports['Connection: Random ID generation'] = test => {
126
+ let client = new ImapFlow({
127
+ host: 'imap.example.com',
128
+ auth: { user: 'test', pass: 'test' }
129
+ });
130
+
131
+ let id1 = client.getRandomId();
132
+ let id2 = client.getRandomId();
133
+
134
+ test.ok(typeof id1 === 'string' && id1.length > 0);
135
+ test.ok(typeof id2 === 'string' && id2.length > 0);
136
+ test.notEqual(id1, id2, 'IDs should be unique');
137
+ test.done();
138
+ };
139
+
140
+ module.exports['Connection: State management'] = test => {
141
+ let client = new ImapFlow({
142
+ host: 'imap.example.com',
143
+ auth: { user: 'test', pass: 'test' }
144
+ });
145
+
146
+ test.equal(client.state, client.states.NOT_AUTHENTICATED);
147
+ test.equal(client.authenticated, false);
148
+ test.ok(client.capabilities instanceof Map);
149
+ test.done();
150
+ };
151
+
152
+ module.exports['Connection: Event emitter setup'] = test => {
153
+ let client = new ImapFlow({
154
+ host: 'imap.example.com',
155
+ auth: { user: 'test', pass: 'test' }
156
+ });
157
+
158
+ test.equal(typeof client.on, 'function');
159
+ test.equal(typeof client.emit, 'function');
160
+ test.equal(typeof client.removeListener, 'function');
161
+ test.done();
162
+ };
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ const { ImapFlow } = require('../lib/imap-flow');
4
+
5
+ module.exports['Integration: Basic client creation'] = test => {
6
+ let client = new ImapFlow({
7
+ host: 'imap.example.com',
8
+ auth: { user: 'test', pass: 'test' }
9
+ });
10
+
11
+ test.ok(client);
12
+ test.equal(client.state, client.states.NOT_AUTHENTICATED);
13
+ test.equal(client.authenticated, false);
14
+ test.done();
15
+ };
16
+
17
+ module.exports['Integration: Multiple client instances'] = test => {
18
+ let client1 = new ImapFlow({
19
+ host: 'imap1.example.com',
20
+ auth: { user: 'test1', pass: 'test1' }
21
+ });
22
+
23
+ let client2 = new ImapFlow({
24
+ host: 'imap2.example.com',
25
+ auth: { user: 'test2', pass: 'test2' }
26
+ });
27
+
28
+ test.ok(client1);
29
+ test.ok(client2);
30
+ test.notEqual(client1.id, client2.id);
31
+ test.done();
32
+ };
33
+
34
+ module.exports['Integration: Client configuration'] = test => {
35
+ let client = new ImapFlow({
36
+ host: 'imap.example.com',
37
+ port: 993,
38
+ secure: true,
39
+ auth: { user: 'test', pass: 'test' },
40
+ logger: false
41
+ });
42
+
43
+ test.equal(client.host, 'imap.example.com');
44
+ test.equal(client.port, 993);
45
+ test.equal(client.secureConnection, true);
46
+ test.done();
47
+ };
48
+
49
+ module.exports['Integration: Event emitter functionality'] = test => {
50
+ let client = new ImapFlow({
51
+ host: 'imap.example.com',
52
+ auth: { user: 'test', pass: 'test' }
53
+ });
54
+
55
+ let eventFired = false;
56
+ client.on('test-event', () => {
57
+ eventFired = true;
58
+ });
59
+
60
+ client.emit('test-event');
61
+
62
+ setTimeout(() => {
63
+ test.ok(eventFired);
64
+ test.done();
65
+ }, 10);
66
+ };
67
+
68
+ module.exports['Integration: Stats functionality'] = test => {
69
+ let client = new ImapFlow({
70
+ host: 'imap.example.com',
71
+ auth: { user: 'test', pass: 'test' }
72
+ });
73
+
74
+ let stats = client.stats();
75
+ test.ok(typeof stats === 'object');
76
+ test.ok(Object.prototype.hasOwnProperty.call(stats, 'sent'));
77
+ test.ok(Object.prototype.hasOwnProperty.call(stats, 'received'));
78
+
79
+ // Reset stats
80
+ let resetStats = client.stats(true);
81
+ test.ok(typeof resetStats === 'object');
82
+ test.done();
83
+ };
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ const { searchCompiler } = require('../lib/search-compiler');
4
+
5
+ // Mock connection for testing
6
+ let createMockConnection = () => ({
7
+ capabilities: new Map([
8
+ ['IMAP4rev1', true],
9
+ ['UTF8=ACCEPT', true]
10
+ ]),
11
+ enabled: new Set(['UTF8=ACCEPT'])
12
+ });
13
+
14
+ // Mock mailbox for testing
15
+ let createMockMailbox = () => ({
16
+ flags: new Set(['\\Seen', '\\Answered', '\\Flagged', '\\Deleted', '\\Draft']),
17
+ permanentFlags: new Set(['\\*'])
18
+ });
19
+
20
+ module.exports['Search Compiler: Basic functionality'] = test => {
21
+ let connection = createMockConnection();
22
+ let mailbox = createMockMailbox();
23
+
24
+ test.doesNotThrow(() => {
25
+ let compiled = searchCompiler(connection, { seen: false }, mailbox);
26
+ test.ok(Array.isArray(compiled));
27
+ });
28
+
29
+ test.done();
30
+ };
31
+
32
+ module.exports['Search Compiler: Text searches'] = test => {
33
+ let connection = createMockConnection();
34
+ let mailbox = createMockMailbox();
35
+
36
+ let compiled = searchCompiler(connection, {
37
+ from: 'user@example.com',
38
+ subject: 'Test'
39
+ }, mailbox);
40
+
41
+ test.ok(Array.isArray(compiled));
42
+ test.ok(compiled.length > 0);
43
+ test.done();
44
+ };
45
+
46
+ module.exports['Search Compiler: Flag searches'] = test => {
47
+ let connection = createMockConnection();
48
+ let mailbox = createMockMailbox();
49
+
50
+ let compiled = searchCompiler(connection, {
51
+ seen: true,
52
+ answered: false
53
+ }, mailbox);
54
+
55
+ test.ok(Array.isArray(compiled));
56
+ test.ok(compiled.length > 0);
57
+ test.done();
58
+ };
59
+
60
+ module.exports['Search Compiler: Date searches'] = test => {
61
+ let connection = createMockConnection();
62
+ let mailbox = createMockMailbox();
63
+
64
+ let compiled = searchCompiler(connection, {
65
+ since: new Date('2023-01-01')
66
+ }, mailbox);
67
+
68
+ test.ok(Array.isArray(compiled));
69
+ test.ok(compiled.length > 0);
70
+ test.done();
71
+ };
72
+
73
+ module.exports['Search Compiler: Empty query'] = test => {
74
+ let connection = createMockConnection();
75
+ let mailbox = createMockMailbox();
76
+
77
+ let compiled = searchCompiler(connection, {}, mailbox);
78
+
79
+ test.ok(Array.isArray(compiled));
80
+ test.done();
81
+ };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const specialUse = require('../lib/special-use');
4
+
5
+ module.exports['Special Use: flags array'] = test => {
6
+ test.ok(Array.isArray(specialUse.flags));
7
+ test.ok(specialUse.flags.includes('\\Sent'));
8
+ test.ok(specialUse.flags.includes('\\Drafts'));
9
+ test.ok(specialUse.flags.includes('\\Trash'));
10
+ test.ok(specialUse.flags.includes('\\Archive'));
11
+ test.done();
12
+ };
13
+
14
+ module.exports['Special Use: names object'] = test => {
15
+ test.ok(typeof specialUse.names === 'object');
16
+ test.ok(specialUse.names['\\Sent']);
17
+ test.ok(Array.isArray(specialUse.names['\\Sent']));
18
+ test.ok(specialUse.names['\\Sent'].includes('sent'));
19
+ test.done();
20
+ };
21
+
22
+ module.exports['Special Use: Sent folder names'] = test => {
23
+ let sentNames = specialUse.names['\\Sent'];
24
+ test.ok(sentNames.includes('sent'));
25
+ test.ok(sentNames.includes('sent items'));
26
+ test.ok(sentNames.includes('sent messages'));
27
+ test.done();
28
+ };
29
+
30
+ module.exports['Special Use: Drafts folder names'] = test => {
31
+ let draftsNames = specialUse.names['\\Drafts'];
32
+ test.ok(draftsNames.includes('drafts'));
33
+ test.done();
34
+ };
35
+
36
+ module.exports['Special Use: Trash folder names'] = test => {
37
+ let trashNames = specialUse.names['\\Trash'];
38
+ test.ok(trashNames.includes('trash'));
39
+ test.ok(trashNames.includes('deleted items'));
40
+ test.ok(trashNames.includes('deleted messages'));
41
+ test.done();
42
+ };
43
+
44
+ module.exports['Special Use: Junk folder names'] = test => {
45
+ let junkNames = specialUse.names['\\Junk'];
46
+ test.ok(junkNames.includes('spam'));
47
+ test.ok(junkNames.includes('junk'));
48
+ test.done();
49
+ };