imapkit 5.1.0 → 5.2.0
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/README.md +5 -3
- package/bin/help.txt +5 -3
- package/bin/imapkit.js +8 -0
- package/dist/browser/imapkit.js +24 -24
- package/dist/cjs/control.js +1 -1
- package/dist/cjs/rest.js +15 -5
- package/dist/cjs/server.d.ts +24 -0
- package/dist/cjs/server.js +54 -2
- package/dist/cjs/types.d.ts +6 -0
- package/dist/esm/control.js +1 -1
- package/dist/esm/rest.js +15 -5
- package/dist/esm/server.d.ts +24 -0
- package/dist/esm/server.js +54 -2
- package/dist/esm/types.d.ts +6 -0
- package/package.json +6 -6
package/dist/cjs/control.js
CHANGED
package/dist/cjs/rest.js
CHANGED
|
@@ -69,8 +69,10 @@ function decodeBody(value, encoding) {
|
|
|
69
69
|
}
|
|
70
70
|
return encoding === 'base64' && typeof value === 'string' ? Buffer.from(value, 'base64') : value;
|
|
71
71
|
}
|
|
72
|
-
// the server events that GET /v1/events streams
|
|
73
|
-
const EVENT_TYPES = ['session', 'command', 'mailbox', 'expunge', 'flags', 'acl', 'script', 'reset'];
|
|
72
|
+
// the server events that GET /v1/events streams by default
|
|
73
|
+
const EVENT_TYPES = ['session', 'command', 'mailbox', 'append', 'expunge', 'flags', 'acl', 'script', 'reset'];
|
|
74
|
+
// event types that are streamed only when ?types= asks for them, the protocol trace of every session is a lot of data
|
|
75
|
+
const OPT_IN_EVENT_TYPES = ['trace'];
|
|
74
76
|
const sessionNumber = (origin) => (origin ? origin.sessionNumber : null);
|
|
75
77
|
/**
|
|
76
78
|
* Turns the arguments of a server event into JSON data: mailboxes become paths, messages UIDs, sessions numbers
|
|
@@ -85,8 +87,13 @@ function eventData(type, args) {
|
|
|
85
87
|
const { type: change, path, oldPath, origin } = args[0];
|
|
86
88
|
return { type: change, path, oldPath, origin: sessionNumber(origin) };
|
|
87
89
|
}
|
|
90
|
+
case 'append':
|
|
88
91
|
case 'expunge':
|
|
89
92
|
return { path: args[0].path, uids: args[1].map(message => message.uid), origin: sessionNumber(args[2]) };
|
|
93
|
+
case 'trace': {
|
|
94
|
+
const { session, direction, data } = args[0];
|
|
95
|
+
return { session, direction, data: Buffer.from(data).toString('base64'), encoding: 'base64' };
|
|
96
|
+
}
|
|
90
97
|
case 'flags':
|
|
91
98
|
return {
|
|
92
99
|
path: args[0].path,
|
|
@@ -161,13 +168,16 @@ function getRoutes(server, streams = new Set()) {
|
|
|
161
168
|
{
|
|
162
169
|
method: 'GET',
|
|
163
170
|
path: '/v1/events',
|
|
164
|
-
summary: 'Server events as Server-Sent Events (?types=session,command to choose: ' +
|
|
171
|
+
summary: 'Server events as Server-Sent Events (?types=session,command to choose: ' +
|
|
172
|
+
EVENT_TYPES.concat(OPT_IN_EVENT_TYPES).join(', ') +
|
|
173
|
+
', trace only when asked for)',
|
|
165
174
|
handler: ({ query }) => {
|
|
166
175
|
const wanted = query.get('types');
|
|
167
176
|
const types = wanted === null ? EVENT_TYPES : wanted.split(',');
|
|
168
|
-
const
|
|
177
|
+
const known = EVENT_TYPES.concat(OPT_IN_EVENT_TYPES);
|
|
178
|
+
const unknown = types.filter(type => known.indexOf(type) < 0);
|
|
169
179
|
if (unknown.length) {
|
|
170
|
-
throw (0, store_operations_js_1.storeError)('Unknown event type ' + unknown.join(', ') + ', expected ' +
|
|
180
|
+
throw (0, store_operations_js_1.storeError)('Unknown event type ' + unknown.join(', ') + ', expected ' + known.join(', '), 'INVALID');
|
|
171
181
|
}
|
|
172
182
|
return { stream: eventStream(server, types, streams) };
|
|
173
183
|
}
|
package/dist/cjs/server.d.ts
CHANGED
|
@@ -83,6 +83,8 @@ declare class IMAPServer extends Stream {
|
|
|
83
83
|
sessionCounter: number;
|
|
84
84
|
/** the control API, see src/control.ts */
|
|
85
85
|
control: Control;
|
|
86
|
+
/** the implicit TLS listener that `start()` runs for the `tls` option */
|
|
87
|
+
tlsServer: tls.Server | null;
|
|
86
88
|
/** the SMTP server that `start()` runs for the `smtp` option */
|
|
87
89
|
smtpServer: SMTPListener | null;
|
|
88
90
|
/** the HTTP server of the REST API that `start()` runs for the `rest` option */
|
|
@@ -107,6 +109,14 @@ declare class IMAPServer extends Stream {
|
|
|
107
109
|
* @return {Promise<Number>} resolves with the port the server listens on
|
|
108
110
|
*/
|
|
109
111
|
start(port?: number, host?: string): Promise<number>;
|
|
112
|
+
/**
|
|
113
|
+
* Starts the implicit TLS listener of the `tls` option. Its sessions are sessions of this server like the ones of
|
|
114
|
+
* the main listener, they share the storage and count as encrypted
|
|
115
|
+
*
|
|
116
|
+
* @param {String} [defaultHost] Address to listen on when the `tls` option has no host
|
|
117
|
+
* @return {Promise<Number>} resolves with the port of the TLS listener
|
|
118
|
+
*/
|
|
119
|
+
startTlsListener(defaultHost?: string): Promise<number>;
|
|
110
120
|
/**
|
|
111
121
|
* Starts the REST API of the `rest` option, see src/rest.ts
|
|
112
122
|
*
|
|
@@ -140,6 +150,13 @@ declare class IMAPServer extends Stream {
|
|
|
140
150
|
listen(port: number, hostname: string, listeningListener?: () => void): void;
|
|
141
151
|
listen(options: net.ListenOptions, listeningListener?: () => void): void;
|
|
142
152
|
close(callback?: (err?: Error) => void): void;
|
|
153
|
+
/**
|
|
154
|
+
* Stops accepting IMAP connections, on the main listener and on the TLS listener of the `tls` option
|
|
155
|
+
*
|
|
156
|
+
* @param {Function} [callback] Called once both listeners are closed, which is after their last connection is
|
|
157
|
+
* gone, with the first error
|
|
158
|
+
*/
|
|
159
|
+
closeImapListeners(callback?: (err?: Error) => void): void;
|
|
143
160
|
/**
|
|
144
161
|
* Stops the SMTP and REST listeners of `start()`
|
|
145
162
|
*
|
|
@@ -677,6 +694,13 @@ declare class IMAPConnection {
|
|
|
677
694
|
* @param {Function} [callback] Called once the data was handed to the system (a transport: on the next turn)
|
|
678
695
|
*/
|
|
679
696
|
writeLayer(data: Buffer, transport: Transport | null, callback?: () => void): void;
|
|
697
|
+
/**
|
|
698
|
+
* Emits the protocol traffic of the session as `trace` events, as the IMAP layer sees it: above TLS and COMPRESS
|
|
699
|
+
*
|
|
700
|
+
* @param {String} direction "in" for client input, "out" for server output
|
|
701
|
+
* @param {Buffer} data The octets
|
|
702
|
+
*/
|
|
703
|
+
trace(direction: 'in' | 'out', data: Buffer): void;
|
|
680
704
|
/**
|
|
681
705
|
* Sends output through the script rule that handles its event, if there is one
|
|
682
706
|
*
|
package/dist/cjs/server.js
CHANGED
|
@@ -242,6 +242,7 @@ class IMAPServer extends node_stream_1.Stream {
|
|
|
242
242
|
}
|
|
243
243
|
this.systemFlags = [].concat(this.options.systemFlags || ['\\Answered', '\\Flagged', '\\Draft', '\\Deleted', '\\Seen']);
|
|
244
244
|
this.loadStorage();
|
|
245
|
+
this.tlsServer = null;
|
|
245
246
|
this.smtpServer = null;
|
|
246
247
|
this.restServer = null;
|
|
247
248
|
}
|
|
@@ -292,9 +293,25 @@ class IMAPServer extends node_stream_1.Stream {
|
|
|
292
293
|
*/
|
|
293
294
|
async start(port, host) {
|
|
294
295
|
const imapPort = await listenOn(this.server, port, host);
|
|
295
|
-
await Promise.all([
|
|
296
|
+
await Promise.all([
|
|
297
|
+
this.options.tls ? this.startTlsListener(host) : null,
|
|
298
|
+
this.options.smtp ? this.startSmtp() : null,
|
|
299
|
+
this.options.rest ? this.startRest() : null
|
|
300
|
+
]);
|
|
296
301
|
return imapPort;
|
|
297
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Starts the implicit TLS listener of the `tls` option. Its sessions are sessions of this server like the ones of
|
|
305
|
+
* the main listener, they share the storage and count as encrypted
|
|
306
|
+
*
|
|
307
|
+
* @param {String} [defaultHost] Address to listen on when the `tls` option has no host
|
|
308
|
+
* @return {Promise<Number>} resolves with the port of the TLS listener
|
|
309
|
+
*/
|
|
310
|
+
startTlsListener(defaultHost) {
|
|
311
|
+
const { port, host } = this.options.tls || {};
|
|
312
|
+
this.tlsServer = node_tls_1.default.createServer(this.getCredentials(), this.createClient.bind(this));
|
|
313
|
+
return listenOn(this.tlsServer, port, host || defaultHost);
|
|
314
|
+
}
|
|
298
315
|
/**
|
|
299
316
|
* Starts the REST API of the `rest` option, see src/rest.ts
|
|
300
317
|
*
|
|
@@ -344,7 +361,7 @@ class IMAPServer extends node_stream_1.Stream {
|
|
|
344
361
|
}
|
|
345
362
|
close(callback) {
|
|
346
363
|
this.closeListeners(true);
|
|
347
|
-
this.
|
|
364
|
+
this.closeImapListeners(callback);
|
|
348
365
|
// close() only completes once all connections are gone
|
|
349
366
|
this.connections.forEach((connection) => {
|
|
350
367
|
if (connection.socket) {
|
|
@@ -352,6 +369,24 @@ class IMAPServer extends node_stream_1.Stream {
|
|
|
352
369
|
}
|
|
353
370
|
});
|
|
354
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Stops accepting IMAP connections, on the main listener and on the TLS listener of the `tls` option
|
|
374
|
+
*
|
|
375
|
+
* @param {Function} [callback] Called once both listeners are closed, which is after their last connection is
|
|
376
|
+
* gone, with the first error
|
|
377
|
+
*/
|
|
378
|
+
closeImapListeners(callback) {
|
|
379
|
+
const listeners = this.tlsServer ? [this.server, this.tlsServer] : [this.server];
|
|
380
|
+
this.tlsServer = null;
|
|
381
|
+
let pending = listeners.length;
|
|
382
|
+
let error;
|
|
383
|
+
listeners.forEach(listener => listener.close(err => {
|
|
384
|
+
error = error || err;
|
|
385
|
+
if (--pending === 0) {
|
|
386
|
+
callback?.(error);
|
|
387
|
+
}
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
355
390
|
/**
|
|
356
391
|
* Stops the SMTP and REST listeners of `start()`
|
|
357
392
|
*
|
|
@@ -1152,6 +1187,8 @@ class IMAPServer extends node_stream_1.Stream {
|
|
|
1152
1187
|
break;
|
|
1153
1188
|
}
|
|
1154
1189
|
}
|
|
1190
|
+
// like expunge, before the sessions are told: APPEND, COPY, MOVE, REPLACE, SMTP and the control API all add here
|
|
1191
|
+
this.emit('append', mailbox, [message], this.activeConnection);
|
|
1155
1192
|
this.notify({
|
|
1156
1193
|
tag: '*',
|
|
1157
1194
|
attributes: [
|
|
@@ -1596,6 +1633,7 @@ class IMAPConnection {
|
|
|
1596
1633
|
* @param {Function} [callback] Called once the data was handed to the system (a transport: on the next turn)
|
|
1597
1634
|
*/
|
|
1598
1635
|
writeLayer(data, transport, callback) {
|
|
1636
|
+
this.trace('out', data);
|
|
1599
1637
|
if (transport) {
|
|
1600
1638
|
transport.write(data);
|
|
1601
1639
|
if (callback) {
|
|
@@ -1606,6 +1644,17 @@ class IMAPConnection {
|
|
|
1606
1644
|
this.writeRaw(data, callback);
|
|
1607
1645
|
}
|
|
1608
1646
|
}
|
|
1647
|
+
/**
|
|
1648
|
+
* Emits the protocol traffic of the session as `trace` events, as the IMAP layer sees it: above TLS and COMPRESS
|
|
1649
|
+
*
|
|
1650
|
+
* @param {String} direction "in" for client input, "out" for server output
|
|
1651
|
+
* @param {Buffer} data The octets
|
|
1652
|
+
*/
|
|
1653
|
+
trace(direction, data) {
|
|
1654
|
+
if (this.server.listenerCount('trace')) {
|
|
1655
|
+
this.server.emit('trace', { session: this.sessionNumber, direction, data });
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1609
1658
|
/**
|
|
1610
1659
|
* Sends output through the script rule that handles its event, if there is one
|
|
1611
1660
|
*
|
|
@@ -1916,6 +1965,9 @@ class IMAPConnection {
|
|
|
1916
1965
|
if (this._resetting) {
|
|
1917
1966
|
return;
|
|
1918
1967
|
}
|
|
1968
|
+
if (chunk && chunk.length) {
|
|
1969
|
+
this.trace('in', chunk);
|
|
1970
|
+
}
|
|
1919
1971
|
// everything in one read arrived before anything this read causes to be sent
|
|
1920
1972
|
this._readCount = (this._readCount || 0) + 1;
|
|
1921
1973
|
str = (chunk || '').toString('binary');
|
package/dist/cjs/types.d.ts
CHANGED
|
@@ -228,6 +228,12 @@ export interface IMAPServerOptions {
|
|
|
228
228
|
plugins?: (string | Plugin)[] | string | Plugin | undefined;
|
|
229
229
|
users?: Record<string, UserData> | undefined;
|
|
230
230
|
secureConnection?: boolean | undefined;
|
|
231
|
+
/** starts a second IMAP listener for implicit TLS (IMAPS) with `server.start()`, its sessions share the storage
|
|
232
|
+
* with the main listener. Without a host it listens on the host given to `start()` */
|
|
233
|
+
tls?: {
|
|
234
|
+
port?: number | undefined;
|
|
235
|
+
host?: string | undefined;
|
|
236
|
+
} | undefined;
|
|
231
237
|
/** starts an SMTP server with `server.start()` that appends every received message to INBOX, needs the optional
|
|
232
238
|
* smtp-server package */
|
|
233
239
|
smtp?: {
|
package/dist/esm/control.js
CHANGED
package/dist/esm/rest.js
CHANGED
|
@@ -60,8 +60,10 @@ function decodeBody(value, encoding) {
|
|
|
60
60
|
}
|
|
61
61
|
return encoding === 'base64' && typeof value === 'string' ? Buffer.from(value, 'base64') : value;
|
|
62
62
|
}
|
|
63
|
-
// the server events that GET /v1/events streams
|
|
64
|
-
const EVENT_TYPES = ['session', 'command', 'mailbox', 'expunge', 'flags', 'acl', 'script', 'reset'];
|
|
63
|
+
// the server events that GET /v1/events streams by default
|
|
64
|
+
const EVENT_TYPES = ['session', 'command', 'mailbox', 'append', 'expunge', 'flags', 'acl', 'script', 'reset'];
|
|
65
|
+
// event types that are streamed only when ?types= asks for them, the protocol trace of every session is a lot of data
|
|
66
|
+
const OPT_IN_EVENT_TYPES = ['trace'];
|
|
65
67
|
const sessionNumber = (origin) => (origin ? origin.sessionNumber : null);
|
|
66
68
|
/**
|
|
67
69
|
* Turns the arguments of a server event into JSON data: mailboxes become paths, messages UIDs, sessions numbers
|
|
@@ -76,8 +78,13 @@ function eventData(type, args) {
|
|
|
76
78
|
const { type: change, path, oldPath, origin } = args[0];
|
|
77
79
|
return { type: change, path, oldPath, origin: sessionNumber(origin) };
|
|
78
80
|
}
|
|
81
|
+
case 'append':
|
|
79
82
|
case 'expunge':
|
|
80
83
|
return { path: args[0].path, uids: args[1].map(message => message.uid), origin: sessionNumber(args[2]) };
|
|
84
|
+
case 'trace': {
|
|
85
|
+
const { session, direction, data } = args[0];
|
|
86
|
+
return { session, direction, data: Buffer.from(data).toString('base64'), encoding: 'base64' };
|
|
87
|
+
}
|
|
81
88
|
case 'flags':
|
|
82
89
|
return {
|
|
83
90
|
path: args[0].path,
|
|
@@ -152,13 +159,16 @@ function getRoutes(server, streams = new Set()) {
|
|
|
152
159
|
{
|
|
153
160
|
method: 'GET',
|
|
154
161
|
path: '/v1/events',
|
|
155
|
-
summary: 'Server events as Server-Sent Events (?types=session,command to choose: ' +
|
|
162
|
+
summary: 'Server events as Server-Sent Events (?types=session,command to choose: ' +
|
|
163
|
+
EVENT_TYPES.concat(OPT_IN_EVENT_TYPES).join(', ') +
|
|
164
|
+
', trace only when asked for)',
|
|
156
165
|
handler: ({ query }) => {
|
|
157
166
|
const wanted = query.get('types');
|
|
158
167
|
const types = wanted === null ? EVENT_TYPES : wanted.split(',');
|
|
159
|
-
const
|
|
168
|
+
const known = EVENT_TYPES.concat(OPT_IN_EVENT_TYPES);
|
|
169
|
+
const unknown = types.filter(type => known.indexOf(type) < 0);
|
|
160
170
|
if (unknown.length) {
|
|
161
|
-
throw storeError('Unknown event type ' + unknown.join(', ') + ', expected ' +
|
|
171
|
+
throw storeError('Unknown event type ' + unknown.join(', ') + ', expected ' + known.join(', '), 'INVALID');
|
|
162
172
|
}
|
|
163
173
|
return { stream: eventStream(server, types, streams) };
|
|
164
174
|
}
|
package/dist/esm/server.d.ts
CHANGED
|
@@ -83,6 +83,8 @@ declare class IMAPServer extends Stream {
|
|
|
83
83
|
sessionCounter: number;
|
|
84
84
|
/** the control API, see src/control.ts */
|
|
85
85
|
control: Control;
|
|
86
|
+
/** the implicit TLS listener that `start()` runs for the `tls` option */
|
|
87
|
+
tlsServer: tls.Server | null;
|
|
86
88
|
/** the SMTP server that `start()` runs for the `smtp` option */
|
|
87
89
|
smtpServer: SMTPListener | null;
|
|
88
90
|
/** the HTTP server of the REST API that `start()` runs for the `rest` option */
|
|
@@ -107,6 +109,14 @@ declare class IMAPServer extends Stream {
|
|
|
107
109
|
* @return {Promise<Number>} resolves with the port the server listens on
|
|
108
110
|
*/
|
|
109
111
|
start(port?: number, host?: string): Promise<number>;
|
|
112
|
+
/**
|
|
113
|
+
* Starts the implicit TLS listener of the `tls` option. Its sessions are sessions of this server like the ones of
|
|
114
|
+
* the main listener, they share the storage and count as encrypted
|
|
115
|
+
*
|
|
116
|
+
* @param {String} [defaultHost] Address to listen on when the `tls` option has no host
|
|
117
|
+
* @return {Promise<Number>} resolves with the port of the TLS listener
|
|
118
|
+
*/
|
|
119
|
+
startTlsListener(defaultHost?: string): Promise<number>;
|
|
110
120
|
/**
|
|
111
121
|
* Starts the REST API of the `rest` option, see src/rest.ts
|
|
112
122
|
*
|
|
@@ -140,6 +150,13 @@ declare class IMAPServer extends Stream {
|
|
|
140
150
|
listen(port: number, hostname: string, listeningListener?: () => void): void;
|
|
141
151
|
listen(options: net.ListenOptions, listeningListener?: () => void): void;
|
|
142
152
|
close(callback?: (err?: Error) => void): void;
|
|
153
|
+
/**
|
|
154
|
+
* Stops accepting IMAP connections, on the main listener and on the TLS listener of the `tls` option
|
|
155
|
+
*
|
|
156
|
+
* @param {Function} [callback] Called once both listeners are closed, which is after their last connection is
|
|
157
|
+
* gone, with the first error
|
|
158
|
+
*/
|
|
159
|
+
closeImapListeners(callback?: (err?: Error) => void): void;
|
|
143
160
|
/**
|
|
144
161
|
* Stops the SMTP and REST listeners of `start()`
|
|
145
162
|
*
|
|
@@ -677,6 +694,13 @@ declare class IMAPConnection {
|
|
|
677
694
|
* @param {Function} [callback] Called once the data was handed to the system (a transport: on the next turn)
|
|
678
695
|
*/
|
|
679
696
|
writeLayer(data: Buffer, transport: Transport | null, callback?: () => void): void;
|
|
697
|
+
/**
|
|
698
|
+
* Emits the protocol traffic of the session as `trace` events, as the IMAP layer sees it: above TLS and COMPRESS
|
|
699
|
+
*
|
|
700
|
+
* @param {String} direction "in" for client input, "out" for server output
|
|
701
|
+
* @param {Buffer} data The octets
|
|
702
|
+
*/
|
|
703
|
+
trace(direction: 'in' | 'out', data: Buffer): void;
|
|
680
704
|
/**
|
|
681
705
|
* Sends output through the script rule that handles its event, if there is one
|
|
682
706
|
*
|
package/dist/esm/server.js
CHANGED
|
@@ -201,6 +201,7 @@ class IMAPServer extends Stream {
|
|
|
201
201
|
}
|
|
202
202
|
this.systemFlags = [].concat(this.options.systemFlags || ['\\Answered', '\\Flagged', '\\Draft', '\\Deleted', '\\Seen']);
|
|
203
203
|
this.loadStorage();
|
|
204
|
+
this.tlsServer = null;
|
|
204
205
|
this.smtpServer = null;
|
|
205
206
|
this.restServer = null;
|
|
206
207
|
}
|
|
@@ -251,9 +252,25 @@ class IMAPServer extends Stream {
|
|
|
251
252
|
*/
|
|
252
253
|
async start(port, host) {
|
|
253
254
|
const imapPort = await listenOn(this.server, port, host);
|
|
254
|
-
await Promise.all([
|
|
255
|
+
await Promise.all([
|
|
256
|
+
this.options.tls ? this.startTlsListener(host) : null,
|
|
257
|
+
this.options.smtp ? this.startSmtp() : null,
|
|
258
|
+
this.options.rest ? this.startRest() : null
|
|
259
|
+
]);
|
|
255
260
|
return imapPort;
|
|
256
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Starts the implicit TLS listener of the `tls` option. Its sessions are sessions of this server like the ones of
|
|
264
|
+
* the main listener, they share the storage and count as encrypted
|
|
265
|
+
*
|
|
266
|
+
* @param {String} [defaultHost] Address to listen on when the `tls` option has no host
|
|
267
|
+
* @return {Promise<Number>} resolves with the port of the TLS listener
|
|
268
|
+
*/
|
|
269
|
+
startTlsListener(defaultHost) {
|
|
270
|
+
const { port, host } = this.options.tls || {};
|
|
271
|
+
this.tlsServer = tls.createServer(this.getCredentials(), this.createClient.bind(this));
|
|
272
|
+
return listenOn(this.tlsServer, port, host || defaultHost);
|
|
273
|
+
}
|
|
257
274
|
/**
|
|
258
275
|
* Starts the REST API of the `rest` option, see src/rest.ts
|
|
259
276
|
*
|
|
@@ -303,7 +320,7 @@ class IMAPServer extends Stream {
|
|
|
303
320
|
}
|
|
304
321
|
close(callback) {
|
|
305
322
|
this.closeListeners(true);
|
|
306
|
-
this.
|
|
323
|
+
this.closeImapListeners(callback);
|
|
307
324
|
// close() only completes once all connections are gone
|
|
308
325
|
this.connections.forEach((connection) => {
|
|
309
326
|
if (connection.socket) {
|
|
@@ -311,6 +328,24 @@ class IMAPServer extends Stream {
|
|
|
311
328
|
}
|
|
312
329
|
});
|
|
313
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Stops accepting IMAP connections, on the main listener and on the TLS listener of the `tls` option
|
|
333
|
+
*
|
|
334
|
+
* @param {Function} [callback] Called once both listeners are closed, which is after their last connection is
|
|
335
|
+
* gone, with the first error
|
|
336
|
+
*/
|
|
337
|
+
closeImapListeners(callback) {
|
|
338
|
+
const listeners = this.tlsServer ? [this.server, this.tlsServer] : [this.server];
|
|
339
|
+
this.tlsServer = null;
|
|
340
|
+
let pending = listeners.length;
|
|
341
|
+
let error;
|
|
342
|
+
listeners.forEach(listener => listener.close(err => {
|
|
343
|
+
error = error || err;
|
|
344
|
+
if (--pending === 0) {
|
|
345
|
+
callback?.(error);
|
|
346
|
+
}
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
314
349
|
/**
|
|
315
350
|
* Stops the SMTP and REST listeners of `start()`
|
|
316
351
|
*
|
|
@@ -1111,6 +1146,8 @@ class IMAPServer extends Stream {
|
|
|
1111
1146
|
break;
|
|
1112
1147
|
}
|
|
1113
1148
|
}
|
|
1149
|
+
// like expunge, before the sessions are told: APPEND, COPY, MOVE, REPLACE, SMTP and the control API all add here
|
|
1150
|
+
this.emit('append', mailbox, [message], this.activeConnection);
|
|
1114
1151
|
this.notify({
|
|
1115
1152
|
tag: '*',
|
|
1116
1153
|
attributes: [
|
|
@@ -1554,6 +1591,7 @@ class IMAPConnection {
|
|
|
1554
1591
|
* @param {Function} [callback] Called once the data was handed to the system (a transport: on the next turn)
|
|
1555
1592
|
*/
|
|
1556
1593
|
writeLayer(data, transport, callback) {
|
|
1594
|
+
this.trace('out', data);
|
|
1557
1595
|
if (transport) {
|
|
1558
1596
|
transport.write(data);
|
|
1559
1597
|
if (callback) {
|
|
@@ -1564,6 +1602,17 @@ class IMAPConnection {
|
|
|
1564
1602
|
this.writeRaw(data, callback);
|
|
1565
1603
|
}
|
|
1566
1604
|
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Emits the protocol traffic of the session as `trace` events, as the IMAP layer sees it: above TLS and COMPRESS
|
|
1607
|
+
*
|
|
1608
|
+
* @param {String} direction "in" for client input, "out" for server output
|
|
1609
|
+
* @param {Buffer} data The octets
|
|
1610
|
+
*/
|
|
1611
|
+
trace(direction, data) {
|
|
1612
|
+
if (this.server.listenerCount('trace')) {
|
|
1613
|
+
this.server.emit('trace', { session: this.sessionNumber, direction, data });
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1567
1616
|
/**
|
|
1568
1617
|
* Sends output through the script rule that handles its event, if there is one
|
|
1569
1618
|
*
|
|
@@ -1874,6 +1923,9 @@ class IMAPConnection {
|
|
|
1874
1923
|
if (this._resetting) {
|
|
1875
1924
|
return;
|
|
1876
1925
|
}
|
|
1926
|
+
if (chunk && chunk.length) {
|
|
1927
|
+
this.trace('in', chunk);
|
|
1928
|
+
}
|
|
1877
1929
|
// everything in one read arrived before anything this read causes to be sent
|
|
1878
1930
|
this._readCount = (this._readCount || 0) + 1;
|
|
1879
1931
|
str = (chunk || '').toString('binary');
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -228,6 +228,12 @@ export interface IMAPServerOptions {
|
|
|
228
228
|
plugins?: (string | Plugin)[] | string | Plugin | undefined;
|
|
229
229
|
users?: Record<string, UserData> | undefined;
|
|
230
230
|
secureConnection?: boolean | undefined;
|
|
231
|
+
/** starts a second IMAP listener for implicit TLS (IMAPS) with `server.start()`, its sessions share the storage
|
|
232
|
+
* with the main listener. Without a host it listens on the host given to `start()` */
|
|
233
|
+
tls?: {
|
|
234
|
+
port?: number | undefined;
|
|
235
|
+
host?: string | undefined;
|
|
236
|
+
} | undefined;
|
|
231
237
|
/** starts an SMTP server with `server.start()` that appends every received message to INBOX, needs the optional
|
|
232
238
|
* smtp-server package */
|
|
233
239
|
smtp?: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapkit",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "Scriptable, strictly RFC compliant in-memory IMAP server for testing IMAP clients",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -95,16 +95,16 @@
|
|
|
95
95
|
"@types/smtp-server": "3.5.13",
|
|
96
96
|
"buffer": "6.0.3",
|
|
97
97
|
"esbuild": "0.28.2",
|
|
98
|
-
"eslint": "10.
|
|
98
|
+
"eslint": "10.13.0",
|
|
99
99
|
"eslint-config-prettier": "10.1.8",
|
|
100
100
|
"events": "3.3.0",
|
|
101
101
|
"globals": "17.13.0",
|
|
102
|
-
"imapflow": "2.
|
|
103
|
-
"prettier": "3.9.
|
|
104
|
-
"smtp-server": "3.
|
|
102
|
+
"imapflow": "2.5.6",
|
|
103
|
+
"prettier": "3.9.10",
|
|
104
|
+
"smtp-server": "3.20.2",
|
|
105
105
|
"tsx": "4.23.15",
|
|
106
106
|
"typescript": "6.0.3",
|
|
107
|
-
"typescript-eslint": "8.71.
|
|
107
|
+
"typescript-eslint": "8.71.2"
|
|
108
108
|
},
|
|
109
109
|
"engines": {
|
|
110
110
|
"node": ">=20.0.0"
|