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 +12 -0
- package/lib/commands/idle.js +120 -91
- package/lib/imap-flow.js +18 -1
- package/lib/tools.js +3 -1
- package/lib/types.d.ts +2 -0
- package/package.json +5 -5
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
|
package/lib/commands/idle.js
CHANGED
|
@@ -2,112 +2,139 @@
|
|
|
2
2
|
|
|
3
3
|
const NOOP_INTERVAL = 2 * 60 * 1000;
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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 {
|
|
107
|
-
|
|
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,
|
|
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') && /[\
|
|
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.
|
|
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.
|
|
33
|
+
"@types/node": "17.0.23",
|
|
34
34
|
"braintree-jsdoc-template": "3.3.0",
|
|
35
|
-
"eslint": "8.
|
|
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": "
|
|
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.
|
|
53
|
+
"pino": "7.9.2",
|
|
54
54
|
"socks": "2.6.2"
|
|
55
55
|
}
|
|
56
56
|
}
|