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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # CHANGELOG
2
2
 
3
+ - 1.0.81 - 2022-02-15
4
+
5
+ - Added support for the QRESYNC extension and untagged VANISHED responses
6
+
3
7
  - 1.0.80 - 2022-02-07
4
8
 
5
9
  - Added support for the missing NOMODSEQ mailbox modifier
@@ -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) {
@@ -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
- attributes.push([
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, {
@@ -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
- response = await connection.exec(!options.readOnly ? 'SELECT' : 'EXAMINE', [{ type: 'ATOM', value: path }], {
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 untaggedFetch(untagged) {
952
- if (!this.mailbox) {
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 message = await formatMessageResponse(untagged, this.mailbox);
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: this.mailbox.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.80",
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.15",
33
+ "@types/node": "17.0.18",
34
34
  "braintree-jsdoc-template": "3.3.0",
35
- "eslint": "8.8.0",
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.6.5",
53
+ "pino": "7.8.0",
54
54
  "socks": "2.6.2"
55
55
  }
56
56
  }