imapflow 1.0.117 → 1.0.119

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.118 - 2022-12-22
4
+
5
+ - Refactored detecting special use folders
6
+
3
7
  - 1.0.117 - 2022-12-05
4
8
 
5
9
  - Updated command compiling. The command compiler returns a Buffer, not a "binary" string.
@@ -7,11 +7,13 @@ const { specialUse } = require('../special-use');
7
7
  module.exports = async (connection, reference, mailbox, options) => {
8
8
  options = options || {};
9
9
 
10
+ const FLAG_SORT_ORDER = ['\\Inbox', '\\Flagged', '\\Sent', '\\Drafts', '\\All', '\\Archive', '\\Junk', '\\Trash'];
11
+ const SOURCE_SORT_ORDER = ['user', 'extension', 'name'];
12
+
10
13
  let listCommand = connection.capabilities.has('XLIST') && !connection.capabilities.has('SPECIAL-USE') ? 'XLIST' : 'LIST';
11
14
 
12
15
  let response;
13
16
  try {
14
- let flagsSeen = new Set();
15
17
  let entries = [];
16
18
 
17
19
  let statusMap = new Map();
@@ -49,6 +51,23 @@ module.exports = async (connection, reference, mailbox, options) => {
49
51
  }
50
52
  }
51
53
 
54
+ let specialUseMatches = {};
55
+ let addSpecialUseMatch = (entry, type, source) => {
56
+ if (!specialUseMatches[type]) {
57
+ specialUseMatches[type] = [];
58
+ }
59
+ specialUseMatches[type].push({ entry, source });
60
+ };
61
+
62
+ let specialUseHints = {};
63
+ if (options.specialUseHints && typeof options.specialUseHints === 'object') {
64
+ for (let type of Object.keys(options.specialUseHints)) {
65
+ if (['sent', 'junk', 'trash', 'drafts'].includes(type) && options.specialUseHints[type] && typeof options.specialUseHints[type] === 'string') {
66
+ specialUseHints[normalizePath(connection, options.specialUseHints[type])] = `\\${type.replace(/^./, c => c.toUpperCase())}`;
67
+ }
68
+ }
69
+ }
70
+
52
71
  let runList = async (reference, mailbox) => {
53
72
  const cmdArgs = [encodePath(connection, reference), encodePath(connection, mailbox)];
54
73
 
@@ -71,17 +90,21 @@ module.exports = async (connection, reference, mailbox, options) => {
71
90
  listed: true
72
91
  };
73
92
 
93
+ if (specialUseHints[entry.path]) {
94
+ addSpecialUseMatch(entry, specialUseHints[entry.path], 'user');
95
+ }
96
+
74
97
  if (listCommand === 'XLIST' && entry.flags.has('\\Inbox')) {
75
98
  // XLIST specific flag, ignore
76
99
  entry.flags.delete('\\Inbox');
77
100
  if (entry.path !== 'INBOX') {
78
101
  // XLIST may use localised inbox name
79
- entry.specialUse = '\\Inbox';
102
+ addSpecialUseMatch(entry, '\\Inbox', 'extension');
80
103
  }
81
104
  }
82
105
 
83
106
  if (entry.path.toUpperCase() === 'INBOX') {
84
- entry.specialUse = '\\Inbox';
107
+ addSpecialUseMatch(entry, '\\Inbox', 'name');
85
108
  }
86
109
 
87
110
  if (entry.delimiter && entry.path.charAt(0) === entry.delimiter) {
@@ -92,9 +115,13 @@ module.exports = async (connection, reference, mailbox, options) => {
92
115
  entry.parent = entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path];
93
116
  entry.name = entry.parent.pop();
94
117
 
95
- let specialUseFlag = specialUse(connection.capabilities.has('XLIST') || connection.capabilities.has('SPECIAL-USE'), entry);
96
- if (specialUseFlag && !flagsSeen.has(specialUseFlag)) {
97
- entry.specialUse = specialUseFlag;
118
+ let { flag: specialUseFlag, source: flagSource } = specialUse(
119
+ connection.capabilities.has('XLIST') || connection.capabilities.has('SPECIAL-USE'),
120
+ entry
121
+ );
122
+
123
+ if (specialUseFlag) {
124
+ addSpecialUseMatch(entry, specialUseFlag, flagSource);
98
125
  }
99
126
 
100
127
  entries.push(entry);
@@ -172,7 +199,7 @@ module.exports = async (connection, reference, mailbox, options) => {
172
199
  return entries;
173
200
  }
174
201
 
175
- if (normalizedReference && !entries.find(entry => entry.specialUse === '\\Inbox')) {
202
+ if (normalizedReference && !specialUseMatches['\\Inbox']) {
176
203
  // INBOX was most probably not included in the listing if namespace was used
177
204
  await runList('', 'INBOX');
178
205
  }
@@ -213,7 +240,7 @@ module.exports = async (connection, reference, mailbox, options) => {
213
240
  };
214
241
 
215
242
  if (entry.path.toUpperCase() === 'INBOX') {
216
- entry.specialUse = '\\Inbox';
243
+ addSpecialUseMatch(entry, '\\Inbox', 'name');
217
244
  }
218
245
 
219
246
  if (entry.delimiter && entry.path.charAt(0) === entry.delimiter) {
@@ -244,26 +271,52 @@ module.exports = async (connection, reference, mailbox, options) => {
244
271
  );
245
272
  response.next();
246
273
 
274
+ for (let type of Object.keys(specialUseMatches)) {
275
+ let sortedEntries = specialUseMatches[type].sort((a, b) => {
276
+ let aSource = SOURCE_SORT_ORDER.indexOf(a.source);
277
+ let bSource = SOURCE_SORT_ORDER.indexOf(b.source);
278
+ if (aSource === bSource) {
279
+ return a.entry.path.localeCompare(b.entry.path);
280
+ }
281
+ return aSource - bSource;
282
+ });
283
+
284
+ if (!sortedEntries[0].entry.specialUse) {
285
+ sortedEntries[0].entry.specialUse = type;
286
+ sortedEntries[0].entry.specialUseSource = sortedEntries[0].source;
287
+ }
288
+ }
289
+
247
290
  let inboxEntry = entries.find(entry => entry.specialUse === '\\Inbox');
248
291
  if (inboxEntry && !inboxEntry.subscribed) {
249
292
  // override server settings and make INBOX always as subscribed
250
293
  inboxEntry.subscribed = true;
251
294
  }
252
295
 
253
- ['\\Drafts', '\\Junk', '\\Sent', '\\Trash'].forEach(specialUseFlag => {
254
- // try to ensure that we have most needed special use mailboxes detected
255
- if (entries.find(entry => entry.specialUse === specialUseFlag)) {
256
- return;
296
+ return entries.sort((a, b) => {
297
+ if (a.specialUse && !b.specialUse) {
298
+ return -1;
299
+ }
300
+ if (!a.specialUse && b.specialUse) {
301
+ return 1;
257
302
  }
303
+ if (a.specialUse && b.specialUse) {
304
+ return FLAG_SORT_ORDER.indexOf(a.specialUse) - FLAG_SORT_ORDER.indexOf(b.specialUse);
305
+ }
306
+
307
+ let aList = [].concat(a.parent).concat(a.name);
308
+ let bList = [].concat(b.parent).concat(b.name);
258
309
 
259
- // special use mailbox not found? try again
260
- let match = entries.find(entry => !entry.specialUse && specialUse(false, entry) === specialUseFlag);
261
- if (match) {
262
- match.specialUse = specialUseFlag;
310
+ for (let i = 0; i < aList.length; i++) {
311
+ let aPart = aList[i];
312
+ let bPart = bList[i];
313
+ if (aPart !== bPart) {
314
+ return aPart.localeCompare(bPart || '');
315
+ }
263
316
  }
264
- });
265
317
 
266
- return entries;
318
+ return a.path.localeCompare(b.path);
319
+ });
267
320
  } catch (err) {
268
321
  connection.log.warn({ msg: 'Failed to list folders', err, cid: connection.id });
269
322
  throw err;
package/lib/imap-flow.js CHANGED
@@ -1452,6 +1452,11 @@ class ImapFlow extends EventEmitter {
1452
1452
  * @param {Boolean} [options.statusQuery.uidValidity] if `true` request mailbox `UIDVALIDITY` value
1453
1453
  * @param {Boolean} [options.statusQuery.unseen] if `true` request count of unseen messages
1454
1454
  * @param {Boolean} [options.statusQuery.highestModseq] if `true` request last known modseq value
1455
+ * @param {Object} [options.specialUseHints] set specific paths as special use folders, this would override special use flags provided from the server
1456
+ * @param {String} [options.specialUseHints.sent] Path to "Sent Mail" folder
1457
+ * @param {String} [options.specialUseHints.trash] Path to "Trash" folder
1458
+ * @param {String} [options.specialUseHints.junk] Path to "Junk Mail" folder
1459
+ * @param {String} [options.specialUseHints.drafts] Path to "Drafts" folder
1455
1460
  * @returns {Promise<ListResponse[]>} An array of ListResponse objects
1456
1461
  *
1457
1462
  * @example
@@ -269,11 +269,23 @@ module.exports = {
269
269
  },
270
270
 
271
271
  specialUse(hasSpecialUseExtension, folder) {
272
+ let result;
273
+
272
274
  if (hasSpecialUseExtension) {
273
- return module.exports.flags.find(flag => folder.flags.has(flag));
275
+ result = {
276
+ flag: module.exports.flags.find(flag => folder.flags.has(flag)),
277
+ source: 'extension'
278
+ };
279
+ }
280
+
281
+ if (!result || !result.flag) {
282
+ let name = folder.name.toLowerCase().trim();
283
+ result = {
284
+ flag: Object.keys(module.exports.names).find(flag => module.exports.names[flag].includes(name)),
285
+ source: 'name'
286
+ };
274
287
  }
275
288
 
276
- let name = folder.name.toLowerCase().trim();
277
- return Object.keys(module.exports.names).find(flag => module.exports.names[flag].includes(name));
289
+ return result && result.flag ? result : { flag: null };
278
290
  }
279
291
  };
package/lib/types.d.ts CHANGED
@@ -167,6 +167,11 @@ declare module "imapflow" {
167
167
  * @param [options.statusQuery.uidValidity] - if `true` request mailbox `UIDVALIDITY` value
168
168
  * @param [options.statusQuery.unseen] - if `true` request count of unseen messages
169
169
  * @param [options.statusQuery.highestModseq] - if `true` request last known modseq value
170
+ * @param [options.specialUseHints] - set specific paths as special use folders, this would override special use flags provided from the server
171
+ * @param [options.specialUseHints.sent] - Path to "Sent Mail" folder
172
+ * @param [options.specialUseHints.trash] - Path to "Trash" folder
173
+ * @param [options.specialUseHints.junk] - Path to "Junk Mail" folder
174
+ * @param [options.specialUseHints.drafts] - Path to "Drafts" folder
170
175
  * @returns An array of ListResponse objects
171
176
  */
172
177
  list(options?: {
@@ -178,6 +183,12 @@ declare module "imapflow" {
178
183
  unseen?: boolean;
179
184
  highestModseq?: boolean;
180
185
  };
186
+ specialUseHints?: {
187
+ sent?: string;
188
+ trash?: string;
189
+ junk?: string;
190
+ drafts?: string;
191
+ };
181
192
  }): Promise<ListResponse[]>;
182
193
  /**
183
194
  * Lists available mailboxes as a tree structured object
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.0.117",
3
+ "version": "1.0.119",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "./lib/imap-flow.js",
6
6
  "scripts": {
@@ -31,9 +31,9 @@
31
31
  "@babel/eslint-plugin": "7.19.1",
32
32
  "@babel/plugin-syntax-class-properties": "7.12.13",
33
33
  "@babel/preset-env": "7.20.2",
34
- "@types/node": "18.11.10",
34
+ "@types/node": "18.11.17",
35
35
  "braintree-jsdoc-template": "3.3.0",
36
- "eslint": "8.29.0",
36
+ "eslint": "8.30.0",
37
37
  "eslint-config-nodemailer": "1.2.0",
38
38
  "eslint-config-prettier": "8.5.0",
39
39
  "grunt": "1.5.3",
@@ -48,11 +48,11 @@
48
48
  "encoding-japanese": "2.0.0",
49
49
  "iconv-lite": "0.6.3",
50
50
  "libbase64": "1.2.1",
51
- "libmime": "5.1.0",
51
+ "libmime": "5.2.0",
52
52
  "libqp": "2.0.1",
53
- "mailsplit": "5.3.2",
53
+ "mailsplit": "5.4.0",
54
54
  "nodemailer": "6.8.0",
55
- "pino": "8.7.0",
55
+ "pino": "8.8.0",
56
56
  "socks": "2.7.1"
57
57
  }
58
58
  }