imapkit 4.3.1 → 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 (121) hide show
  1. package/README.md +187 -67
  2. package/bin/help.txt +20 -5
  3. package/bin/imapkit.js +64 -24
  4. package/dist/cjs/commands/append.js +1 -1
  5. package/dist/cjs/commands/create.js +2 -2
  6. package/dist/cjs/commands/delete.js +2 -14
  7. package/dist/cjs/commands/handlers/fetch.js +2 -1
  8. package/dist/cjs/commands/handlers/flags.d.ts +3 -3
  9. package/dist/cjs/commands/handlers/flags.js +3 -3
  10. package/dist/cjs/commands/handlers/store.js +7 -7
  11. package/dist/cjs/commands/list.js +17 -3
  12. package/dist/cjs/commands/rename.js +6 -46
  13. package/dist/cjs/commands/select.js +1 -0
  14. package/dist/cjs/commands/subscribe.js +7 -8
  15. package/dist/cjs/commands/unsubscribe.js +3 -9
  16. package/dist/cjs/control.d.ts +425 -0
  17. package/dist/cjs/control.js +637 -0
  18. package/dist/cjs/dates.d.ts +9 -1
  19. package/dist/cjs/dates.js +20 -0
  20. package/dist/cjs/envelope.d.ts +1 -1
  21. package/dist/cjs/envelope.js +26 -1
  22. package/dist/cjs/index.d.ts +9 -1
  23. package/dist/cjs/index.js +9 -2
  24. package/dist/cjs/load-plugins.d.ts +3 -1
  25. package/dist/cjs/load-plugins.js +28 -4
  26. package/dist/cjs/plugins/acl.js +109 -42
  27. package/dist/cjs/plugins/binary.js +2 -1
  28. package/dist/cjs/plugins/condstore.js +7 -0
  29. package/dist/cjs/plugins/context-search.js +2 -2
  30. package/dist/cjs/plugins/create-special-use.d.ts +0 -5
  31. package/dist/cjs/plugins/create-special-use.js +11 -1
  32. package/dist/cjs/plugins/imap4rev2.js +5 -0
  33. package/dist/cjs/plugins/index.js +1 -3
  34. package/dist/cjs/plugins/metadata-server.d.ts +6 -2
  35. package/dist/cjs/plugins/metadata-server.js +3 -1
  36. package/dist/cjs/plugins/metadata.d.ts +5 -1
  37. package/dist/cjs/plugins/metadata.js +143 -57
  38. package/dist/cjs/plugins/objectid.js +10 -0
  39. package/dist/cjs/plugins/quota.js +50 -2
  40. package/dist/cjs/plugins/savedate.js +1 -1
  41. package/dist/cjs/plugins/special-use.d.ts +0 -8
  42. package/dist/cjs/plugins/special-use.js +23 -0
  43. package/dist/cjs/quirks.d.ts +28 -0
  44. package/dist/cjs/quirks.js +125 -0
  45. package/dist/cjs/random.d.ts +10 -0
  46. package/dist/cjs/random.js +22 -0
  47. package/dist/cjs/rest.d.ts +54 -0
  48. package/dist/cjs/rest.js +567 -0
  49. package/dist/cjs/script.d.ts +33 -4
  50. package/dist/cjs/script.js +64 -4
  51. package/dist/cjs/server.d.ts +199 -9
  52. package/dist/cjs/server.js +603 -227
  53. package/dist/cjs/smtp-listener.d.ts +3 -2
  54. package/dist/cjs/smtp-listener.js +10 -6
  55. package/dist/cjs/storage-schema.d.ts +139 -0
  56. package/dist/cjs/storage-schema.js +222 -0
  57. package/dist/cjs/store-operations.d.ts +171 -0
  58. package/dist/cjs/store-operations.js +425 -0
  59. package/dist/cjs/types.d.ts +26 -1
  60. package/dist/esm/commands/append.js +1 -1
  61. package/dist/esm/commands/create.js +2 -2
  62. package/dist/esm/commands/delete.js +2 -14
  63. package/dist/esm/commands/handlers/fetch.js +2 -1
  64. package/dist/esm/commands/handlers/flags.d.ts +3 -3
  65. package/dist/esm/commands/handlers/flags.js +3 -3
  66. package/dist/esm/commands/handlers/store.js +7 -7
  67. package/dist/esm/commands/list.js +17 -3
  68. package/dist/esm/commands/rename.js +6 -46
  69. package/dist/esm/commands/select.js +1 -0
  70. package/dist/esm/commands/subscribe.js +7 -8
  71. package/dist/esm/commands/unsubscribe.js +3 -9
  72. package/dist/esm/control.d.ts +425 -0
  73. package/dist/esm/control.js +630 -0
  74. package/dist/esm/dates.d.ts +9 -1
  75. package/dist/esm/dates.js +20 -1
  76. package/dist/esm/envelope.d.ts +1 -1
  77. package/dist/esm/envelope.js +26 -1
  78. package/dist/esm/index.d.ts +9 -1
  79. package/dist/esm/index.js +5 -2
  80. package/dist/esm/load-plugins.d.ts +3 -1
  81. package/dist/esm/load-plugins.js +28 -4
  82. package/dist/esm/plugins/acl.js +109 -42
  83. package/dist/esm/plugins/binary.js +2 -1
  84. package/dist/esm/plugins/condstore.js +7 -0
  85. package/dist/esm/plugins/context-search.js +2 -2
  86. package/dist/esm/plugins/create-special-use.d.ts +0 -5
  87. package/dist/esm/plugins/create-special-use.js +8 -1
  88. package/dist/esm/plugins/imap4rev2.js +5 -0
  89. package/dist/esm/plugins/index.js +1 -3
  90. package/dist/esm/plugins/metadata-server.d.ts +6 -2
  91. package/dist/esm/plugins/metadata-server.js +3 -1
  92. package/dist/esm/plugins/metadata.d.ts +5 -1
  93. package/dist/esm/plugins/metadata.js +143 -57
  94. package/dist/esm/plugins/objectid.js +10 -0
  95. package/dist/esm/plugins/quota.js +50 -2
  96. package/dist/esm/plugins/savedate.js +1 -1
  97. package/dist/esm/plugins/special-use.d.ts +0 -8
  98. package/dist/esm/plugins/special-use.js +23 -0
  99. package/dist/esm/quirks.d.ts +28 -0
  100. package/dist/esm/quirks.js +121 -0
  101. package/dist/esm/random.d.ts +10 -0
  102. package/dist/esm/random.js +19 -0
  103. package/dist/esm/rest.d.ts +54 -0
  104. package/dist/esm/rest.js +559 -0
  105. package/dist/esm/script.d.ts +33 -4
  106. package/dist/esm/script.js +63 -4
  107. package/dist/esm/server.d.ts +199 -9
  108. package/dist/esm/server.js +606 -230
  109. package/dist/esm/smtp-listener.d.ts +3 -2
  110. package/dist/esm/smtp-listener.js +10 -6
  111. package/dist/esm/storage-schema.d.ts +139 -0
  112. package/dist/esm/storage-schema.js +218 -0
  113. package/dist/esm/store-operations.d.ts +171 -0
  114. package/dist/esm/store-operations.js +410 -0
  115. package/dist/esm/types.d.ts +26 -1
  116. package/dist/plugin-help.json +3 -16
  117. package/package.json +11 -3
  118. package/dist/cjs/plugins/xtoybird.d.ts +0 -2
  119. package/dist/cjs/plugins/xtoybird.js +0 -236
  120. package/dist/esm/plugins/xtoybird.d.ts +0 -2
  121. package/dist/esm/plugins/xtoybird.js +0 -218
@@ -7,7 +7,8 @@ import type { IMAPServer } from './types.js';
7
7
  *
8
8
  * @param {Number} smtpPort - port to listen on for SMTP commands
9
9
  * @param {Object} imapServer - the ImapKit IMAP server
10
- * @param {Function} [callback] - function executed when SMTP server is listening
10
+ * @param {Function} [callback] - function executed when SMTP server is listening, with the port
11
+ * @param {String} [host] - address to listen on, all addresses if not set
11
12
  * @return {SMTPServer} the SMTP server instance, use `close()` to stop it
12
13
  */
13
- export declare function startSMTPServer(smtpPort: number, imapServer: IMAPServer, callback?: (() => void) | null): SMTPServer;
14
+ export declare function startSMTPServer(smtpPort: number, imapServer: IMAPServer, callback?: ((port: number) => void) | null, host?: string): SMTPServer;
@@ -9,10 +9,11 @@ const smtp_server_1 = require("smtp-server");
9
9
  *
10
10
  * @param {Number} smtpPort - port to listen on for SMTP commands
11
11
  * @param {Object} imapServer - the ImapKit IMAP server
12
- * @param {Function} [callback] - function executed when SMTP server is listening
12
+ * @param {Function} [callback] - function executed when SMTP server is listening, with the port
13
+ * @param {String} [host] - address to listen on, all addresses if not set
13
14
  * @return {SMTPServer} the SMTP server instance, use `close()` to stop it
14
15
  */
15
- function startSMTPServer(smtpPort, imapServer, callback) {
16
+ function startSMTPServer(smtpPort, imapServer, callback, host) {
16
17
  const credentials = imapServer.getCredentials();
17
18
  const server = new smtp_server_1.SMTPServer({
18
19
  banner: 'ImapKit',
@@ -39,9 +40,12 @@ function startSMTPServer(smtpPort, imapServer, callback) {
39
40
  console.error('SMTP server error: %s', err.message);
40
41
  }
41
42
  });
42
- server.listen(smtpPort, () => {
43
- console.log('Incoming SMTP server up and running on port %s', server.server.address().port);
44
- callback?.();
45
- });
43
+ const onListening = () => callback?.(server.server.address().port);
44
+ if (host) {
45
+ server.listen(smtpPort, host, onListening);
46
+ }
47
+ else {
48
+ server.listen(smtpPort, onListening);
49
+ }
46
50
  return server;
47
51
  }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * The shape of the `storage` option, which is also the shape of `server.control.snapshot()`: a JSON Schema for
3
+ * editors and other tools, and the check the server runs on the option, so that a typo in a fixture ("message" for
4
+ * "messages") fails with a clear error instead of an empty mailbox. Plugins keep their own data on mailboxes and
5
+ * messages (acl, metadata, MODSEQ ...), so other keys are allowed, only the types of the known keys are checked
6
+ */
7
+ /** JSON Schema (draft 2020-12) of the `storage` option */
8
+ export declare const storageSchema: {
9
+ readonly $schema: "https://json-schema.org/draft/2020-12/schema";
10
+ readonly title: "ImapKit storage";
11
+ readonly description: "Namespaces by prefix (\"INBOX\", \"\", \"INBOX.\", \"#shared/\" ...), INBOX is a mailbox of its own";
12
+ readonly type: "object";
13
+ readonly properties: {
14
+ readonly INBOX: {
15
+ readonly $ref: "#/$defs/mailbox";
16
+ };
17
+ };
18
+ readonly additionalProperties: {
19
+ readonly $ref: "#/$defs/namespace";
20
+ };
21
+ readonly $defs: {
22
+ readonly message: {
23
+ readonly oneOf: readonly [{
24
+ readonly type: "string";
25
+ readonly description: "the message source";
26
+ }, {
27
+ readonly type: "object";
28
+ readonly properties: {
29
+ readonly raw: {
30
+ readonly type: "string";
31
+ readonly description: "the message source (a Buffer also works in JavaScript)";
32
+ };
33
+ readonly uid: {
34
+ type: string;
35
+ minimum: number;
36
+ maximum: number;
37
+ };
38
+ readonly flags: {
39
+ readonly oneOf: readonly [{
40
+ readonly type: "string";
41
+ }, {
42
+ type: string;
43
+ items: {
44
+ type: string;
45
+ };
46
+ }];
47
+ };
48
+ readonly internaldate: {
49
+ readonly type: "string";
50
+ readonly description: "RFC 3501 date-time, e.g. \"14-Sep-2013 21:22:28 -0300\" (a Date also works in JavaScript)";
51
+ };
52
+ readonly recent: {
53
+ readonly type: "boolean";
54
+ };
55
+ };
56
+ }];
57
+ };
58
+ readonly mailbox: {
59
+ readonly type: "object";
60
+ readonly properties: {
61
+ readonly uid: {
62
+ readonly type: "integer";
63
+ };
64
+ readonly uidvalidity: {
65
+ type: string;
66
+ minimum: number;
67
+ maximum: number;
68
+ };
69
+ readonly uidnext: {
70
+ type: string;
71
+ minimum: number;
72
+ maximum: number;
73
+ };
74
+ readonly flags: {
75
+ type: string;
76
+ items: {
77
+ type: string;
78
+ };
79
+ };
80
+ readonly permanentFlags: {
81
+ type: string;
82
+ items: {
83
+ type: string;
84
+ };
85
+ };
86
+ readonly allowPermanentFlags: {
87
+ readonly type: "boolean";
88
+ };
89
+ readonly subscribed: {
90
+ readonly type: "boolean";
91
+ };
92
+ readonly knownFlags: {
93
+ type: string;
94
+ items: {
95
+ type: string;
96
+ };
97
+ };
98
+ readonly messages: {
99
+ readonly type: "array";
100
+ readonly items: {
101
+ readonly $ref: "#/$defs/message";
102
+ };
103
+ };
104
+ readonly folders: {
105
+ readonly type: "object";
106
+ readonly additionalProperties: {
107
+ readonly $ref: "#/$defs/mailbox";
108
+ };
109
+ };
110
+ };
111
+ };
112
+ readonly namespace: {
113
+ readonly type: "object";
114
+ readonly properties: {
115
+ readonly separator: {
116
+ readonly type: "string";
117
+ readonly minLength: 1;
118
+ readonly maxLength: 1;
119
+ };
120
+ readonly type: {
121
+ readonly enum: readonly ["personal", "user", "shared"];
122
+ };
123
+ readonly folders: {
124
+ readonly type: "object";
125
+ readonly additionalProperties: {
126
+ readonly $ref: "#/$defs/mailbox";
127
+ };
128
+ };
129
+ };
130
+ };
131
+ };
132
+ };
133
+ /**
134
+ * Checks the `storage` option
135
+ *
136
+ * @param {Object} storage Storage
137
+ * @throws {Error} "Invalid storage at <path>: <problem>"
138
+ */
139
+ export declare function validateStorage(storage: unknown): void;
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ /**
3
+ * The shape of the `storage` option, which is also the shape of `server.control.snapshot()`: a JSON Schema for
4
+ * editors and other tools, and the check the server runs on the option, so that a typo in a fixture ("message" for
5
+ * "messages") fails with a clear error instead of an empty mailbox. Plugins keep their own data on mailboxes and
6
+ * messages (acl, metadata, MODSEQ ...), so other keys are allowed, only the types of the known keys are checked
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.storageSchema = void 0;
10
+ exports.validateStorage = validateStorage;
11
+ const numbers_js_1 = require("./numbers.js");
12
+ const dates_js_1 = require("./dates.js");
13
+ // keys of plugins, an unknown key that looks like a typo of a known key is refused unless a plugin uses it
14
+ const PLUGIN_KEYS = [
15
+ 'acl',
16
+ 'metadata',
17
+ 'special-use',
18
+ 'MAILBOXID',
19
+ 'HIGHESTMODSEQ',
20
+ 'qresyncExpunged',
21
+ 'appendLimit',
22
+ 'SAVEDATE',
23
+ 'MODSEQ',
24
+ 'EMAILID',
25
+ 'THREADID',
26
+ 'X-GM-LABELS',
27
+ 'X-GM-MSGID',
28
+ 'X-GM-THRID'
29
+ ];
30
+ const flagList = { type: 'array', items: { type: 'string' } };
31
+ const nzNumber = { type: 'integer', minimum: 1, maximum: numbers_js_1.MAX_NUMBER };
32
+ /** JSON Schema (draft 2020-12) of the `storage` option */
33
+ exports.storageSchema = {
34
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
35
+ title: 'ImapKit storage',
36
+ description: 'Namespaces by prefix ("INBOX", "", "INBOX.", "#shared/" ...), INBOX is a mailbox of its own',
37
+ type: 'object',
38
+ properties: { INBOX: { $ref: '#/$defs/mailbox' } },
39
+ additionalProperties: { $ref: '#/$defs/namespace' },
40
+ $defs: {
41
+ message: {
42
+ oneOf: [
43
+ { type: 'string', description: 'the message source' },
44
+ {
45
+ type: 'object',
46
+ properties: {
47
+ raw: { type: 'string', description: 'the message source (a Buffer also works in JavaScript)' },
48
+ uid: nzNumber,
49
+ flags: { oneOf: [{ type: 'string' }, flagList] },
50
+ internaldate: {
51
+ type: 'string',
52
+ description: 'RFC 3501 date-time, e.g. "14-Sep-2013 21:22:28 -0300" (a Date also works in JavaScript)'
53
+ },
54
+ recent: { type: 'boolean' }
55
+ }
56
+ }
57
+ ]
58
+ },
59
+ mailbox: {
60
+ type: 'object',
61
+ properties: {
62
+ uid: { type: 'integer' },
63
+ uidvalidity: nzNumber,
64
+ uidnext: nzNumber,
65
+ flags: flagList,
66
+ permanentFlags: flagList,
67
+ allowPermanentFlags: { type: 'boolean' },
68
+ subscribed: { type: 'boolean' },
69
+ knownFlags: flagList,
70
+ messages: { type: 'array', items: { $ref: '#/$defs/message' } },
71
+ folders: { type: 'object', additionalProperties: { $ref: '#/$defs/mailbox' } }
72
+ }
73
+ },
74
+ namespace: {
75
+ type: 'object',
76
+ properties: {
77
+ separator: { type: 'string', minLength: 1, maxLength: 1 },
78
+ type: { enum: ['personal', 'user', 'shared'] },
79
+ folders: { type: 'object', additionalProperties: { $ref: '#/$defs/mailbox' } }
80
+ }
81
+ }
82
+ }
83
+ };
84
+ // the keys the schema knows, the typo check compares unknown keys with them
85
+ const MAILBOX_KEYS = Object.keys(exports.storageSchema.$defs.mailbox.properties);
86
+ const NAMESPACE_KEYS = ['separator', 'type'];
87
+ const MESSAGE_KEYS = Object.keys(exports.storageSchema.$defs.message.oneOf[1].properties);
88
+ /**
89
+ * Finds the known key that an unknown key looks like a typo of: the same key in another case, or one edit away
90
+ *
91
+ * @param {String} key Unknown key
92
+ * @param {Array} known Known keys
93
+ * @return {String|null} the known key, or null
94
+ */
95
+ function typoOf(key, known) {
96
+ if (known.includes(key) || PLUGIN_KEYS.includes(key)) {
97
+ return null;
98
+ }
99
+ return known.find(name => name.toLowerCase() === key.toLowerCase() || (key.length > 3 && editDistance(name, key) === 1)) || null;
100
+ }
101
+ /**
102
+ * Levenshtein distance, only used for short keys
103
+ */
104
+ function editDistance(a, b) {
105
+ const row = Array.from({ length: b.length + 1 }, (_, i) => i);
106
+ for (let i = 1; i <= a.length; i++) {
107
+ let previous = row[0];
108
+ row[0] = i;
109
+ for (let j = 1; j <= b.length; j++) {
110
+ const current = row[j];
111
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
112
+ previous = current;
113
+ }
114
+ }
115
+ return row[b.length];
116
+ }
117
+ const isObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
118
+ const isFlagList = (value) => Array.isArray(value) && value.every(flag => typeof flag === 'string');
119
+ const isNz = (value) => Number.isSafeInteger(value) && value >= 1 && value <= numbers_js_1.MAX_NUMBER;
120
+ /**
121
+ * Checks the `storage` option
122
+ *
123
+ * @param {Object} storage Storage
124
+ * @throws {Error} "Invalid storage at <path>: <problem>"
125
+ */
126
+ function validateStorage(storage) {
127
+ const fail = (path, problem) => {
128
+ throw new Error('Invalid storage at ' + path + ': ' + problem);
129
+ };
130
+ const checkKeys = (value, path, known) => {
131
+ Object.keys(value).forEach(key => {
132
+ const typo = typoOf(key, known);
133
+ if (typo) {
134
+ fail(path, 'unknown key ' + JSON.stringify(key) + ', did you mean ' + JSON.stringify(typo) + '?');
135
+ }
136
+ });
137
+ };
138
+ const checkMessage = (message, path) => {
139
+ if (typeof message === 'string' || message instanceof Uint8Array) {
140
+ return;
141
+ }
142
+ if (!isObject(message)) {
143
+ return fail(path, 'a message is a string or an object');
144
+ }
145
+ checkKeys(message, path, MESSAGE_KEYS);
146
+ if (message.raw !== undefined && typeof message.raw !== 'string' && !(message.raw instanceof Uint8Array)) {
147
+ fail(path + '.raw', 'must be a string');
148
+ }
149
+ if (message.uid !== undefined && !isNz(message.uid)) {
150
+ fail(path + '.uid', 'must be an integer from 1 to ' + numbers_js_1.MAX_NUMBER);
151
+ }
152
+ if (message.flags !== undefined && typeof message.flags !== 'string' && !isFlagList(message.flags)) {
153
+ fail(path + '.flags', 'must be a flag or a list of flags');
154
+ }
155
+ // FETCH sends the internal date as it is and SORT ARRIVAL reads it, so it must be a date-time of RFC 3501
156
+ // section 9 (month names in any case, they are sent as "Jan", "Feb", ...) or a valid Date. The save date
157
+ // of the SAVEDATE plugin is a date-time too (RFC 8514 section 4.2)
158
+ for (const key of ['internaldate', 'SAVEDATE']) {
159
+ const value = message[key];
160
+ if (value === undefined || value === false || (key === 'SAVEDATE' && value === null)) {
161
+ continue;
162
+ }
163
+ if (value instanceof Date ? isNaN(value.getTime()) : !(0, dates_js_1.isDateTime)(value)) {
164
+ fail(path + '.' + key, 'must be a date-time string like "14-Sep-2013 21:22:28 -0300" or a Date, not ' + JSON.stringify(value));
165
+ }
166
+ }
167
+ if (message.recent !== undefined && typeof message.recent !== 'boolean') {
168
+ fail(path + '.recent', 'must be true or false');
169
+ }
170
+ };
171
+ const checkMailbox = (mailbox, path, known) => {
172
+ if (!isObject(mailbox)) {
173
+ return fail(path, 'a mailbox is an object');
174
+ }
175
+ checkKeys(mailbox, path, known);
176
+ for (const key of ['uidvalidity', 'uidnext']) {
177
+ if (mailbox[key] !== undefined && !isNz(mailbox[key])) {
178
+ fail(path + '.' + key, 'must be an integer from 1 to ' + numbers_js_1.MAX_NUMBER);
179
+ }
180
+ }
181
+ for (const key of ['flags', 'permanentFlags', 'knownFlags']) {
182
+ if (mailbox[key] !== undefined && !isFlagList(mailbox[key])) {
183
+ fail(path + '.' + key, 'must be a list of strings');
184
+ }
185
+ }
186
+ for (const key of ['allowPermanentFlags', 'subscribed']) {
187
+ if (mailbox[key] !== undefined && typeof mailbox[key] !== 'boolean') {
188
+ fail(path + '.' + key, 'must be true or false');
189
+ }
190
+ }
191
+ if (mailbox.messages !== undefined) {
192
+ if (!Array.isArray(mailbox.messages)) {
193
+ fail(path + '.messages', 'must be a list of messages');
194
+ }
195
+ mailbox.messages.forEach((message, i) => checkMessage(message, path + '.messages[' + i + ']'));
196
+ }
197
+ if (mailbox.folders !== undefined) {
198
+ if (!isObject(mailbox.folders)) {
199
+ fail(path + '.folders', 'must be an object of mailboxes by name');
200
+ }
201
+ Object.keys(mailbox.folders).forEach(name => checkMailbox(mailbox.folders[name], path + '.folders[' + JSON.stringify(name) + ']', MAILBOX_KEYS));
202
+ }
203
+ };
204
+ if (!isObject(storage)) {
205
+ fail('the top level', 'storage is an object of namespaces');
206
+ }
207
+ Object.keys(storage).forEach(key => {
208
+ const value = storage[key];
209
+ const path = JSON.stringify(key);
210
+ if (key === 'INBOX') {
211
+ return checkMailbox(value, path, MAILBOX_KEYS.concat('separator'));
212
+ }
213
+ checkMailbox(value, path, MAILBOX_KEYS.concat(NAMESPACE_KEYS));
214
+ const namespace = value;
215
+ if (namespace.separator !== undefined && (typeof namespace.separator !== 'string' || namespace.separator.length !== 1)) {
216
+ fail(path + '.separator', 'must be a single character');
217
+ }
218
+ if (namespace.type !== undefined && !['personal', 'user', 'shared'].includes(namespace.type)) {
219
+ fail(path + '.type', 'must be "personal", "user" or "shared"');
220
+ }
221
+ });
222
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Changes of the message store that IMAP commands and the control API share: expunge, flag changes, and the
3
+ * mailbox operations of DELETE, RENAME, SUBSCRIBE and UNSUBSCRIBE. Each one tells the sessions about the change
4
+ * the way another session's command would (EXISTS, EXPUNGE, unsolicited FETCH, BYE, `mailbox` events). The
5
+ * session that caused a change is `server.activeConnection` (null for the control API), the commands keep only
6
+ * what is specific to a session: argument checks, the selected mailbox, read-only state and the responses.
7
+ */
8
+ import type { IMAPConnection, IMAPServer, Mailbox, Message } from './types.js';
9
+ /**
10
+ * the deprecated XOAUTH2 session timeout of the default user, and of a user the control API adds without one. It is
11
+ * stored and listed but never used, access tokens do not expire
12
+ */
13
+ declare const DEFAULT_SESSION_TIMEOUT: number;
14
+ /** An error of a failed store operation or control API call, `code` is a RFC 5530 response code or INVALID */
15
+ declare class ImapKitError extends Error {
16
+ code: string;
17
+ constructor(message: string, code: string);
18
+ }
19
+ /**
20
+ * Creates an error for a failed operation, with a RFC 5530 response code
21
+ *
22
+ * @param {String} message Error message
23
+ * @param {String} code Response code, e.g. "NONEXISTENT"
24
+ * @return {Error} Error object
25
+ */
26
+ declare function storeError(message: string, code: string): ImapKitError;
27
+ interface ExpungeOptions {
28
+ /** the session that caused the expunge, for the `expunge` event (QRESYNC, NOTIFY), defaults to the active session */
29
+ origin?: IMAPConnection | null | undefined;
30
+ /** a session that gets no EXPUNGE and no EXISTS responses (CLOSE) */
31
+ skip?: IMAPConnection | false | null | undefined;
32
+ /** a session that gets the EXPUNGE responses but no EXISTS response (EXPUNGE, MOVE) */
33
+ skipExists?: IMAPConnection | false | null | undefined;
34
+ /** the EXPUNGE responses go from the highest UID to the lowest (MESSAGELIMIT, RFC 9738 section 3.1) */
35
+ highestFirst?: boolean | undefined;
36
+ }
37
+ /**
38
+ * Removes messages from a mailbox and tells the sessions that have it selected with EXPUNGE responses
39
+ * (VANISHED with QRESYNC) and a new EXISTS. The `expunge` event goes out before the notifications
40
+ *
41
+ * @param {Object} server IMAP server
42
+ * @param {Object} mailbox Mailbox of the messages
43
+ * @param {Array|Function} messagesOrFilterFunc Messages to remove, or a function that returns true for them
44
+ * @param {Object} [options] `{ origin, skip, skipExists, highestFirst }`
45
+ * @return {Array} the removed messages
46
+ */
47
+ declare function expungeMessages(server: IMAPServer, mailbox: Mailbox, messagesOrFilterFunc: Message[] | ((message: Message) => unknown), options?: ExpungeOptions): Message[];
48
+ /**
49
+ * Tells the sessions that have a mailbox selected about changed flags, they get an untagged FETCH with the
50
+ * new flags (RFC 3501 section 5.2)
51
+ *
52
+ * @param {Object} server IMAP server
53
+ * @param {Object} mailbox Mailbox of the messages
54
+ * @param {Array} messages Messages with changed flags
55
+ * @param {Object} [ignoreConnection] A session that is not told, e.g. the one whose STORE changed the flags
56
+ */
57
+ declare function notifyFlagChanges(server: IMAPServer, mailbox: Mailbox, messages: Message[], ignoreConnection?: IMAPConnection | null): void;
58
+ type FlagMode = 'set' | 'add' | 'remove';
59
+ /**
60
+ * Checks flags the way STORE does: system flags the server knows (never \Recent), valid keywords, and only
61
+ * permanent flags in a mailbox that does not allow new keywords
62
+ *
63
+ * @param {Object} server IMAP server
64
+ * @param {Object} mailbox Mailbox the flags are for
65
+ * @param {Array} flags Flags to check
66
+ * @param {Boolean} stored true if the flags are stored, so they must be permanent flags of the mailbox
67
+ * @return {Array} the normalized flags without duplicates
68
+ * @throws {Error} INVALID for a flag that can not be used
69
+ */
70
+ declare function checkFlags(server: IMAPServer, mailbox: Mailbox, flags: unknown, stored: boolean): string[];
71
+ /**
72
+ * Changes the flags of messages outside of a STORE command. The flags follow the rules of STORE: system flags
73
+ * the server knows (never \Recent), valid keywords, and only permanent flags in a mailbox that does not allow
74
+ * new keywords. Plugins learn about the changed messages from the `flags` event `(mailbox, messages, origin)`
75
+ * before the sessions are told (CONDSTORE gives them new mod-sequences there)
76
+ *
77
+ * @param {Object} server IMAP server
78
+ * @param {Object} mailbox Mailbox of the messages
79
+ * @param {Array} messages Messages to change
80
+ * @param {Array} flags Flags to set, add or remove
81
+ * @param {String} mode "set", "add" or "remove"
82
+ * @return {Array} the messages whose flags changed
83
+ */
84
+ declare function changeFlags(server: IMAPServer, mailbox: Mailbox, messages: Message[], flags: string[], mode: FlagMode): Message[];
85
+ /**
86
+ * Creates a mailbox (CREATE), with any missing superior hierarchy levels
87
+ *
88
+ * @param {Object} server IMAP server
89
+ * @param {String} path Storage name of the mailbox
90
+ * @return {Object} the created mailbox
91
+ * @throws {Error} with a RFC 5530 `code` (ALREADYEXISTS, CANNOT ...)
92
+ */
93
+ declare function createMailbox(server: IMAPServer, path: string): Mailbox;
94
+ /**
95
+ * Deletes a mailbox (DELETE). Other sessions that have it selected are disconnected with an untagged BYE
96
+ * (RFC 2180 section 3.3, RFC 2683 section 3.1.2), as they can not be told about the deletion in any other way
97
+ *
98
+ * @param {Object} server IMAP server
99
+ * @param {String} path Storage name of the mailbox
100
+ * @throws {Error} with a response `code` (NONEXISTENT, CANNOT, HASCHILDREN ...)
101
+ */
102
+ declare function deleteMailbox(server: IMAPServer, path: string): void;
103
+ /**
104
+ * Renames a mailbox (RENAME). Renaming INBOX moves its messages to the new mailbox and leaves INBOX
105
+ * empty (RFC 3501 section 6.3.5). Everything that can be checked is checked up front, so that a failed
106
+ * rename never loses the source
107
+ *
108
+ * @param {Object} server IMAP server
109
+ * @param {String} source Storage name of the mailbox
110
+ * @param {String} destination New storage name
111
+ * @return {Object} `{ path, mailbox }`: the new name, and the source mailbox object, which is INBOX itself for
112
+ * a rename of INBOX
113
+ * @throws {Error} with a RFC 5530 `code` (NONEXISTENT, ALREADYEXISTS, CANNOT ...)
114
+ */
115
+ declare function renameMailbox(server: IMAPServer, source: string, destination: string): {
116
+ path: string;
117
+ mailbox: Mailbox;
118
+ };
119
+ type UidMode = 'keep' | 'renumber' | 'shuffle' | 'offset';
120
+ interface UidValidityOptions {
121
+ /** the new UIDVALIDITY, it must be greater than the current one. Default: one above every value in use */
122
+ uidvalidity?: number | undefined;
123
+ /** what happens to the UIDs: "keep" (default), "renumber" (1 to n in the current order), "shuffle" (1 to n in a
124
+ * random order) or "offset" (every UID moves above the old UIDNEXT, plus `offset`) */
125
+ uids?: UidMode | undefined;
126
+ /** extra gap for "offset" */
127
+ offset?: number | undefined;
128
+ /** seed of the "shuffle" order, for repeatable tests */
129
+ seed?: number | undefined;
130
+ }
131
+ /**
132
+ * Gives a mailbox a new UIDVALIDITY, optionally with new UIDs. A UID never changes during a session (RFC 9051
133
+ * section 2.3.1.1), so the sessions that have the mailbox selected are disconnected with BYE, and the new value
134
+ * is greater than every earlier one, as UIDs that do not persist require. A client that keeps its cached UIDs
135
+ * without checking UIDVALIDITY gets other messages ("shuffle") or none ("offset") for them
136
+ *
137
+ * @param {Object} server IMAP server
138
+ * @param {Object} mailbox Mailbox, not \Noselect
139
+ * @param {Object} [options] `{ uidvalidity, uids, offset, seed }`
140
+ * @return {Object} `{ uidvalidity, uidnext, uids }`, `uids` maps every old UID to its new one
141
+ * @throws {Error} INVALID for a UIDVALIDITY that is not greater, or invalid options
142
+ */
143
+ declare function resetUidValidity(server: IMAPServer, mailbox: Mailbox, options?: UidValidityOptions): {
144
+ uidvalidity: number;
145
+ uidnext: number;
146
+ uids: {
147
+ uid: number;
148
+ newUid: number;
149
+ }[];
150
+ };
151
+ /**
152
+ * Subscribes a mailbox (SUBSCRIBE), only an existing mailbox can be subscribed
153
+ *
154
+ * @param {Object} server IMAP server
155
+ * @param {String} path Storage name of the mailbox
156
+ * @return {Boolean} true if the subscription changed
157
+ * @throws {Error} NONEXISTENT for a name that is not a mailbox
158
+ */
159
+ declare function subscribeMailbox(server: IMAPServer, path: string): boolean;
160
+ /**
161
+ * Unsubscribes a name (UNSUBSCRIBE). The subscription list holds names, so a name stays removable after its
162
+ * mailbox is deleted (RFC 3501 section 6.3.6). Removing a name that is not subscribed is not an error
163
+ * (RFC 9051 section 6.3.8)
164
+ *
165
+ * @param {Object} server IMAP server
166
+ * @param {String} path Storage name
167
+ * @return {Boolean} true if the subscription changed
168
+ */
169
+ declare function unsubscribeMailbox(server: IMAPServer, path: string): boolean;
170
+ export { DEFAULT_SESSION_TIMEOUT, ImapKitError, storeError, expungeMessages, notifyFlagChanges, checkFlags, changeFlags, createMailbox, deleteMailbox, renameMailbox, resetUidValidity, subscribeMailbox, unsubscribeMailbox };
171
+ export type { ExpungeOptions, FlagMode, UidMode, UidValidityOptions };