imapflow 1.0.183 → 1.0.185

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,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.185](https://github.com/postalsys/imapflow/compare/v1.0.184...v1.0.185) (2025-04-14)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **search:** add a `not` option for search options ([#281](https://github.com/postalsys/imapflow/issues/281)) ([000686b](https://github.com/postalsys/imapflow/commit/000686b2ace1ef0e21cca64d8e056d1f7f7f8914))
9
+
10
+ ## [1.0.184](https://github.com/postalsys/imapflow/compare/v1.0.183...v1.0.184) (2025-03-11)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * Allow to call the connect method only once ([ca7ec96](https://github.com/postalsys/imapflow/commit/ca7ec969f19a93c5dc574bf3873cd7afc4d7af47))
16
+ * **Authentication:** Report errors; Fix LOGINDISABLED ([#275](https://github.com/postalsys/imapflow/issues/275)) ([a3a3fb9](https://github.com/postalsys/imapflow/commit/a3a3fb993dee66c02b34bba0e0b26fc349487b26))
17
+
3
18
  ## [1.0.183](https://github.com/postalsys/imapflow/compare/v1.0.182...v1.0.183) (2025-03-09)
4
19
 
5
20
 
@@ -11,7 +11,7 @@ module.exports = async (connection, path) => {
11
11
 
12
12
  path = normalizePath(connection, path);
13
13
 
14
- if (connection.states.SELECTED && connection.mailbox.path === path) {
14
+ if (connection.state === connection.states.SELECTED && connection.mailbox.path === path) {
15
15
  await connection.run('CLOSE');
16
16
  }
17
17
 
@@ -12,7 +12,7 @@ module.exports = async (connection, path, newPath) => {
12
12
  path = normalizePath(connection, path);
13
13
  newPath = normalizePath(connection, newPath);
14
14
 
15
- if (connection.states.SELECTED && connection.mailbox.path === path) {
15
+ if (connection.state === connection.states.SELECTED && connection.mailbox.path === path) {
16
16
  await connection.run('CLOSE');
17
17
  }
18
18
 
package/lib/imap-flow.js CHANGED
@@ -38,6 +38,7 @@ const {
38
38
  packMessageRange,
39
39
  normalizePath,
40
40
  expandRange,
41
+ AuthenticationFailure,
41
42
  getColorFlags
42
43
  } = require('./tools');
43
44
 
@@ -280,6 +281,8 @@ class ImapFlow extends EventEmitter {
280
281
  this.socket = false;
281
282
  this.writeSocket = false;
282
283
 
284
+ this.isClosed = false;
285
+
283
286
  this.states = states;
284
287
  this.state = this.states.NOT_AUTHENTICATED;
285
288
 
@@ -370,6 +373,9 @@ class ImapFlow extends EventEmitter {
370
373
  setImmediate(() => this.close());
371
374
  this.emitError(err);
372
375
  });
376
+
377
+ // Has the `connect` method already been called
378
+ this._connectCalled = false;
373
379
  }
374
380
 
375
381
  emitError(err) {
@@ -387,7 +393,7 @@ class ImapFlow extends EventEmitter {
387
393
  }
388
394
 
389
395
  write(chunk) {
390
- if (this.socket.destroyed) {
396
+ if (!this.socket || this.socket.destroyed) {
391
397
  // do not write after connection end or logout
392
398
  const error = new Error('Socket is already closed');
393
399
  error.code = 'NoConnection';
@@ -503,7 +509,7 @@ class ImapFlow extends EventEmitter {
503
509
  }
504
510
 
505
511
  async exec(command, attributes, options) {
506
- if (this.socket.destroyed) {
512
+ if (!this.socket || this.socket.destroyed) {
507
513
  let error = new Error('Connection closed');
508
514
  error.code = 'EConnectionClosed';
509
515
  throw error;
@@ -796,11 +802,7 @@ class ImapFlow extends EventEmitter {
796
802
 
797
803
  await this.upgradeToSTARTTLS();
798
804
 
799
- let authenticated = await this.authenticate();
800
- if (!authenticated) {
801
- // nothing to do here
802
- return await this.logout();
803
- }
805
+ await this.authenticate();
804
806
 
805
807
  if (!this.idRequested && this.capabilities.has('ID')) {
806
808
  // re-request ID after LOGIN
@@ -812,8 +814,7 @@ class ImapFlow extends EventEmitter {
812
814
  if (nsResponse && nsResponse.error && nsResponse.status === 'BAD' && /User is authenticated but not connected/i.test(nsResponse.text)) {
813
815
  // Not a NAMESPACE failure but authentication failure, so report as
814
816
  this.authenticated = false;
815
- let err = new Error('Authentication failed');
816
- err.authenticationFailed = true;
817
+ let err = new AuthenticationFailure('Authentication failed');
817
818
  err.response = nsResponse.text;
818
819
  throw err;
819
820
  }
@@ -874,7 +875,7 @@ class ImapFlow extends EventEmitter {
874
875
  };
875
876
 
876
877
  Object.defineProperty(this.writeSocket, 'destroyed', {
877
- get: () => this.socket.destroyed
878
+ get: () => !this.socket || this.socket.destroyed
878
879
  });
879
880
 
880
881
  // we need to force flush deflated data to socket so we can't
@@ -1043,14 +1044,17 @@ class ImapFlow extends EventEmitter {
1043
1044
  }
1044
1045
 
1045
1046
  async authenticate() {
1047
+ if (this.state === this.states.LOGOUT) {
1048
+ throw new AuthenticationFailure('Already logged out');
1049
+ }
1050
+
1046
1051
  if (this.state !== this.states.NOT_AUTHENTICATED) {
1047
1052
  // nothing to do here, usually happens with PREAUTH greeting
1048
- return this.state !== this.states.LOGOUT;
1053
+ return true;
1049
1054
  }
1050
1055
 
1051
- if (this.capabilities.has('LOGINDISABLED') || !this.options.auth) {
1052
- // can not log in
1053
- return false;
1056
+ if (!this.options.auth) {
1057
+ throw new AuthenticationFailure('Please configure the login');
1054
1058
  }
1055
1059
 
1056
1060
  this.expectCapabilityUpdate = true;
@@ -1067,8 +1071,13 @@ class ImapFlow extends EventEmitter {
1067
1071
  if ((this.capabilities.has('AUTH=LOGIN') || this.capabilities.has('AUTH=PLAIN')) && loginMethod !== 'LOGIN') {
1068
1072
  this.authenticated = await this.run('AUTHENTICATE', this.options.auth.user, { password: this.options.auth.pass, loginMethod });
1069
1073
  } else {
1074
+ if (this.capabilities.has('LOGINDISABLED')) {
1075
+ throw new AuthenticationFailure('Login is disabled');
1076
+ }
1070
1077
  this.authenticated = await this.run('LOGIN', this.options.auth.user, this.options.auth.pass);
1071
1078
  }
1079
+ } else {
1080
+ throw new AuthenticationFailure('No password configured');
1072
1081
  }
1073
1082
 
1074
1083
  if (this.authenticated) {
@@ -1082,7 +1091,7 @@ class ImapFlow extends EventEmitter {
1082
1091
  return true;
1083
1092
  }
1084
1093
 
1085
- return false;
1094
+ throw new AuthenticationFailure('No matching authentication method');
1086
1095
  }
1087
1096
 
1088
1097
  async initialOK(message) {
@@ -1405,6 +1414,12 @@ class ImapFlow extends EventEmitter {
1405
1414
  * await client.connect();
1406
1415
  */
1407
1416
  async connect() {
1417
+ if (this._connectCalled) {
1418
+ // Prevent re-using ImapFlow instances by allowing to call connect just once.
1419
+ throw new Error('Can not re-use ImapFlow instance');
1420
+ }
1421
+ this._connectCalled = true;
1422
+
1408
1423
  let connector = this.secureConnection ? tls : net;
1409
1424
 
1410
1425
  let opts = Object.assign(
@@ -1977,6 +1992,7 @@ class ImapFlow extends EventEmitter {
1977
1992
  * @property {String} [keyword] Matches messages that have the custom flag set
1978
1993
  * @property {String} [unKeyword] Matches messages that do not have the custom flag set
1979
1994
  * @property {Object.<string, Boolean|String>} [header] Matches messages with header key set if value is `true` (**NB!** not supported by all servers) or messages where header partially matches a string value
1995
+ * @property {SearchObject} [not] A {@link SearchObject} object. It must not match.
1980
1996
  * @property {SearchObject[]} [or] An array of 2 or more {@link SearchObject} objects. At least on of these must match
1981
1997
  */
1982
1998
 
@@ -2467,7 +2483,7 @@ class ImapFlow extends EventEmitter {
2467
2483
 
2468
2484
  let res;
2469
2485
  while ((res = await getNext())) {
2470
- if (this.isClosed || this.socket.destroyed) {
2486
+ if (this.isClosed || !this.socket || this.socket.destroyed) {
2471
2487
  let error = new Error('Connection closed');
2472
2488
  error.code = 'EConnectionClosed';
2473
2489
  throw error;
@@ -2987,7 +3003,7 @@ class ImapFlow extends EventEmitter {
2987
3003
  return false;
2988
3004
  }
2989
3005
 
2990
- if (this.socket.destroyed) {
3006
+ if (!this.socket || this.socket.destroyed) {
2991
3007
  const error = new Error('Connection not available');
2992
3008
  error.code = 'NoConnection';
2993
3009
  throw error;
@@ -3056,7 +3072,7 @@ class ImapFlow extends EventEmitter {
3056
3072
  const lock = this.locks.shift();
3057
3073
  const { resolve, reject, path, options, lockId } = lock;
3058
3074
 
3059
- if (!this.usable || this.socket.destroyed) {
3075
+ if (!this.usable || !this.socket || this.socket.destroyed) {
3060
3076
  this.log.trace({ msg: 'Failed to acquire mailbox lock', path, lockId, idling: this.idling });
3061
3077
  // reject all
3062
3078
  let error = new Error('Connection not available');
@@ -190,6 +190,19 @@ module.exports.searchCompiler = (connection, query) => {
190
190
  }
191
191
  break;
192
192
 
193
+ case 'NOT':
194
+ {
195
+ if (!params[term]) {
196
+ break;
197
+ }
198
+
199
+ if (typeof params[term] === 'object') {
200
+ attributes.push({ type: 'ATOM', value: 'NOT' });
201
+ walk(params[term]);
202
+ }
203
+ }
204
+ break;
205
+
193
206
  case 'OR':
194
207
  {
195
208
  if (!params[term] || !Array.isArray(params[term]) || !params[term].length) {
package/lib/tools.js CHANGED
@@ -11,6 +11,10 @@ const iconv = require('iconv-lite');
11
11
 
12
12
  const FLAG_COLORS = ['red', 'orange', 'yellow', 'green', 'blue', 'purple', 'grey'];
13
13
 
14
+ class AuthenticationFailure extends Error {
15
+ authenticationFailed = true;
16
+ }
17
+
14
18
  const tools = {
15
19
  encodePath(connection, path) {
16
20
  path = (path || '').toString();
@@ -90,6 +94,8 @@ const tools = {
90
94
  return map;
91
95
  },
92
96
 
97
+ AuthenticationFailure,
98
+
93
99
  getStatusCode(response) {
94
100
  return response &&
95
101
  response.attributes &&
@@ -843,10 +849,9 @@ const tools = {
843
849
  },
844
850
 
845
851
  packMessageRange(list) {
846
- if (typeof uidList === 'string')
847
- if (!Array.isArray(list)) {
848
- list = [].concat(list || []);
849
- }
852
+ if (!Array.isArray(list)) {
853
+ list = [].concat(list || []);
854
+ }
850
855
 
851
856
  if (!list.length) {
852
857
  return '';
package/lib/types.d.ts CHANGED
@@ -904,6 +904,7 @@ declare type SequenceString = string;
904
904
  * @property [keyword] - Matches messages that have the custom flag set
905
905
  * @property [unKeyword] - Matches messages that do not have the custom flag set
906
906
  * @property [header] - Matches messages with header key set if value is `true` (**NB!** not supported by all servers) or messages where header partially matches a string value
907
+ * @property [not] - A {@link SearchObject} object. It must not match.
907
908
  * @property [or] - An array of 2 or more {@link SearchObject} objects. At least on of these must match
908
909
  */
909
910
  declare type SearchObject = {
@@ -940,6 +941,7 @@ declare type SearchObject = {
940
941
  header?: {
941
942
  [key: string]: Boolean | String;
942
943
  };
944
+ not?: SearchObject;
943
945
  or?: SearchObject[];
944
946
  };
945
947
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.0.183",
3
+ "version": "1.0.185",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "./lib/imap-flow.js",
6
6
  "scripts": {