imapflow 1.0.87 → 1.0.90

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,17 @@
1
1
  # CHANGELOG
2
2
 
3
+ - 1.0.90 - 2022-04-04
4
+
5
+ - Added new configuration option `maxIdleTime`
6
+
7
+ - 1.0.89 - 2022-04-04
8
+
9
+ - Added new event type `response` that emits all command responses
10
+
11
+ - 1.0.88 - 2022-03-24
12
+
13
+ - Fixed folder name match where not all non-ascii characters triggered UTF encoding
14
+
3
15
  - 1.0.87 - 2022-03-21
4
16
 
5
17
  - If LIST failed then do not suppress exception
@@ -2,112 +2,139 @@
2
2
 
3
3
  const NOOP_INTERVAL = 2 * 60 * 1000;
4
4
 
5
- // Listes for changes in mailbox
6
- module.exports = async connection => {
7
- if (connection.state !== connection.states.SELECTED) {
8
- // nothing to do here
9
- return;
10
- }
5
+ async function runIdle(connection) {
6
+ let response;
11
7
 
12
- if (connection.capabilities.has('IDLE')) {
13
- let response;
14
-
15
- let preCheckWaitQueue = [];
16
- try {
17
- connection.idling = true;
18
-
19
- //let idleSent = false;
20
- let doneRequested = false;
21
- let doneSent = false;
22
- let canEnd = false;
23
-
24
- let preCheck = () => {
25
- doneRequested = true;
26
- if (canEnd && !doneSent) {
27
- connection.log.debug({
28
- src: 'c',
29
- msg: `DONE`,
30
- comment: `breaking IDLE`,
31
- lockId: connection.currentLockId,
32
- path: connection.mailbox && connection.mailbox.path
33
- });
34
- connection.write('DONE');
35
- doneSent = true;
36
-
37
- connection.idling = false;
38
- connection.preCheck = false; // unset itself
39
-
40
- while (preCheckWaitQueue.length) {
41
- let { resolve } = preCheckWaitQueue.shift();
42
- resolve();
43
- }
44
- }
45
- };
46
-
47
- connection.preCheck = () => {
48
- let handler = new Promise((resolve, reject) => {
49
- preCheckWaitQueue.push({ resolve, reject });
50
- });
8
+ let preCheckWaitQueue = [];
9
+ try {
10
+ connection.idling = true;
51
11
 
52
- connection.log.trace({
53
- msg: 'Requesting IDLE break',
12
+ //let idleSent = false;
13
+ let doneRequested = false;
14
+ let doneSent = false;
15
+ let canEnd = false;
16
+
17
+ let preCheck = () => {
18
+ doneRequested = true;
19
+ if (canEnd && !doneSent) {
20
+ connection.log.debug({
21
+ src: 'c',
22
+ msg: `DONE`,
23
+ comment: `breaking IDLE`,
54
24
  lockId: connection.currentLockId,
55
- path: connection.mailbox && connection.mailbox.path,
56
- queued: preCheckWaitQueue.length,
57
- doneRequested,
58
- canEnd,
59
- doneSent
25
+ path: connection.mailbox && connection.mailbox.path
60
26
  });
27
+ connection.write('DONE');
28
+ doneSent = true;
61
29
 
62
- preCheck();
63
-
64
- return handler;
65
- };
30
+ connection.idling = false;
31
+ connection.preCheck = false; // unset itself
66
32
 
67
- response = await connection.exec('IDLE', false, {
68
- onPlusTag: async () => {
69
- connection.log.debug({ msg: `Initiated IDLE, waiting for server input`, doneRequested });
70
- canEnd = true;
71
- if (doneRequested) {
72
- preCheck();
73
- }
74
- },
75
- onSend: () => {
76
- //idleSent = true;
77
- }
78
- });
79
-
80
- // unset before response.next()
81
- if (typeof connection.preCheck === 'function') {
82
- connection.log.trace({
83
- msg: 'Clearing pre-check function',
84
- lockId: connection.currentLockId,
85
- path: connection.mailbox && connection.mailbox.path,
86
- queued: preCheckWaitQueue.length,
87
- doneRequested,
88
- canEnd,
89
- doneSent
90
- });
91
- connection.preCheck = false;
92
33
  while (preCheckWaitQueue.length) {
93
34
  let { resolve } = preCheckWaitQueue.shift();
94
35
  resolve();
95
36
  }
96
37
  }
38
+ };
97
39
 
98
- response.next();
99
- return;
100
- } catch (err) {
101
- connection.preCheck = false;
102
- connection.idling = false;
40
+ connection.preCheck = () => {
41
+ let handler = new Promise((resolve, reject) => {
42
+ preCheckWaitQueue.push({ resolve, reject });
43
+ });
44
+
45
+ connection.log.trace({
46
+ msg: 'Requesting IDLE break',
47
+ lockId: connection.currentLockId,
48
+ path: connection.mailbox && connection.mailbox.path,
49
+ queued: preCheckWaitQueue.length,
50
+ doneRequested,
51
+ canEnd,
52
+ doneSent
53
+ });
103
54
 
104
- connection.log.warn({ err, cid: connection.id });
55
+ preCheck();
56
+
57
+ return handler;
58
+ };
59
+
60
+ response = await connection.exec('IDLE', false, {
61
+ onPlusTag: async () => {
62
+ connection.log.debug({ msg: `Initiated IDLE, waiting for server input`, doneRequested });
63
+ canEnd = true;
64
+ if (doneRequested) {
65
+ preCheck();
66
+ }
67
+ },
68
+ onSend: () => {
69
+ //idleSent = true;
70
+ }
71
+ });
72
+
73
+ // unset before response.next()
74
+ if (typeof connection.preCheck === 'function') {
75
+ connection.log.trace({
76
+ msg: 'Clearing pre-check function',
77
+ lockId: connection.currentLockId,
78
+ path: connection.mailbox && connection.mailbox.path,
79
+ queued: preCheckWaitQueue.length,
80
+ doneRequested,
81
+ canEnd,
82
+ doneSent
83
+ });
84
+ connection.preCheck = false;
105
85
  while (preCheckWaitQueue.length) {
106
- let { reject } = preCheckWaitQueue.shift();
107
- reject(err);
86
+ let { resolve } = preCheckWaitQueue.shift();
87
+ resolve();
108
88
  }
109
- return false;
110
89
  }
90
+
91
+ response.next();
92
+ return;
93
+ } catch (err) {
94
+ connection.preCheck = false;
95
+ connection.idling = false;
96
+
97
+ connection.log.warn({ err, cid: connection.id });
98
+ while (preCheckWaitQueue.length) {
99
+ let { reject } = preCheckWaitQueue.shift();
100
+ reject(err);
101
+ }
102
+ return false;
103
+ }
104
+ }
105
+
106
+ // Listes for changes in mailbox
107
+ module.exports = async (connection, maxIdleTime) => {
108
+ if (connection.state !== connection.states.SELECTED) {
109
+ // nothing to do here
110
+ return;
111
+ }
112
+
113
+ if (connection.capabilities.has('IDLE')) {
114
+ let idleTimer;
115
+ let stillIdling = false;
116
+ let runIdleLoop = async () => {
117
+ if (maxIdleTime) {
118
+ idleTimer = setTimeout(() => {
119
+ if (connection.idling) {
120
+ if (typeof connection.preCheck === 'function') {
121
+ stillIdling = true;
122
+ // request IDLE break if IDLE has been running for allowed time
123
+ connection.log.trace({ msg: 'Max allowed IDLE time reached', cid: connection.id });
124
+ connection.preCheck().catch(err => connection.log.warn({ err, cid: connection.id }));
125
+ }
126
+ }
127
+ }, maxIdleTime);
128
+ }
129
+ let resp = await runIdle(connection);
130
+ clearTimeout(idleTimer);
131
+ if (stillIdling) {
132
+ stillIdling = false;
133
+ return runIdleLoop();
134
+ }
135
+ return resp;
136
+ };
137
+ return runIdleLoop();
111
138
  }
112
139
 
113
140
  let idleTimer;
@@ -127,11 +154,13 @@ module.exports = async connection => {
127
154
  response.next();
128
155
  };
129
156
 
157
+ let noopInterval = maxIdleTime ? Math.min(NOOP_INTERVAL, maxIdleTime) : NOOP_INTERVAL;
158
+
130
159
  let runLoop = () => {
131
160
  idleCheck()
132
161
  .then(() => {
133
162
  clearTimeout(idleTimer);
134
- idleTimer = setTimeout(runLoop, NOOP_INTERVAL);
163
+ idleTimer = setTimeout(runLoop, noopInterval);
135
164
  })
136
165
  .catch(err => {
137
166
  clearTimeout(idleTimer);
package/lib/imap-flow.js CHANGED
@@ -129,6 +129,7 @@ class ImapFlow extends EventEmitter {
129
129
  * @param {Boolean} [options.verifyOnly=false] If `true` then logs out automatically after successful authentication
130
130
  * @param {Boolean} [options.proxy] Optional proxy URL. Supports HTTP CONNECT (`http://`, `https://`) and SOCKS (`socks://`, `socks4://`, `socks5://`) proxies
131
131
  * @param {Boolean} [options.qresync=false] If true, then enables QRESYNC support. EXPUNGE notifications will include `uid` property instead of `seq`
132
+ * @param {Number} [options.maxIdleTime] If set, then breaks and restarts IDLE every maxIdleTime ms
132
133
  */
133
134
  constructor(options) {
134
135
  super({ captureRejections: true });
@@ -260,6 +261,8 @@ class ImapFlow extends EventEmitter {
260
261
  this.currentLock = false;
261
262
  this.locks = [];
262
263
 
264
+ this.maxIdleTime = options.maxIdleTime || false;
265
+
263
266
  this.streamer.on('error', err => {
264
267
  if (['Z_BUF_ERROR', 'ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EHOSTUNREACH'].includes(err.code)) {
265
268
  // just close the connection, usually nothing but noise
@@ -411,6 +414,20 @@ class ImapFlow extends EventEmitter {
411
414
 
412
415
  try {
413
416
  parsed = await parser(data.payload, { literals: data.literals });
417
+ if (parsed.tag && !['*', '+'].includes(parsed.tag) && parsed.command) {
418
+ let payload = { response: parsed.command };
419
+
420
+ if (
421
+ parsed.attributes &&
422
+ parsed.attributes[0] &&
423
+ parsed.attributes[0].section &&
424
+ parsed.attributes[0].section[0] &&
425
+ parsed.attributes[0].section[0].type === 'ATOM'
426
+ ) {
427
+ payload.code = parsed.attributes[0].section[0].value;
428
+ }
429
+ this.emit('response', payload);
430
+ }
414
431
  } catch (err) {
415
432
  // can not make sense of this
416
433
  this.log.error({ src: 's', msg: data.payload.toString(), err, cid: this.id });
@@ -1579,7 +1596,7 @@ class ImapFlow extends EventEmitter {
1579
1596
  */
1580
1597
  async idle() {
1581
1598
  if (!this.idling) {
1582
- return await this.run('IDLE');
1599
+ return await this.run('IDLE', this.maxIdleTime);
1583
1600
  }
1584
1601
  }
1585
1602
 
package/lib/tools.js CHANGED
@@ -1,3 +1,5 @@
1
+ /* eslint no-control-regex:0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  const libmime = require('libmime');
@@ -17,7 +19,7 @@ module.exports = {
17
19
  return 'INBOX';
18
20
  }
19
21
 
20
- if (!connection.enabled.has('UTF8=ACCEPT') && /[\x80-\xff]/.test(path)) {
22
+ if (!connection.enabled.has('UTF8=ACCEPT') && /[\x00-\x08\x0b-\x0c\x0e-\x1f\u0080-\uffff]/.test(path)) {
21
23
  try {
22
24
  path = iconv.encode(path, 'utf-7-imap').toString();
23
25
  } catch (err) {
package/lib/types.d.ts CHANGED
@@ -28,6 +28,7 @@ declare module "imapflow" {
28
28
  * @param [options.verifyOnly = false] - If `true` then logs out automatically after successful authentication
29
29
  * @param [options.proxy] - Optional proxy URL. Supports HTTP CONNECT (`http://`, `https://`) and SOCKS (`socks://`, `socks4://`, `socks5://`) proxies
30
30
  * @param [options.qresync = false] - If true, then enables QRESYNC support. EXPUNGE notifications will include `uid` property instead of `seq`
31
+ * @param [options.maxIdleTime] - If set, then breaks and restarts IDLE every maxIdleTime ms
31
32
  */
32
33
  class ImapFlow extends EventEmitter {
33
34
  constructor(options: {
@@ -54,6 +55,7 @@ declare module "imapflow" {
54
55
  verifyOnly?: boolean;
55
56
  proxy?: boolean;
56
57
  qresync?: boolean;
58
+ maxIdleTime?: number;
57
59
  });
58
60
  /**
59
61
  * Current module version as a static class property
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.0.87",
3
+ "version": "1.0.90",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "./lib/imap-flow.js",
6
6
  "scripts": {
@@ -30,9 +30,9 @@
30
30
  "@babel/eslint-plugin": "7.17.7",
31
31
  "@babel/plugin-syntax-class-properties": "7.12.13",
32
32
  "@babel/preset-env": "7.16.11",
33
- "@types/node": "17.0.22",
33
+ "@types/node": "17.0.23",
34
34
  "braintree-jsdoc-template": "3.3.0",
35
- "eslint": "8.11.0",
35
+ "eslint": "8.12.0",
36
36
  "eslint-config-nodemailer": "1.2.0",
37
37
  "eslint-config-prettier": "8.5.0",
38
38
  "grunt": "1.4.1",
@@ -43,14 +43,14 @@
43
43
  "tsd-jsdoc": "2.5.0"
44
44
  },
45
45
  "dependencies": {
46
- "encoding-japanese": "1.0.30",
46
+ "encoding-japanese": "2.0.0",
47
47
  "iconv-lite": "0.6.3",
48
48
  "libbase64": "1.2.1",
49
49
  "libmime": "5.0.0",
50
50
  "libqp": "1.1.0",
51
51
  "mailsplit": "5.3.1",
52
52
  "nodemailer": "6.7.3",
53
- "pino": "7.9.1",
53
+ "pino": "7.9.2",
54
54
  "socks": "2.6.2"
55
55
  }
56
56
  }