imapflow 1.0.80 → 1.0.81
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 +4 -0
- package/lib/commands/expunge.js +11 -0
- package/lib/commands/fetch.js +11 -2
- package/lib/commands/select.js +29 -1
- package/lib/imap-flow.js +45 -9
- package/lib/types.d.ts +2 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
package/lib/commands/expunge.js
CHANGED
|
@@ -20,6 +20,17 @@ module.exports = async (connection, range, options) => {
|
|
|
20
20
|
let response;
|
|
21
21
|
try {
|
|
22
22
|
response = await connection.exec(command, attributes);
|
|
23
|
+
|
|
24
|
+
// A OK [HIGHESTMODSEQ 9122] Expunge completed (0.010 + 0.000 + 0.012 secs).
|
|
25
|
+
let section = response.response.attributes && response.response.attributes[0] && response.response.attributes[0].section;
|
|
26
|
+
let responseCode = section && section.length && section[0] && typeof section[0].value === 'string' ? section[0].value : '';
|
|
27
|
+
if (responseCode.toUpperCase() === 'HIGHESTMODSEQ') {
|
|
28
|
+
let highestModseq = section[1] && typeof section[1].value === 'string' && !isNaN(section[1].value) ? BigInt(section[1].value) : false;
|
|
29
|
+
if (highestModseq && (!connection.mailbox.highestModseq || highestModseq > connection.mailbox.highestModseq)) {
|
|
30
|
+
connection.mailbox.highestModseq = highestModseq;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
response.next();
|
|
24
35
|
return true;
|
|
25
36
|
} catch (err) {
|
package/lib/commands/fetch.js
CHANGED
|
@@ -138,7 +138,7 @@ module.exports = async (connection, range, query, options) => {
|
|
|
138
138
|
attributes.push(queryStructure);
|
|
139
139
|
|
|
140
140
|
if (options.changedSince && connection.enabled.has('CONDSTORE') && !mailbox.noModseq) {
|
|
141
|
-
|
|
141
|
+
let changedSinceArgs = [
|
|
142
142
|
{
|
|
143
143
|
type: 'ATOM',
|
|
144
144
|
value: 'CHANGEDSINCE'
|
|
@@ -147,7 +147,16 @@ module.exports = async (connection, range, query, options) => {
|
|
|
147
147
|
type: 'ATOM',
|
|
148
148
|
value: options.changedSince.toString()
|
|
149
149
|
}
|
|
150
|
-
]
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
if (options.uid && connection.enabled.has('QRESYNC')) {
|
|
153
|
+
changedSinceArgs.push({
|
|
154
|
+
type: 'ATOM',
|
|
155
|
+
value: 'VANISHED'
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
attributes.push(changedSinceArgs);
|
|
151
160
|
}
|
|
152
161
|
|
|
153
162
|
response = await connection.exec(options.uid ? 'UID FETCH' : 'FETCH', attributes, {
|
package/lib/commands/select.js
CHANGED
|
@@ -21,6 +21,7 @@ module.exports = async (connection, path, options) => {
|
|
|
21
21
|
connection.folders.set(folder.path, folder);
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
|
+
|
|
24
25
|
let folderListData = connection.folders.has(path) ? connection.folders.get(path) : false;
|
|
25
26
|
|
|
26
27
|
let response;
|
|
@@ -33,7 +34,19 @@ module.exports = async (connection, path, options) => {
|
|
|
33
34
|
}
|
|
34
35
|
});
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
+
|
|
38
|
+
let extraArgs = [];
|
|
39
|
+
if (connection.enabled.has('QRESYNC') && options.changedSince && options.uidValidity) {
|
|
40
|
+
extraArgs.push([
|
|
41
|
+
{ type: 'ATOM', value: 'QRESYNC' },
|
|
42
|
+
[
|
|
43
|
+
{ type: 'ATOM', value: options.uidValidity },
|
|
44
|
+
{ type: 'ATOM', value: options.changedSince }
|
|
45
|
+
]
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
response = await connection.exec(!options.readOnly ? 'SELECT' : 'EXAMINE', [{ type: 'ATOM', value: path }].concat(extraArgs || []), {
|
|
37
50
|
untagged: {
|
|
38
51
|
OK: async untagged => {
|
|
39
52
|
if (!untagged.attributes || !untagged.attributes.length) {
|
|
@@ -110,6 +123,21 @@ module.exports = async (connection, path, options) => {
|
|
|
110
123
|
}
|
|
111
124
|
|
|
112
125
|
map.exists = num;
|
|
126
|
+
},
|
|
127
|
+
VANISHED: async untagged => {
|
|
128
|
+
await connection.untaggedVanished(
|
|
129
|
+
untagged,
|
|
130
|
+
// mailbox is not yet open, so use a dummy mailbox object
|
|
131
|
+
{ path, uidNext: false, uidValidity: false }
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
// we should only get an untagged FETCH for a SELECT/EXAMINE if QRESYNC was asked for
|
|
135
|
+
FETCH: async untagged => {
|
|
136
|
+
await connection.untaggedFetch(
|
|
137
|
+
untagged,
|
|
138
|
+
// mailbox is not yet open, so use a dummy mailbox object
|
|
139
|
+
{ path, uidNext: false, uidValidity: false }
|
|
140
|
+
);
|
|
113
141
|
}
|
|
114
142
|
}
|
|
115
143
|
});
|
package/lib/imap-flow.js
CHANGED
|
@@ -29,7 +29,7 @@ const { PassThrough } = require('stream');
|
|
|
29
29
|
|
|
30
30
|
const { proxyConnection } = require('./proxy-connection');
|
|
31
31
|
|
|
32
|
-
const { comparePaths, updateCapabilities, getFolderTree, formatMessageResponse, getDecoder, packMessageRange, normalizePath } = require('./tools');
|
|
32
|
+
const { comparePaths, updateCapabilities, getFolderTree, formatMessageResponse, getDecoder, packMessageRange, normalizePath, expandRange } = require('./tools');
|
|
33
33
|
|
|
34
34
|
const imapCommands = require('./imap-commands.js');
|
|
35
35
|
|
|
@@ -128,9 +128,10 @@ class ImapFlow extends EventEmitter {
|
|
|
128
128
|
* @param {Boolean} [options.emitLogs=false] If `true` then in addition of sending data to logger, ImapFlow emits 'log' events with the same data
|
|
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
|
+
* @param {Boolean} [options.qresync=false] If true, then enables QRESYNC support. EXPUNGE notifications will include `uid` property instead of `seq`
|
|
131
132
|
*/
|
|
132
133
|
constructor(options) {
|
|
133
|
-
super();
|
|
134
|
+
super({ captureRejections: true });
|
|
134
135
|
|
|
135
136
|
this.options = options || {};
|
|
136
137
|
|
|
@@ -617,7 +618,7 @@ class ImapFlow extends EventEmitter {
|
|
|
617
618
|
}
|
|
618
619
|
|
|
619
620
|
// enable extensions if possible
|
|
620
|
-
await this.run('ENABLE', ['CONDSTORE', 'UTF8=ACCEPT']);
|
|
621
|
+
await this.run('ENABLE', ['CONDSTORE', 'UTF8=ACCEPT'].concat(this.options.qresync ? 'QRESYNC' : []));
|
|
621
622
|
|
|
622
623
|
this.usable = true;
|
|
623
624
|
}
|
|
@@ -943,21 +944,53 @@ class ImapFlow extends EventEmitter {
|
|
|
943
944
|
this.mailbox.exists--;
|
|
944
945
|
this.emit('expunge', {
|
|
945
946
|
path: this.mailbox.path,
|
|
946
|
-
seq
|
|
947
|
+
seq,
|
|
948
|
+
vanished: false
|
|
947
949
|
});
|
|
948
950
|
}
|
|
949
951
|
}
|
|
950
952
|
|
|
951
|
-
async
|
|
952
|
-
|
|
953
|
+
async untaggedVanished(untagged, mailbox) {
|
|
954
|
+
mailbox = mailbox || this.mailbox;
|
|
955
|
+
if (!mailbox) {
|
|
953
956
|
// mailbox closed, ignore
|
|
954
957
|
return;
|
|
955
958
|
}
|
|
956
959
|
|
|
957
|
-
let
|
|
960
|
+
let tags = [];
|
|
961
|
+
let uids = false;
|
|
962
|
+
|
|
963
|
+
if (untagged.attributes.length > 1 && Array.isArray(untagged.attributes[0])) {
|
|
964
|
+
tags = untagged.attributes[0].map(entry => (typeof entry.value === 'string' ? entry.value.toUpperCase() : false)).filter(value => value);
|
|
965
|
+
untagged.attributes.shift();
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if (untagged.attributes[0] && typeof untagged.attributes[0].value === 'string') {
|
|
969
|
+
uids = untagged.attributes[0].value;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
let uidList = expandRange(uids);
|
|
973
|
+
for (let uid of uidList) {
|
|
974
|
+
this.emit('expunge', {
|
|
975
|
+
path: mailbox.path,
|
|
976
|
+
uid,
|
|
977
|
+
vanished: true,
|
|
978
|
+
earlier: tags.includes('EARLIER')
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
async untaggedFetch(untagged, mailbox) {
|
|
984
|
+
mailbox = mailbox || this.mailbox;
|
|
985
|
+
if (!mailbox) {
|
|
986
|
+
// mailbox closed, ignore
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
let message = await formatMessageResponse(untagged, mailbox);
|
|
958
991
|
if (message.flags) {
|
|
959
992
|
let updateEvent = {
|
|
960
|
-
path:
|
|
993
|
+
path: mailbox.path,
|
|
961
994
|
seq: message.seq
|
|
962
995
|
};
|
|
963
996
|
|
|
@@ -1060,10 +1093,13 @@ class ImapFlow extends EventEmitter {
|
|
|
1060
1093
|
this.untaggedHandlers.CAPABILITY = (...args) => this.untaggedCapability(...args);
|
|
1061
1094
|
this.sectionHandlers.CAPABILITY = (...args) => this.sectionCapability(...args);
|
|
1062
1095
|
|
|
1063
|
-
this.untaggedHandlers.FETCH = (...args) => this.untaggedFetch(...args);
|
|
1064
1096
|
this.untaggedHandlers.EXISTS = (...args) => this.untaggedExists(...args);
|
|
1065
1097
|
this.untaggedHandlers.EXPUNGE = (...args) => this.untaggedExpunge(...args);
|
|
1066
1098
|
|
|
1099
|
+
// these methods take an optional second argument, so make sure that some random IMAP tag is not used as the second argument
|
|
1100
|
+
this.untaggedHandlers.FETCH = untagged => this.untaggedFetch(untagged);
|
|
1101
|
+
this.untaggedHandlers.VANISHED = untagged => this.untaggedVanished(untagged);
|
|
1102
|
+
|
|
1067
1103
|
let socket = false;
|
|
1068
1104
|
if (this.options.proxy) {
|
|
1069
1105
|
try {
|
package/lib/types.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ declare module "imapflow" {
|
|
|
27
27
|
* @param [options.emitLogs = false] - If `true` then in addition of sending data to logger, ImapFlow emits 'log' events with the same data
|
|
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
|
+
* @param [options.qresync = false] - If true, then enables QRESYNC support. EXPUNGE notifications will include `uid` property instead of `seq`
|
|
30
31
|
*/
|
|
31
32
|
class ImapFlow extends EventEmitter {
|
|
32
33
|
constructor(options: {
|
|
@@ -52,6 +53,7 @@ declare module "imapflow" {
|
|
|
52
53
|
emitLogs?: boolean;
|
|
53
54
|
verifyOnly?: boolean;
|
|
54
55
|
proxy?: boolean;
|
|
56
|
+
qresync?: boolean;
|
|
55
57
|
});
|
|
56
58
|
/**
|
|
57
59
|
* 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.81",
|
|
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.16.5",
|
|
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.18",
|
|
34
34
|
"braintree-jsdoc-template": "3.3.0",
|
|
35
|
-
"eslint": "8.
|
|
35
|
+
"eslint": "8.9.0",
|
|
36
36
|
"eslint-config-nodemailer": "1.2.0",
|
|
37
37
|
"eslint-config-prettier": "8.3.0",
|
|
38
38
|
"grunt": "1.4.1",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"libqp": "1.1.0",
|
|
51
51
|
"mailsplit": "5.3.1",
|
|
52
52
|
"nodemailer": "6.7.2",
|
|
53
|
-
"pino": "7.
|
|
53
|
+
"pino": "7.8.0",
|
|
54
54
|
"socks": "2.6.2"
|
|
55
55
|
}
|
|
56
56
|
}
|