imapkit 5.0.0 → 5.0.1

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.
Files changed (67) hide show
  1. package/README.md +33 -29
  2. package/bin/help.txt +3 -2
  3. package/bin/imapkit.js +15 -5
  4. package/dist/cjs/commands/handlers/fetch.js +2 -1
  5. package/dist/cjs/commands/handlers/store.js +4 -4
  6. package/dist/cjs/commands/list.js +17 -3
  7. package/dist/cjs/control.d.ts +4 -9
  8. package/dist/cjs/control.js +1 -1
  9. package/dist/cjs/dates.d.ts +9 -1
  10. package/dist/cjs/dates.js +20 -0
  11. package/dist/cjs/envelope.d.ts +1 -1
  12. package/dist/cjs/envelope.js +26 -1
  13. package/dist/cjs/index.d.ts +2 -1
  14. package/dist/cjs/load-plugins.d.ts +3 -1
  15. package/dist/cjs/load-plugins.js +25 -6
  16. package/dist/cjs/plugins/binary.js +2 -1
  17. package/dist/cjs/plugins/context-search.js +2 -2
  18. package/dist/cjs/plugins/create-special-use.d.ts +0 -5
  19. package/dist/cjs/plugins/create-special-use.js +11 -1
  20. package/dist/cjs/plugins/imap4rev2.js +5 -0
  21. package/dist/cjs/plugins/metadata-server.d.ts +6 -2
  22. package/dist/cjs/plugins/metadata-server.js +3 -1
  23. package/dist/cjs/plugins/metadata.d.ts +5 -1
  24. package/dist/cjs/plugins/metadata.js +3 -0
  25. package/dist/cjs/quirks.d.ts +2 -2
  26. package/dist/cjs/quirks.js +5 -4
  27. package/dist/cjs/rest.d.ts +7 -3
  28. package/dist/cjs/rest.js +11 -5
  29. package/dist/cjs/server.d.ts +54 -3
  30. package/dist/cjs/server.js +210 -88
  31. package/dist/cjs/storage-schema.js +12 -5
  32. package/dist/cjs/store-operations.d.ts +5 -2
  33. package/dist/cjs/store-operations.js +6 -3
  34. package/dist/cjs/types.d.ts +4 -0
  35. package/dist/esm/commands/handlers/fetch.js +2 -1
  36. package/dist/esm/commands/handlers/store.js +4 -4
  37. package/dist/esm/commands/list.js +17 -3
  38. package/dist/esm/control.d.ts +4 -9
  39. package/dist/esm/control.js +1 -1
  40. package/dist/esm/dates.d.ts +9 -1
  41. package/dist/esm/dates.js +20 -1
  42. package/dist/esm/envelope.d.ts +1 -1
  43. package/dist/esm/envelope.js +26 -1
  44. package/dist/esm/index.d.ts +2 -1
  45. package/dist/esm/load-plugins.d.ts +3 -1
  46. package/dist/esm/load-plugins.js +25 -6
  47. package/dist/esm/plugins/binary.js +2 -1
  48. package/dist/esm/plugins/context-search.js +2 -2
  49. package/dist/esm/plugins/create-special-use.d.ts +0 -5
  50. package/dist/esm/plugins/create-special-use.js +8 -1
  51. package/dist/esm/plugins/imap4rev2.js +5 -0
  52. package/dist/esm/plugins/metadata-server.d.ts +6 -2
  53. package/dist/esm/plugins/metadata-server.js +3 -1
  54. package/dist/esm/plugins/metadata.d.ts +5 -1
  55. package/dist/esm/plugins/metadata.js +3 -0
  56. package/dist/esm/quirks.d.ts +2 -2
  57. package/dist/esm/quirks.js +5 -4
  58. package/dist/esm/rest.d.ts +7 -3
  59. package/dist/esm/rest.js +11 -5
  60. package/dist/esm/server.d.ts +54 -3
  61. package/dist/esm/server.js +212 -90
  62. package/dist/esm/storage-schema.js +12 -5
  63. package/dist/esm/store-operations.d.ts +5 -2
  64. package/dist/esm/store-operations.js +6 -3
  65. package/dist/esm/types.d.ts +4 -0
  66. package/dist/plugin-help.json +3 -2
  67. package/package.json +1 -1
@@ -49,12 +49,21 @@ function resolvePlugin(name) {
49
49
  *
50
50
  * @param {Object} server IMAPServer instance
51
51
  * @param {Array|String|Function} plugins List of plugins to load
52
+ * @param {Map} [exclude] Plugins a quirk preset leaves out, plugin name to quirk name
53
+ * @throws {Error} when a loaded plugin requires an excluded one
52
54
  */
53
55
  function loadPlugins(server, plugins, exclude) {
54
56
  const loaded = new Set();
55
- // plugins a quirk preset leaves out (no-move), they are not loaded even when listed
56
- const excluded = new Set((exclude || []).map(name => resolvePlugin(name)).flatMap(name => (name ? [index_js_1.plugins[name]] : [])));
57
- const load = (entry) => {
57
+ // plugins a quirk preset leaves out (no-move), they are not loaded even when listed, and the quirk that removes them
58
+ const excluded = new Map();
59
+ (exclude || new Map()).forEach((quirk, name) => {
60
+ const resolved = resolvePlugin(name);
61
+ if (resolved) {
62
+ excluded.set(index_js_1.plugins[resolved], quirk);
63
+ }
64
+ });
65
+ // `requiredBy` names the plugin whose `requires` lists this one
66
+ const load = (entry, requiredBy) => {
58
67
  let plugin = entry;
59
68
  if (typeof plugin === 'string') {
60
69
  const name = resolvePlugin(plugin);
@@ -69,14 +78,24 @@ function loadPlugins(server, plugins, exclude) {
69
78
  if (typeof plugin !== 'function') {
70
79
  throw new TypeError('Invalid plugin, expecting a plugin name or a function');
71
80
  }
72
- if (loaded.has(plugin) || excluded.has(plugin)) {
81
+ const quirk = excluded.get(plugin);
82
+ if (quirk) {
83
+ if (requiredBy) {
84
+ // the plugin can not work without it (IMAP4rev2 folds in MOVE and UIDPLUS, RFC 9051 Appendix E), so
85
+ // advertising it without the required plugin would break its RFC
86
+ throw new Error(requiredBy + ' requires ' + entry + ', which the "' + quirk + '" quirk removes');
87
+ }
88
+ return;
89
+ }
90
+ if (loaded.has(plugin)) {
73
91
  return;
74
92
  }
75
93
  loaded.add(plugin);
76
- [].concat(plugin.requires || []).forEach(load);
94
+ const name = typeof entry === 'string' ? entry.trim() : plugin.name;
95
+ [].concat(plugin.requires || []).forEach(required => load(required, name));
77
96
  plugin(server);
78
97
  };
79
- [].concat(plugins || []).forEach(load);
98
+ [].concat(plugins || []).forEach(entry => load(entry));
80
99
  server.emit('pluginsLoaded');
81
100
  }
82
101
  exports.default = loadPlugins;
@@ -109,7 +109,8 @@ function getDecodedSection(connection, message, query) {
109
109
  }
110
110
  const node = (0, mimeparser_js_1.resolveNode)((0, mimeparser_js_1.getMessageData)(message).tree, section[0].value, connection.messageGlobal);
111
111
  if (!node) {
112
- // like BODY[<section>], a part that does not exist is empty
112
+ // like BODY[<section>], a part that does not exist is empty: RFC 3516 and RFC 9051 section 6.4.5 define no
113
+ // error for it, and BINARY follows the semantics of BODY (RFC 3516 section 4.2)
113
114
  return '';
114
115
  }
115
116
  if (!isLeaf(node, connection.messageGlobal)) {
@@ -252,8 +252,8 @@ function contextSearchPlugin(server) {
252
252
  // knows the current message list, so new matches can be reported after their EXISTS and FETCH responses
253
253
  // (RFC 5267 section 4.3.2). A command that closes the mailbox gets no updates, its tagged response ends them
254
254
  const processNotifications = connection.processNotifications;
255
- connection.processNotifications = function (data) {
256
- processNotifications.call(this, data);
255
+ connection.processNotifications = function (data, beforeCommand) {
256
+ processNotifications.call(this, data, beforeCommand);
257
257
  const command = ((data && data.command) || '').toUpperCase();
258
258
  if (this.searchContexts && this.searchContexts.size && !this.notificationQueue.length && !CLOSING_COMMANDS.has(command)) {
259
259
  checkContexts(this);
@@ -1,7 +1,2 @@
1
1
  import type { IMAPServer } from '../types.js';
2
- /**
3
- * @help Enables CREATE-SPECIAL-USE [RFC6154] capability
4
- * @help Allowed special flags can be set with server
5
- * @help option "special-use"
6
- */
7
2
  export default function createSpecialUsePlugin(server: IMAPServer): void;
@@ -1,11 +1,18 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.default = createSpecialUsePlugin;
7
+ const formal_1 = __importDefault(require("imap-handler/lib/formal"));
8
+ const arguments_js_1 = require("../arguments.js");
4
9
  /**
5
10
  * @help Enables CREATE-SPECIAL-USE [RFC6154] capability
6
11
  * @help Allowed special flags can be set with server
7
12
  * @help option "special-use"
8
13
  */
14
+ // use-attr-ext = "\" atom (RFC 6154 section 6), with the ATOM-CHAR of the imap-handler grammar (RFC 3501 section 9)
15
+ const USE_ATTR = new RegExp('^\\\\[' + formal_1.default['ATOM-CHAR']().replace(/[\\\]^-]/g, '\\$&') + ']+$');
9
16
  function createSpecialUsePlugin(server) {
10
17
  // Register capability
11
18
  server.registerCapability('CREATE-SPECIAL-USE');
@@ -34,7 +41,10 @@ function createSpecialUsePlugin(server) {
34
41
  }
35
42
  if (specialUseList) {
36
43
  for (i = 0, len = specialUseList.length; i < len; i++) {
37
- if (['ATOM', 'STRING', 'LITERAL'].indexOf(specialUseList[i].type) < 0) {
44
+ // RFC 6154 section 6: use-attr-ext = "\" atom, so every entry is an atom that starts with a backslash,
45
+ // not NIL, a string, a literal, a number or a list
46
+ const entry = specialUseList[i];
47
+ if (!(0, arguments_js_1.isAtom)(entry) || !USE_ATTR.test(entry.value)) {
38
48
  connection.send({
39
49
  tag: parsed.tag,
40
50
  command: 'BAD',
@@ -128,6 +128,11 @@ function imap4rev2Plugin(server) {
128
128
  }, 'SELECT LIST', parsed, data, mailbox);
129
129
  };
130
130
  server.outputHandlers.push((connection, response, description, parsed, data) => {
131
+ if (description === 'RECENT NOTIFICATION' && isRev2(connection)) {
132
+ // Appendix E item 12: no RECENT response after the EXISTS of new messages either
133
+ response.skipResponse = true;
134
+ return;
135
+ }
131
136
  if (!parsed || !response || !isRev2(connection)) {
132
137
  return;
133
138
  }
@@ -1,7 +1,11 @@
1
1
  import type { IMAPServer } from '../types.js';
2
2
  /**
3
3
  * @help Adds METADATA-SERVER [RFC5464] capability, like METADATA but
4
- * @help only for server annotations (mailbox name ""). With METADATA
4
+ * @help only for server annotations (mailbox name ""), loads ENABLE. With METADATA
5
5
  * @help also loaded, only METADATA is advertised
6
6
  */
7
- export default function metadataServerPlugin(server: IMAPServer): void;
7
+ declare function metadataServerPlugin(server: IMAPServer): void;
8
+ declare namespace metadataServerPlugin {
9
+ var requires: string[];
10
+ }
11
+ export default metadataServerPlugin;
@@ -4,12 +4,14 @@ exports.default = metadataServerPlugin;
4
4
  const metadata_js_1 = require("./metadata.js");
5
5
  /**
6
6
  * @help Adds METADATA-SERVER [RFC5464] capability, like METADATA but
7
- * @help only for server annotations (mailbox name ""). With METADATA
7
+ * @help only for server annotations (mailbox name ""), loads ENABLE. With METADATA
8
8
  * @help also loaded, only METADATA is advertised
9
9
  */
10
10
  function metadataServerPlugin(server) {
11
11
  (0, metadata_js_1.setup)(server, false);
12
12
  }
13
+ // RFC 5464 section 4.1: a server that sends unsolicited METADATA responses "MUST support the ENABLE command"
14
+ metadataServerPlugin.requires = ['ENABLE'];
13
15
  {
14
16
  const defaultExport = exports.default;
15
17
  if (defaultExport && (typeof defaultExport === 'function' || typeof defaultExport === 'object')) {
@@ -7,5 +7,9 @@ import type { IMAPServer } from '../types.js';
7
7
  * @param {Boolean} mailboxes If true, mailbox annotations are supported, otherwise only server annotations
8
8
  */
9
9
  declare function setup(server: IMAPServer, mailboxes: boolean): void;
10
- export default function metadataPlugin(server: IMAPServer): void;
10
+ declare function metadataPlugin(server: IMAPServer): void;
11
+ declare namespace metadataPlugin {
12
+ var requires: string[];
13
+ }
14
+ export default metadataPlugin;
11
15
  export { setup };
@@ -15,6 +15,7 @@ const enable_js_1 = require("./enable.js");
15
15
  * @help default 65536), "metadataMaxEntries" (per mailbox and for the
16
16
  * @help server, default 100). "metadataPrivate": false turns /private
17
17
  * @help entries off. ENABLE METADATA turns on unsolicited METADATA responses
18
+ * @help (loads the ENABLE plugin, RFC 5464 section 4.1)
18
19
  */
19
20
  // Default limits, RFC 5464 section 4.1 requires at least 1024 octets and 10 entries
20
21
  const DEFAULT_MAX_SIZE = 64 * 1024;
@@ -505,6 +506,8 @@ function setup(server, mailboxes) {
505
506
  function metadataPlugin(server) {
506
507
  setup(server, true);
507
508
  }
509
+ // RFC 5464 section 4.1: a server that sends unsolicited METADATA responses "MUST support the ENABLE command"
510
+ metadataPlugin.requires = ['ENABLE'];
508
511
  {
509
512
  const defaultExport = exports.default;
510
513
  if (defaultExport && (typeof defaultExport === 'function' || typeof defaultExport === 'object')) {
@@ -19,10 +19,10 @@ export declare const quirks: Record<string, Quirk>;
19
19
  * Resolves the `quirks` option
20
20
  *
21
21
  * @param {Array|String} names Quirk names
22
- * @return {Object} `{ rules, removePlugins }` of all of them
22
+ * @return {Object} `{ rules, removePlugins }` of all of them, removePlugins maps a plugin name to the quirk that removes it
23
23
  * @throws {Error} for an unknown name
24
24
  */
25
25
  export declare function resolveQuirks(names: string[] | string | null | undefined): {
26
26
  rules: ScriptRule[];
27
- removePlugins: Set<string>;
27
+ removePlugins: Map<string, string>;
28
28
  };
@@ -106,19 +106,20 @@ exports.quirks = {
106
106
  * Resolves the `quirks` option
107
107
  *
108
108
  * @param {Array|String} names Quirk names
109
- * @return {Object} `{ rules, removePlugins }` of all of them
109
+ * @return {Object} `{ rules, removePlugins }` of all of them, removePlugins maps a plugin name to the quirk that removes it
110
110
  * @throws {Error} for an unknown name
111
111
  */
112
112
  function resolveQuirks(names) {
113
113
  const rules = [];
114
- const removePlugins = new Set();
114
+ const removePlugins = new Map();
115
115
  [].concat(names || []).forEach(name => {
116
- const quirk = Object.hasOwn(exports.quirks, String(name).toLowerCase()) ? exports.quirks[String(name).toLowerCase()] : null;
116
+ const key = String(name).toLowerCase();
117
+ const quirk = Object.hasOwn(exports.quirks, key) ? exports.quirks[key] : null;
117
118
  if (!quirk) {
118
119
  throw new Error('Unknown quirk "' + name + '". Available quirks: ' + Object.keys(exports.quirks).join(', '));
119
120
  }
120
121
  rules.push(...(quirk.rules || []));
121
- (quirk.removePlugins || []).forEach(plugin => removePlugins.add(plugin));
122
+ (quirk.removePlugins || []).forEach(plugin => removePlugins.set(plugin, key));
122
123
  });
123
124
  return { rules, removePlugins };
124
125
  }
@@ -8,6 +8,10 @@
8
8
  import http from 'node:http';
9
9
  import type { IMAPServer } from './types.js';
10
10
  import type { ControlRoute as Route } from './control.js';
11
+ /** The HTTP server of the REST API, `endStreams()` ends the open event streams */
12
+ type RestServer = http.Server & {
13
+ endStreams?: () => void;
14
+ };
11
15
  /** Options of the REST API, the `rest` server option */
12
16
  interface RestOptions {
13
17
  port?: number | undefined;
@@ -29,7 +33,7 @@ declare function isLoopback(host: string): boolean;
29
33
  * @param {Object} server IMAP server
30
34
  * @return {Array} routes
31
35
  */
32
- declare function getRoutes(server: IMAPServer): Route[];
36
+ declare function getRoutes(server: IMAPServer, streams?: Set<http.ServerResponse>): Route[];
33
37
  /**
34
38
  * Describes the routes as an OpenAPI 3.1 document
35
39
  *
@@ -45,6 +49,6 @@ declare function openApi(routes: Route[]): Record<string, unknown>;
45
49
  * @return {Object} HTTP server
46
50
  * @throws {Error} for a host that is not a loopback address without a token
47
51
  */
48
- declare function createRestServer(server: IMAPServer, options: RestOptions): http.Server;
52
+ declare function createRestServer(server: IMAPServer, options: RestOptions): RestServer;
49
53
  export { createRestServer, isLoopback, getRoutes, openApi };
50
- export type { RestOptions };
54
+ export type { RestOptions, RestServer };
package/dist/cjs/rest.js CHANGED
@@ -107,8 +107,9 @@ function eventData(type, args) {
107
107
  * @param {Array} types Event types to send
108
108
  * @return {Function} writes the stream to a response
109
109
  */
110
- function eventStream(server, types) {
110
+ function eventStream(server, types, streams) {
111
111
  return (res, req) => {
112
+ streams.add(res);
112
113
  res.writeHead(200, { 'Content-Type': 'text/event-stream; charset=utf-8', 'Cache-Control': 'no-store' });
113
114
  res.write(': connected\n\n');
114
115
  const listeners = types.map(type => {
@@ -120,6 +121,7 @@ function eventStream(server, types) {
120
121
  const ping = setInterval(() => res.write(': ping\n\n'), 15000);
121
122
  ping.unref();
122
123
  req.on('close', () => {
124
+ streams.delete(res);
123
125
  clearInterval(ping);
124
126
  listeners.forEach(({ type, listener }) => server.removeListener(type, listener));
125
127
  });
@@ -131,7 +133,7 @@ function eventStream(server, types) {
131
133
  * @param {Object} server IMAP server
132
134
  * @return {Array} routes
133
135
  */
134
- function getRoutes(server) {
136
+ function getRoutes(server, streams = new Set()) {
135
137
  const control = server.control;
136
138
  const ruleJson = (handle) => ({ id: handle.id, rule: handle.rule, matched: handle.matched, hits: handle.hits });
137
139
  const routes = [
@@ -167,7 +169,7 @@ function getRoutes(server) {
167
169
  if (unknown.length) {
168
170
  throw (0, store_operations_js_1.storeError)('Unknown event type ' + unknown.join(', ') + ', expected ' + EVENT_TYPES.join(', '), 'INVALID');
169
171
  }
170
- return { stream: eventStream(server, types) };
172
+ return { stream: eventStream(server, types, streams) };
171
173
  }
172
174
  },
173
175
  {
@@ -508,7 +510,9 @@ function createRestServer(server, options) {
508
510
  if (!token && !isLoopback(host)) {
509
511
  throw new Error('The REST API controls the whole server, it needs a token (rest.token, --rest-token) to listen on ' + host);
510
512
  }
511
- const routes = getRoutes(server);
513
+ // open event streams, a shutdown ends them so that the server can close
514
+ const streams = new Set();
515
+ const routes = getRoutes(server, streams);
512
516
  const send = (res, status, body) => {
513
517
  const json = JSON.stringify(body);
514
518
  res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': Buffer.byteLength(json), 'Cache-Control': 'no-store' });
@@ -548,7 +552,7 @@ function createRestServer(server, options) {
548
552
  }
549
553
  send(res, 200, result === undefined ? {} : result);
550
554
  };
551
- return node_http_1.default.createServer((req, res) => {
555
+ const rest = node_http_1.default.createServer((req, res) => {
552
556
  handle(req, res).catch((err) => {
553
557
  const code = err instanceof store_operations_js_1.ImapKitError ? err.code : 'SERVERERROR';
554
558
  // RFC 5530 codes of failed mailbox operations (CANNOT, HASCHILDREN ...) are conflicts with the state
@@ -558,4 +562,6 @@ function createRestServer(server, options) {
558
562
  }
559
563
  });
560
564
  });
565
+ rest.endStreams = () => streams.forEach(res => res.end());
566
+ return rest;
561
567
  }
@@ -1,10 +1,10 @@
1
1
  import { Stream } from 'node:stream';
2
2
  import net from 'node:net';
3
- import http from 'node:http';
4
3
  import tls from 'node:tls';
5
4
  import type { CompilerOptions, ParserOptions } from 'imap-handler';
6
5
  import type { ResolvedCommandOptions } from './command-states.js';
7
6
  import { Control } from './control.js';
7
+ import type { RestServer } from './rest.js';
8
8
  import type { SessionInfo } from './control.js';
9
9
  import { ServerScript } from './script.js';
10
10
  import type { DeferredOutput, OutputOperation, ScriptContext, ScriptEvent, ScriptRule } from './script.js';
@@ -85,7 +85,7 @@ declare class IMAPServer extends Stream {
85
85
  /** the SMTP server that `start()` runs for the `smtp` option */
86
86
  smtpServer: SMTPListener | null;
87
87
  /** the HTTP server of the REST API that `start()` runs for the `rest` option */
88
- restServer: http.Server | null;
88
+ restServer: RestServer | null;
89
89
  constructor(options?: IMAPServerOptions);
90
90
  /**
91
91
  * Sets the users from the `users` option, or the default user. The users are deep copied, so that runtime changes
@@ -370,6 +370,25 @@ declare class IMAPServer extends Stream {
370
370
  * @param {Array} flags Flags of a message
371
371
  */
372
372
  rememberFlags(mailbox: Mailbox, flags: string[]): void;
373
+ /**
374
+ * Checks if a flag can be stored on messages of a mailbox: any flag when the mailbox allows new keywords
375
+ * (PERMANENTFLAGS has `\*`), otherwise only a flag in the PERMANENTFLAGS list that getStatus() builds, the
376
+ * `permanentFlags` of the mailbox and every flag its messages had. RFC 3501 section 7.1 (RFC 9051 section 7.1):
377
+ * PERMANENTFLAGS "indicates which of the known flags the client can change permanently", STORE ignores the others
378
+ *
379
+ * @param {Object} mailbox Mailbox object
380
+ * @param {String} flag Normalized flag
381
+ * @return {Boolean} true if the flag is a permanent flag of the mailbox
382
+ */
383
+ isPermanentFlag(mailbox: Mailbox, flag: string): boolean;
384
+ /**
385
+ * The flags of the PERMANENTFLAGS list (without `\*`): the `permanentFlags` of the mailbox and every flag its
386
+ * messages have or had (`knownFlags`, see rememberFlags)
387
+ *
388
+ * @param {Object} mailbox Mailbox object
389
+ * @return {Array} flags, a new list
390
+ */
391
+ permanentFlagList(mailbox: Mailbox): string[];
373
392
  /**
374
393
  * The current time for dates the server sets itself (INTERNALDATE of a message without one, SAVEDATE). The `now`
375
394
  * option fixes it for repeatable tests: a Date, a timestamp, or a function that returns one
@@ -548,6 +567,8 @@ interface QueuedCommand {
548
567
  rule: ScriptRule;
549
568
  context: ScriptContext;
550
569
  } | undefined;
570
+ /** handler for a command that has no registered one (AUTHENTICATE with an unknown mechanism) */
571
+ handler?: CommandHandler | undefined;
551
572
  }
552
573
  declare class IMAPConnection {
553
574
  [key: string]: any;
@@ -921,7 +942,36 @@ declare class IMAPConnection {
921
942
  * @return {Array} Notifications to send
922
943
  */
923
944
  prepareNotifications(queue: Notification[]): Notification[];
924
- processNotifications(data?: CommandContext | null): void;
945
+ /**
946
+ * Checks if EXPUNGE responses must wait while a command runs: during FETCH, STORE and SEARCH (RFC 3501 section
947
+ * 7.4.1), during the commands that extensions add to this list (see the noExpunge command option), and during UID
948
+ * SEARCH with message numbers in the search criteria (RFC 7162 section 3.2.10.2 for VANISHED, EXPUNGE may wait as
949
+ * well, RFC 3501 only allows it during UID commands)
950
+ *
951
+ * @param {Object} data Parsed command
952
+ * @return {Boolean} true if the command holds back EXPUNGE responses
953
+ */
954
+ holdsExpunge(data: CommandContext): boolean;
955
+ /**
956
+ * Sends the queued notifications. During a command that holds back EXPUNGE responses (see holdsExpunge), or with
957
+ * `beforeCommand` before a command that refers to messages by sequence number, only the notifications queued before
958
+ * the first pending EXPUNGE go out: new messages (EXISTS) and flag changes. RFC 3501 section 5.2: "A server MUST send
959
+ * mailbox size updates automatically if a mailbox size change is observed during the processing of a command",
960
+ * section 7.4.1 forbids only EXPUNGE during FETCH, STORE and SEARCH. What was queued after the EXPUNGE waits with it,
961
+ * an EXISTS sent before it would describe a list that the client can not know yet
962
+ *
963
+ * @param {Object} [data] Parsed command that runs, or null between commands
964
+ * @param {Boolean} [beforeCommand] true when the command has not run yet, then a command with message sequence
965
+ * numbers (COPY, MOVE) also waits with the EXPUNGE responses, its numbers refer to the messages before them
966
+ */
967
+ processNotifications(data?: CommandContext | null, beforeCommand?: boolean): void;
968
+ /**
969
+ * Sends an untagged RECENT response with the number of \Recent messages among the first `count` messages
970
+ *
971
+ * @param {Number} count Number of messages the client was told about
972
+ * @param {Map} sequence Message to the sequence number this session knows it by
973
+ */
974
+ sendRecent(count: number, sequence: Map<Message, number>): void;
925
975
  /**
926
976
  * Compile a command object to a response string and write it to socket.
927
977
  * If the command object has a skipResponse property, the command is
@@ -990,6 +1040,7 @@ declare class IMAPConnection {
990
1040
  * @return {Boolean} true if the command uses message sequence numbers
991
1041
  */
992
1042
  usesSequenceNumbers(parsed: CommandContext): boolean;
1043
+ findSequenceNumbers(parsed: CommandContext): boolean;
993
1044
  /**
994
1045
  * Decodes a SASL client response. It must be valid base64 by the RFC 3501 section 9 grammar,
995
1046
  * "=" stands for an empty initial response (RFC 4959 section 3).