imapflow 2.8.0 → 2.9.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/CHANGELOG.md +12 -0
- package/dist/cjs/commands/compress.js +1 -1
- package/dist/cjs/imap-flow.d.ts +9 -0
- package/dist/cjs/imap-flow.js +59 -26
- package/dist/cjs/package-info.d.ts +1 -1
- package/dist/cjs/package-info.js +1 -1
- package/dist/esm/commands/compress.js +1 -1
- package/dist/esm/imap-flow.d.ts +9 -0
- package/dist/esm/imap-flow.js +59 -26
- package/dist/esm/package-info.d.ts +1 -1
- package/dist/esm/package-info.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.9.0](https://github.com/postalsys/imapflow/compare/v2.8.0...v2.9.0) (2026-10-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* closedByClient tells a close the application asked for from one the server made, and auto-IDLE never waits longer than maxIdleTime ([5339c12](https://github.com/postalsys/imapflow/commit/5339c12895a759621b2e30bbb07521dba7b39929))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* logout() marks the close as the application's before the LOGOUT exchange, and closeAfter() is tested ([03602d2](https://github.com/postalsys/imapflow/commit/03602d2afa2dc8e243735ad6ede8403b733ed4b0))
|
|
14
|
+
|
|
3
15
|
## [2.8.0](https://github.com/postalsys/imapflow/compare/v2.7.0...v2.8.0) (2026-10-21)
|
|
4
16
|
|
|
5
17
|
|
|
@@ -45,7 +45,7 @@ async function compress(connection) {
|
|
|
45
45
|
// STARTTLS guard does (starttls.ts records a flag, upgradeToSTARTTLS decides):
|
|
46
46
|
// only the command module holds the response before its backpressure release,
|
|
47
47
|
// so only it can order teardown ahead of that release.
|
|
48
|
-
connection.
|
|
48
|
+
connection.closeConnectionAfter();
|
|
49
49
|
response.next();
|
|
50
50
|
throw error;
|
|
51
51
|
}
|
package/dist/cjs/imap-flow.d.ts
CHANGED
|
@@ -157,8 +157,17 @@ export declare class ImapFlow extends EventEmitter {
|
|
|
157
157
|
* what tells a server dropping an idle session from one dropping a session mid-command
|
|
158
158
|
*/
|
|
159
159
|
idlingAtClose: boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Whether the application ended the connection (`close()`, `closeAfter()`, `logout()`, disposing
|
|
162
|
+
* the client), `false` while it is open and when the server, the network or an error ended it.
|
|
163
|
+
* Tells a session the server dropped from one the application closed, also for an application
|
|
164
|
+
* that closes a connection only to open a new one
|
|
165
|
+
*/
|
|
166
|
+
closedByClient: boolean;
|
|
160
167
|
/** Whether log entries are also emitted as 'log' events, see the `emitLogs` option */
|
|
161
168
|
emitLogs: boolean;
|
|
169
|
+
/** How often IDLE is restarted, in milliseconds (the `maxIdleTime` option), `false` for never */
|
|
170
|
+
maxIdleTime: number | false;
|
|
162
171
|
/** Hash algorithm for the fallback message id, see `ImapFlowOptions.idHashAlgorithm` */
|
|
163
172
|
idHashAlgorithm: string;
|
|
164
173
|
/** The personal namespace, from the NAMESPACE command */
|
package/dist/cjs/imap-flow.js
CHANGED
|
@@ -304,6 +304,8 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
304
304
|
this.lastClientWrite = null;
|
|
305
305
|
this._idleWaiters = [];
|
|
306
306
|
this.idlingAtClose = false;
|
|
307
|
+
this.closedByClient = false;
|
|
308
|
+
this._closePending = false;
|
|
307
309
|
this.emitLogs = !!this.options.emitLogs;
|
|
308
310
|
this.lo = 0;
|
|
309
311
|
this.untaggedHandlers = {};
|
|
@@ -315,6 +317,11 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
315
317
|
this.idRequested = false;
|
|
316
318
|
this.maxIdleTime = this.options.maxIdleTime || false;
|
|
317
319
|
this.autoIdleDelay = normalizeAutoIdleDelay(this.options.autoIdleDelay, this.socketTimeout, this.log, this.id);
|
|
320
|
+
// A server that needs IDLE restarted this often can cut the silence before the first IDLE just
|
|
321
|
+
// as well: the connection is never left quiet for longer than maxIdleTime
|
|
322
|
+
if (this.maxIdleTime && this.autoIdleDelay > this.maxIdleTime) {
|
|
323
|
+
this.autoIdleDelay = this.maxIdleTime;
|
|
324
|
+
}
|
|
318
325
|
this._lastPollAt = 0;
|
|
319
326
|
this._openDownloads = 0;
|
|
320
327
|
this._recoveryPending = false;
|
|
@@ -334,7 +341,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
334
341
|
// than emitting an error event to the caller.
|
|
335
342
|
this._streamerErrorHandler = (err) => {
|
|
336
343
|
if (['Z_BUF_ERROR', 'ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EHOSTUNREACH'].includes(err.code)) {
|
|
337
|
-
this.
|
|
344
|
+
this.closeConnectionAfter();
|
|
338
345
|
return;
|
|
339
346
|
}
|
|
340
347
|
this.log.error({ err, cid: this.id });
|
|
@@ -355,7 +362,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
355
362
|
// only awaited connect(), as an unhandled 'error' that throws.
|
|
356
363
|
if (this._errorReported) {
|
|
357
364
|
this.log.debug({ msg: 'Suppressed error after the connection already reported one', err, cid: this.id });
|
|
358
|
-
this.
|
|
365
|
+
this.closeConnectionAfter();
|
|
359
366
|
return;
|
|
360
367
|
}
|
|
361
368
|
// During a STARTTLS handshake the upgrade owns the single error path (its settle()
|
|
@@ -373,7 +380,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
373
380
|
return;
|
|
374
381
|
}
|
|
375
382
|
this.upgrading = false;
|
|
376
|
-
this.
|
|
383
|
+
this.closeConnectionAfter();
|
|
377
384
|
return;
|
|
378
385
|
}
|
|
379
386
|
// While the initial connect promise is still pending it owns error reporting:
|
|
@@ -384,12 +391,12 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
384
391
|
this.initialResolve = false;
|
|
385
392
|
this.initialReject = false;
|
|
386
393
|
this._errorReported = true;
|
|
387
|
-
this.
|
|
394
|
+
this.closeConnectionAfter();
|
|
388
395
|
reject(err);
|
|
389
396
|
return;
|
|
390
397
|
}
|
|
391
398
|
this._errorReported = true;
|
|
392
|
-
this.
|
|
399
|
+
this.closeConnectionAfter();
|
|
393
400
|
this.emit('error', err);
|
|
394
401
|
}
|
|
395
402
|
/** @internal */
|
|
@@ -415,7 +422,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
415
422
|
}
|
|
416
423
|
if (this.writeSocket.destroyed) {
|
|
417
424
|
this.log.error({ msg: 'Write socket destroyed', cid: this.id });
|
|
418
|
-
this.
|
|
425
|
+
this.closeConnection();
|
|
419
426
|
return;
|
|
420
427
|
}
|
|
421
428
|
// Append CRLF only to the final part of a command. When sending literals,
|
|
@@ -1265,8 +1272,8 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1265
1272
|
this.log.error({ err, cid: this.id });
|
|
1266
1273
|
this.emitError(err);
|
|
1267
1274
|
});
|
|
1268
|
-
this._socketClose = this._socketClose || (() => this.
|
|
1269
|
-
this._socketEnd = this._socketEnd || (() => this.
|
|
1275
|
+
this._socketClose = this._socketClose || (() => this.closeConnection());
|
|
1276
|
+
this._socketEnd = this._socketEnd || (() => this.closeConnection());
|
|
1270
1277
|
/**
|
|
1271
1278
|
* Socket timeout event handler.
|
|
1272
1279
|
*
|
|
@@ -1320,7 +1327,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1320
1327
|
.catch(err => {
|
|
1321
1328
|
this.log.warn({ msg: 'Connection recovery failed after timeout', err, cid: this.id });
|
|
1322
1329
|
if (!this.isClosed) {
|
|
1323
|
-
this.
|
|
1330
|
+
this.closeConnection();
|
|
1324
1331
|
}
|
|
1325
1332
|
})
|
|
1326
1333
|
.finally(() => {
|
|
@@ -1631,7 +1638,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1631
1638
|
// is rejected there instead (with a generic TLS error rather than STARTTLS_INJECTION).
|
|
1632
1639
|
const failSTARTTLSInjection = () => {
|
|
1633
1640
|
let err = (0, errors_js_1.createImapError)('Server sent data after the STARTTLS response and before the TLS handshake; possible plaintext-injection attack', 'STARTTLS_INJECTION', { tlsFailed: true });
|
|
1634
|
-
this.
|
|
1641
|
+
this.closeConnectionAfter();
|
|
1635
1642
|
return err;
|
|
1636
1643
|
};
|
|
1637
1644
|
// Check 1: the parser saw more input already buffered right after the tagged OK
|
|
@@ -1699,7 +1706,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1699
1706
|
// Preserve the original error, marked as a TLS failure so callers can tell
|
|
1700
1707
|
// an upgrade failure from an ordinary command failure.
|
|
1701
1708
|
err.tlsFailed = true;
|
|
1702
|
-
this.
|
|
1709
|
+
this.closeConnectionAfter();
|
|
1703
1710
|
return reject(err);
|
|
1704
1711
|
}
|
|
1705
1712
|
resolve(result);
|
|
@@ -1889,7 +1896,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1889
1896
|
.catch(err => {
|
|
1890
1897
|
// The transport goes with the failed attempt: the instance can not be reused, and
|
|
1891
1898
|
// an open socket would only trip the inactivity watchdog later
|
|
1892
|
-
this.
|
|
1899
|
+
this.closeConnectionAfter();
|
|
1893
1900
|
if (typeof this.initialReject === 'function') {
|
|
1894
1901
|
this.log.error({ err, cid: this.id });
|
|
1895
1902
|
(0, tools_js_1.clearTimer)(this.greetingTimeout);
|
|
@@ -1939,7 +1946,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1939
1946
|
// that answers a LOGOUT is the exception: its tagged completion closes the connection.
|
|
1940
1947
|
const answersLogout = !!this.currentRequest && this.currentRequest.command.toUpperCase() === 'LOGOUT';
|
|
1941
1948
|
if (!this.greetingReceived || !answersLogout) {
|
|
1942
|
-
this.
|
|
1949
|
+
this.closeConnectionAfter();
|
|
1943
1950
|
}
|
|
1944
1951
|
}
|
|
1945
1952
|
// Drops every capability-derived field together - the counterpart of
|
|
@@ -2188,10 +2195,15 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2188
2195
|
connectionBusy() {
|
|
2189
2196
|
return !!(this.currentLock || this.locks.length || this.currentRequest || this.requestQueue.length || this._openDownloads);
|
|
2190
2197
|
}
|
|
2198
|
+
// Settles every idle({ whenFree: true }) call waiting for the connection: resolved to re-check
|
|
2199
|
+
// it, or rejected when the connection is gone
|
|
2191
2200
|
/** @internal */
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2201
|
+
settleIdleWaiters(err) {
|
|
2202
|
+
for (let { resolve, reject } of this._idleWaiters.splice(0)) {
|
|
2203
|
+
if (err) {
|
|
2204
|
+
reject(err);
|
|
2205
|
+
}
|
|
2206
|
+
else {
|
|
2195
2207
|
resolve();
|
|
2196
2208
|
}
|
|
2197
2209
|
}
|
|
@@ -2205,7 +2217,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2205
2217
|
autoidle() {
|
|
2206
2218
|
// every place the connection may have become free calls this, so it is where waiting
|
|
2207
2219
|
// idle({ whenFree: true }) calls re-check it, also with auto-IDLE turned off
|
|
2208
|
-
this.
|
|
2220
|
+
this.settleIdleWaiters();
|
|
2209
2221
|
(0, tools_js_1.clearTimer)(this.idleStartTimer);
|
|
2210
2222
|
if (this.options.disableAutoIdle || !this.usable || this.state !== this.states.SELECTED) {
|
|
2211
2223
|
return;
|
|
@@ -2313,7 +2325,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2313
2325
|
this.connectTimeout = setTimeout(() => {
|
|
2314
2326
|
let err = deadline.error();
|
|
2315
2327
|
this.log.error({ err, cid: this.id });
|
|
2316
|
-
this.
|
|
2328
|
+
this.closeConnectionAfter();
|
|
2317
2329
|
reject(err);
|
|
2318
2330
|
}, deadline.remaining());
|
|
2319
2331
|
let onConnect = () => {
|
|
@@ -2340,7 +2352,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2340
2352
|
}
|
|
2341
2353
|
});
|
|
2342
2354
|
this.log.error({ err, cid: this.id });
|
|
2343
|
-
this.
|
|
2355
|
+
this.closeConnectionAfter();
|
|
2344
2356
|
reject(err);
|
|
2345
2357
|
}, this.options.greetingTimeout || GREETING_TIMEOUT);
|
|
2346
2358
|
const connected = this.socket;
|
|
@@ -2394,7 +2406,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2394
2406
|
catch (err) {
|
|
2395
2407
|
// refused up front: the connection deadline must not stay armed
|
|
2396
2408
|
(0, tools_js_1.clearTimer)(this.connectTimeout);
|
|
2397
|
-
this.
|
|
2409
|
+
this.closeConnectionAfter();
|
|
2398
2410
|
this.log.error({ err, cid: this.id });
|
|
2399
2411
|
reject(err);
|
|
2400
2412
|
return;
|
|
@@ -2408,7 +2420,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2408
2420
|
this._connectErrorHandler = (err) => {
|
|
2409
2421
|
(0, tools_js_1.clearTimer)(this.connectTimeout);
|
|
2410
2422
|
(0, tools_js_1.clearTimer)(this.greetingTimeout);
|
|
2411
|
-
this.
|
|
2423
|
+
this.closeConnectionAfter();
|
|
2412
2424
|
this.log.error({ err, cid: this.id });
|
|
2413
2425
|
reject(err);
|
|
2414
2426
|
};
|
|
@@ -2426,6 +2438,9 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2426
2438
|
* await client.logout();
|
|
2427
2439
|
*/
|
|
2428
2440
|
async logout() {
|
|
2441
|
+
// marked before the exchange: a server may answer with BYE and drop the socket before the
|
|
2442
|
+
// tagged OK, which closes the connection from the socket's side first
|
|
2443
|
+
this.markClosedByClient();
|
|
2429
2444
|
return await this.run('LOGOUT');
|
|
2430
2445
|
}
|
|
2431
2446
|
/**
|
|
@@ -2434,7 +2449,21 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2434
2449
|
* caller function to proceed, and run `close()` function afterwards.
|
|
2435
2450
|
*/
|
|
2436
2451
|
closeAfter() {
|
|
2437
|
-
|
|
2452
|
+
this.markClosedByClient();
|
|
2453
|
+
this.closeConnectionAfter();
|
|
2454
|
+
}
|
|
2455
|
+
// The deferred close ImapFlow itself starts (a server BYE, an error), which closedByClient leaves alone
|
|
2456
|
+
/** @internal */
|
|
2457
|
+
closeConnectionAfter() {
|
|
2458
|
+
this._closePending = true;
|
|
2459
|
+
setImmediate(() => this.closeConnection());
|
|
2460
|
+
}
|
|
2461
|
+
// The application asked for the close, unless the connection is already closed or closing
|
|
2462
|
+
/** @internal */
|
|
2463
|
+
markClosedByClient() {
|
|
2464
|
+
if (!this.isClosed && !this._closePending) {
|
|
2465
|
+
this.closedByClient = true;
|
|
2466
|
+
}
|
|
2438
2467
|
}
|
|
2439
2468
|
// Connection-scoped wrapper around the shared stamping helper; see buildConnectionError().
|
|
2440
2469
|
/** @internal */
|
|
@@ -2470,6 +2499,13 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2470
2499
|
* client.close();
|
|
2471
2500
|
*/
|
|
2472
2501
|
close() {
|
|
2502
|
+
this.markClosedByClient();
|
|
2503
|
+
this.closeConnection();
|
|
2504
|
+
}
|
|
2505
|
+
// The close itself, for the application's close() and for the closes ImapFlow starts on its own
|
|
2506
|
+
// (the socket ending, a write to a destroyed socket, a failed timeout recovery)
|
|
2507
|
+
/** @internal */
|
|
2508
|
+
closeConnection() {
|
|
2473
2509
|
try {
|
|
2474
2510
|
this.closeTimers();
|
|
2475
2511
|
this.usable = false;
|
|
@@ -2514,10 +2550,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2514
2550
|
this.closeRequests();
|
|
2515
2551
|
this.closeLocks();
|
|
2516
2552
|
if (this._idleWaiters.length) {
|
|
2517
|
-
|
|
2518
|
-
for (let { reject } of this._idleWaiters.splice(0)) {
|
|
2519
|
-
reject(waiterError);
|
|
2520
|
-
}
|
|
2553
|
+
this.settleIdleWaiters(this.createNoConnectionError(this.byeReason, { rejectedFrom: 'idleWait', command: 'IDLE' }));
|
|
2521
2554
|
}
|
|
2522
2555
|
this.closeStreams();
|
|
2523
2556
|
// clear socket handlers
|
package/dist/cjs/package-info.js
CHANGED
|
@@ -42,7 +42,7 @@ export default async function compress(connection) {
|
|
|
42
42
|
// STARTTLS guard does (starttls.ts records a flag, upgradeToSTARTTLS decides):
|
|
43
43
|
// only the command module holds the response before its backpressure release,
|
|
44
44
|
// so only it can order teardown ahead of that release.
|
|
45
|
-
connection.
|
|
45
|
+
connection.closeConnectionAfter();
|
|
46
46
|
response.next();
|
|
47
47
|
throw error;
|
|
48
48
|
}
|
package/dist/esm/imap-flow.d.ts
CHANGED
|
@@ -157,8 +157,17 @@ export declare class ImapFlow extends EventEmitter {
|
|
|
157
157
|
* what tells a server dropping an idle session from one dropping a session mid-command
|
|
158
158
|
*/
|
|
159
159
|
idlingAtClose: boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Whether the application ended the connection (`close()`, `closeAfter()`, `logout()`, disposing
|
|
162
|
+
* the client), `false` while it is open and when the server, the network or an error ended it.
|
|
163
|
+
* Tells a session the server dropped from one the application closed, also for an application
|
|
164
|
+
* that closes a connection only to open a new one
|
|
165
|
+
*/
|
|
166
|
+
closedByClient: boolean;
|
|
160
167
|
/** Whether log entries are also emitted as 'log' events, see the `emitLogs` option */
|
|
161
168
|
emitLogs: boolean;
|
|
169
|
+
/** How often IDLE is restarted, in milliseconds (the `maxIdleTime` option), `false` for never */
|
|
170
|
+
maxIdleTime: number | false;
|
|
162
171
|
/** Hash algorithm for the fallback message id, see `ImapFlowOptions.idHashAlgorithm` */
|
|
163
172
|
idHashAlgorithm: string;
|
|
164
173
|
/** The personal namespace, from the NAMESPACE command */
|
package/dist/esm/imap-flow.js
CHANGED
|
@@ -261,6 +261,8 @@ export class ImapFlow extends EventEmitter {
|
|
|
261
261
|
this.lastClientWrite = null;
|
|
262
262
|
this._idleWaiters = [];
|
|
263
263
|
this.idlingAtClose = false;
|
|
264
|
+
this.closedByClient = false;
|
|
265
|
+
this._closePending = false;
|
|
264
266
|
this.emitLogs = !!this.options.emitLogs;
|
|
265
267
|
this.lo = 0;
|
|
266
268
|
this.untaggedHandlers = {};
|
|
@@ -272,6 +274,11 @@ export class ImapFlow extends EventEmitter {
|
|
|
272
274
|
this.idRequested = false;
|
|
273
275
|
this.maxIdleTime = this.options.maxIdleTime || false;
|
|
274
276
|
this.autoIdleDelay = normalizeAutoIdleDelay(this.options.autoIdleDelay, this.socketTimeout, this.log, this.id);
|
|
277
|
+
// A server that needs IDLE restarted this often can cut the silence before the first IDLE just
|
|
278
|
+
// as well: the connection is never left quiet for longer than maxIdleTime
|
|
279
|
+
if (this.maxIdleTime && this.autoIdleDelay > this.maxIdleTime) {
|
|
280
|
+
this.autoIdleDelay = this.maxIdleTime;
|
|
281
|
+
}
|
|
275
282
|
this._lastPollAt = 0;
|
|
276
283
|
this._openDownloads = 0;
|
|
277
284
|
this._recoveryPending = false;
|
|
@@ -291,7 +298,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
291
298
|
// than emitting an error event to the caller.
|
|
292
299
|
this._streamerErrorHandler = (err) => {
|
|
293
300
|
if (['Z_BUF_ERROR', 'ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EHOSTUNREACH'].includes(err.code)) {
|
|
294
|
-
this.
|
|
301
|
+
this.closeConnectionAfter();
|
|
295
302
|
return;
|
|
296
303
|
}
|
|
297
304
|
this.log.error({ err, cid: this.id });
|
|
@@ -312,7 +319,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
312
319
|
// only awaited connect(), as an unhandled 'error' that throws.
|
|
313
320
|
if (this._errorReported) {
|
|
314
321
|
this.log.debug({ msg: 'Suppressed error after the connection already reported one', err, cid: this.id });
|
|
315
|
-
this.
|
|
322
|
+
this.closeConnectionAfter();
|
|
316
323
|
return;
|
|
317
324
|
}
|
|
318
325
|
// During a STARTTLS handshake the upgrade owns the single error path (its settle()
|
|
@@ -330,7 +337,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
330
337
|
return;
|
|
331
338
|
}
|
|
332
339
|
this.upgrading = false;
|
|
333
|
-
this.
|
|
340
|
+
this.closeConnectionAfter();
|
|
334
341
|
return;
|
|
335
342
|
}
|
|
336
343
|
// While the initial connect promise is still pending it owns error reporting:
|
|
@@ -341,12 +348,12 @@ export class ImapFlow extends EventEmitter {
|
|
|
341
348
|
this.initialResolve = false;
|
|
342
349
|
this.initialReject = false;
|
|
343
350
|
this._errorReported = true;
|
|
344
|
-
this.
|
|
351
|
+
this.closeConnectionAfter();
|
|
345
352
|
reject(err);
|
|
346
353
|
return;
|
|
347
354
|
}
|
|
348
355
|
this._errorReported = true;
|
|
349
|
-
this.
|
|
356
|
+
this.closeConnectionAfter();
|
|
350
357
|
this.emit('error', err);
|
|
351
358
|
}
|
|
352
359
|
/** @internal */
|
|
@@ -372,7 +379,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
372
379
|
}
|
|
373
380
|
if (this.writeSocket.destroyed) {
|
|
374
381
|
this.log.error({ msg: 'Write socket destroyed', cid: this.id });
|
|
375
|
-
this.
|
|
382
|
+
this.closeConnection();
|
|
376
383
|
return;
|
|
377
384
|
}
|
|
378
385
|
// Append CRLF only to the final part of a command. When sending literals,
|
|
@@ -1222,8 +1229,8 @@ export class ImapFlow extends EventEmitter {
|
|
|
1222
1229
|
this.log.error({ err, cid: this.id });
|
|
1223
1230
|
this.emitError(err);
|
|
1224
1231
|
});
|
|
1225
|
-
this._socketClose = this._socketClose || (() => this.
|
|
1226
|
-
this._socketEnd = this._socketEnd || (() => this.
|
|
1232
|
+
this._socketClose = this._socketClose || (() => this.closeConnection());
|
|
1233
|
+
this._socketEnd = this._socketEnd || (() => this.closeConnection());
|
|
1227
1234
|
/**
|
|
1228
1235
|
* Socket timeout event handler.
|
|
1229
1236
|
*
|
|
@@ -1277,7 +1284,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1277
1284
|
.catch(err => {
|
|
1278
1285
|
this.log.warn({ msg: 'Connection recovery failed after timeout', err, cid: this.id });
|
|
1279
1286
|
if (!this.isClosed) {
|
|
1280
|
-
this.
|
|
1287
|
+
this.closeConnection();
|
|
1281
1288
|
}
|
|
1282
1289
|
})
|
|
1283
1290
|
.finally(() => {
|
|
@@ -1588,7 +1595,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1588
1595
|
// is rejected there instead (with a generic TLS error rather than STARTTLS_INJECTION).
|
|
1589
1596
|
const failSTARTTLSInjection = () => {
|
|
1590
1597
|
let err = createImapError('Server sent data after the STARTTLS response and before the TLS handshake; possible plaintext-injection attack', 'STARTTLS_INJECTION', { tlsFailed: true });
|
|
1591
|
-
this.
|
|
1598
|
+
this.closeConnectionAfter();
|
|
1592
1599
|
return err;
|
|
1593
1600
|
};
|
|
1594
1601
|
// Check 1: the parser saw more input already buffered right after the tagged OK
|
|
@@ -1656,7 +1663,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1656
1663
|
// Preserve the original error, marked as a TLS failure so callers can tell
|
|
1657
1664
|
// an upgrade failure from an ordinary command failure.
|
|
1658
1665
|
err.tlsFailed = true;
|
|
1659
|
-
this.
|
|
1666
|
+
this.closeConnectionAfter();
|
|
1660
1667
|
return reject(err);
|
|
1661
1668
|
}
|
|
1662
1669
|
resolve(result);
|
|
@@ -1846,7 +1853,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1846
1853
|
.catch(err => {
|
|
1847
1854
|
// The transport goes with the failed attempt: the instance can not be reused, and
|
|
1848
1855
|
// an open socket would only trip the inactivity watchdog later
|
|
1849
|
-
this.
|
|
1856
|
+
this.closeConnectionAfter();
|
|
1850
1857
|
if (typeof this.initialReject === 'function') {
|
|
1851
1858
|
this.log.error({ err, cid: this.id });
|
|
1852
1859
|
clearTimer(this.greetingTimeout);
|
|
@@ -1896,7 +1903,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1896
1903
|
// that answers a LOGOUT is the exception: its tagged completion closes the connection.
|
|
1897
1904
|
const answersLogout = !!this.currentRequest && this.currentRequest.command.toUpperCase() === 'LOGOUT';
|
|
1898
1905
|
if (!this.greetingReceived || !answersLogout) {
|
|
1899
|
-
this.
|
|
1906
|
+
this.closeConnectionAfter();
|
|
1900
1907
|
}
|
|
1901
1908
|
}
|
|
1902
1909
|
// Drops every capability-derived field together - the counterpart of
|
|
@@ -2145,10 +2152,15 @@ export class ImapFlow extends EventEmitter {
|
|
|
2145
2152
|
connectionBusy() {
|
|
2146
2153
|
return !!(this.currentLock || this.locks.length || this.currentRequest || this.requestQueue.length || this._openDownloads);
|
|
2147
2154
|
}
|
|
2155
|
+
// Settles every idle({ whenFree: true }) call waiting for the connection: resolved to re-check
|
|
2156
|
+
// it, or rejected when the connection is gone
|
|
2148
2157
|
/** @internal */
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2158
|
+
settleIdleWaiters(err) {
|
|
2159
|
+
for (let { resolve, reject } of this._idleWaiters.splice(0)) {
|
|
2160
|
+
if (err) {
|
|
2161
|
+
reject(err);
|
|
2162
|
+
}
|
|
2163
|
+
else {
|
|
2152
2164
|
resolve();
|
|
2153
2165
|
}
|
|
2154
2166
|
}
|
|
@@ -2162,7 +2174,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
2162
2174
|
autoidle() {
|
|
2163
2175
|
// every place the connection may have become free calls this, so it is where waiting
|
|
2164
2176
|
// idle({ whenFree: true }) calls re-check it, also with auto-IDLE turned off
|
|
2165
|
-
this.
|
|
2177
|
+
this.settleIdleWaiters();
|
|
2166
2178
|
clearTimer(this.idleStartTimer);
|
|
2167
2179
|
if (this.options.disableAutoIdle || !this.usable || this.state !== this.states.SELECTED) {
|
|
2168
2180
|
return;
|
|
@@ -2270,7 +2282,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
2270
2282
|
this.connectTimeout = setTimeout(() => {
|
|
2271
2283
|
let err = deadline.error();
|
|
2272
2284
|
this.log.error({ err, cid: this.id });
|
|
2273
|
-
this.
|
|
2285
|
+
this.closeConnectionAfter();
|
|
2274
2286
|
reject(err);
|
|
2275
2287
|
}, deadline.remaining());
|
|
2276
2288
|
let onConnect = () => {
|
|
@@ -2297,7 +2309,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
2297
2309
|
}
|
|
2298
2310
|
});
|
|
2299
2311
|
this.log.error({ err, cid: this.id });
|
|
2300
|
-
this.
|
|
2312
|
+
this.closeConnectionAfter();
|
|
2301
2313
|
reject(err);
|
|
2302
2314
|
}, this.options.greetingTimeout || GREETING_TIMEOUT);
|
|
2303
2315
|
const connected = this.socket;
|
|
@@ -2351,7 +2363,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
2351
2363
|
catch (err) {
|
|
2352
2364
|
// refused up front: the connection deadline must not stay armed
|
|
2353
2365
|
clearTimer(this.connectTimeout);
|
|
2354
|
-
this.
|
|
2366
|
+
this.closeConnectionAfter();
|
|
2355
2367
|
this.log.error({ err, cid: this.id });
|
|
2356
2368
|
reject(err);
|
|
2357
2369
|
return;
|
|
@@ -2365,7 +2377,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
2365
2377
|
this._connectErrorHandler = (err) => {
|
|
2366
2378
|
clearTimer(this.connectTimeout);
|
|
2367
2379
|
clearTimer(this.greetingTimeout);
|
|
2368
|
-
this.
|
|
2380
|
+
this.closeConnectionAfter();
|
|
2369
2381
|
this.log.error({ err, cid: this.id });
|
|
2370
2382
|
reject(err);
|
|
2371
2383
|
};
|
|
@@ -2383,6 +2395,9 @@ export class ImapFlow extends EventEmitter {
|
|
|
2383
2395
|
* await client.logout();
|
|
2384
2396
|
*/
|
|
2385
2397
|
async logout() {
|
|
2398
|
+
// marked before the exchange: a server may answer with BYE and drop the socket before the
|
|
2399
|
+
// tagged OK, which closes the connection from the socket's side first
|
|
2400
|
+
this.markClosedByClient();
|
|
2386
2401
|
return await this.run('LOGOUT');
|
|
2387
2402
|
}
|
|
2388
2403
|
/**
|
|
@@ -2391,7 +2406,21 @@ export class ImapFlow extends EventEmitter {
|
|
|
2391
2406
|
* caller function to proceed, and run `close()` function afterwards.
|
|
2392
2407
|
*/
|
|
2393
2408
|
closeAfter() {
|
|
2394
|
-
|
|
2409
|
+
this.markClosedByClient();
|
|
2410
|
+
this.closeConnectionAfter();
|
|
2411
|
+
}
|
|
2412
|
+
// The deferred close ImapFlow itself starts (a server BYE, an error), which closedByClient leaves alone
|
|
2413
|
+
/** @internal */
|
|
2414
|
+
closeConnectionAfter() {
|
|
2415
|
+
this._closePending = true;
|
|
2416
|
+
setImmediate(() => this.closeConnection());
|
|
2417
|
+
}
|
|
2418
|
+
// The application asked for the close, unless the connection is already closed or closing
|
|
2419
|
+
/** @internal */
|
|
2420
|
+
markClosedByClient() {
|
|
2421
|
+
if (!this.isClosed && !this._closePending) {
|
|
2422
|
+
this.closedByClient = true;
|
|
2423
|
+
}
|
|
2395
2424
|
}
|
|
2396
2425
|
// Connection-scoped wrapper around the shared stamping helper; see buildConnectionError().
|
|
2397
2426
|
/** @internal */
|
|
@@ -2427,6 +2456,13 @@ export class ImapFlow extends EventEmitter {
|
|
|
2427
2456
|
* client.close();
|
|
2428
2457
|
*/
|
|
2429
2458
|
close() {
|
|
2459
|
+
this.markClosedByClient();
|
|
2460
|
+
this.closeConnection();
|
|
2461
|
+
}
|
|
2462
|
+
// The close itself, for the application's close() and for the closes ImapFlow starts on its own
|
|
2463
|
+
// (the socket ending, a write to a destroyed socket, a failed timeout recovery)
|
|
2464
|
+
/** @internal */
|
|
2465
|
+
closeConnection() {
|
|
2430
2466
|
try {
|
|
2431
2467
|
this.closeTimers();
|
|
2432
2468
|
this.usable = false;
|
|
@@ -2471,10 +2507,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
2471
2507
|
this.closeRequests();
|
|
2472
2508
|
this.closeLocks();
|
|
2473
2509
|
if (this._idleWaiters.length) {
|
|
2474
|
-
|
|
2475
|
-
for (let { reject } of this._idleWaiters.splice(0)) {
|
|
2476
|
-
reject(waiterError);
|
|
2477
|
-
}
|
|
2510
|
+
this.settleIdleWaiters(this.createNoConnectionError(this.byeReason, { rejectedFrom: 'idleWait', command: 'IDLE' }));
|
|
2478
2511
|
}
|
|
2479
2512
|
this.closeStreams();
|
|
2480
2513
|
// clear socket handlers
|
package/dist/esm/package-info.js
CHANGED