imapflow 2.7.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 +19 -0
- package/dist/cjs/commands/compress.js +1 -1
- package/dist/cjs/imap-flow.d.ts +14 -3
- package/dist/cjs/imap-flow.js +79 -20
- package/dist/cjs/package-info.d.ts +1 -1
- package/dist/cjs/package-info.js +1 -1
- package/dist/cjs/types.d.ts +10 -0
- package/dist/esm/commands/compress.js +1 -1
- package/dist/esm/imap-flow.d.ts +14 -3
- package/dist/esm/imap-flow.js +79 -20
- package/dist/esm/package-info.d.ts +1 -1
- package/dist/esm/package-info.js +1 -1
- package/dist/esm/types.d.ts +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
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
|
+
|
|
15
|
+
## [2.8.0](https://github.com/postalsys/imapflow/compare/v2.7.0...v2.8.0) (2026-10-21)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* idle({ whenFree: true }) waits for the connection to be free before starting IDLE ([791c24c](https://github.com/postalsys/imapflow/commit/791c24ca607b60585ef619269abb0aa1f400f525))
|
|
21
|
+
|
|
3
22
|
## [2.7.0](https://github.com/postalsys/imapflow/compare/v2.6.0...v2.7.0) (2026-10-21)
|
|
4
23
|
|
|
5
24
|
|
|
@@ -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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { EventEmitter } from 'node:events';
|
|
5
5
|
import { PassThrough, type Readable } from 'node:stream';
|
|
6
6
|
import { AuthenticationFailure } from './errors.js';
|
|
7
|
-
import type { AppendResponseObject, CopyResponseObject, DownloadManyOptions, DownloadManyResult, DownloadObject, DownloadNotFound, DownloadOptions, ESearchResult, FetchMessageObject, FetchOptions, FetchQueryObject, IdInfoObject, ImapFlowEvents, ImapFlowOptions, InternalLogger, ListOptions, ListResponse, ListTreeResponse, MailboxCreateResponse, MailboxDeleteResponse, MailboxLockObject, MailboxLockOptions, MailboxObject, MailboxOpenOptions, MailboxRenameResponse, MessageRange, MessageRangeOptions, NamespaceObject, NamespacesObject, QuotaResponse, SearchObject, SearchOptions, SearchReturnOption, SequenceString, StatusObject, StatusQuery, StoreOptions, TlsInfo, ImapTransportSocket } from './types.js';
|
|
7
|
+
import type { AppendResponseObject, CopyResponseObject, DownloadManyOptions, DownloadManyResult, DownloadObject, DownloadNotFound, DownloadOptions, ESearchResult, FetchMessageObject, FetchOptions, FetchQueryObject, IdInfoObject, ImapFlowEvents, ImapFlowOptions, InternalLogger, ListOptions, ListResponse, ListTreeResponse, MailboxCreateResponse, MailboxDeleteResponse, MailboxLockObject, MailboxLockOptions, IdleOptions, MailboxObject, MailboxOpenOptions, MailboxRenameResponse, MessageRange, MessageRangeOptions, NamespaceObject, NamespacesObject, QuotaResponse, SearchObject, SearchOptions, SearchReturnOption, SequenceString, StatusObject, StatusQuery, StoreOptions, TlsInfo, ImapTransportSocket } from './types.js';
|
|
8
8
|
export type * from './types.js';
|
|
9
9
|
export type { ImapFlowError } from './errors.js';
|
|
10
10
|
export { AuthenticationFailure, ImapFlowErrorCode } from './errors.js';
|
|
@@ -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 */
|
|
@@ -378,8 +387,10 @@ export declare class ImapFlow extends EventEmitter {
|
|
|
378
387
|
* return until IDLE is finished which means you would have to call some other command out of scope.
|
|
379
388
|
*
|
|
380
389
|
* Unlike auto-IDLE, a direct call does not wait for the connection to be free: a mailbox lock
|
|
381
|
-
* holder may idle inside its own lock, and idle() can not tell it from another caller.
|
|
390
|
+
* holder may idle inside its own lock, and idle() can not tell it from another caller. Code that
|
|
391
|
+
* holds no lock passes `{ whenFree: true }` to wait for the connection to be free first.
|
|
382
392
|
*
|
|
393
|
+
* @param options IDLE options
|
|
383
394
|
* @returns `false` if IDLE failed, `undefined` otherwise
|
|
384
395
|
*
|
|
385
396
|
* @example
|
|
@@ -387,7 +398,7 @@ export declare class ImapFlow extends EventEmitter {
|
|
|
387
398
|
*
|
|
388
399
|
* await client.idle();
|
|
389
400
|
*/
|
|
390
|
-
idle(): Promise<boolean | undefined>;
|
|
401
|
+
idle(options?: IdleOptions | undefined): Promise<boolean | undefined>;
|
|
391
402
|
/**
|
|
392
403
|
* Sets flags for a message or message range
|
|
393
404
|
*
|
package/dist/cjs/imap-flow.js
CHANGED
|
@@ -302,7 +302,10 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
302
302
|
this.context = this.options.context;
|
|
303
303
|
this.connectedAt = null;
|
|
304
304
|
this.lastClientWrite = null;
|
|
305
|
+
this._idleWaiters = [];
|
|
305
306
|
this.idlingAtClose = false;
|
|
307
|
+
this.closedByClient = false;
|
|
308
|
+
this._closePending = false;
|
|
306
309
|
this.emitLogs = !!this.options.emitLogs;
|
|
307
310
|
this.lo = 0;
|
|
308
311
|
this.untaggedHandlers = {};
|
|
@@ -314,6 +317,11 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
314
317
|
this.idRequested = false;
|
|
315
318
|
this.maxIdleTime = this.options.maxIdleTime || false;
|
|
316
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
|
+
}
|
|
317
325
|
this._lastPollAt = 0;
|
|
318
326
|
this._openDownloads = 0;
|
|
319
327
|
this._recoveryPending = false;
|
|
@@ -333,7 +341,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
333
341
|
// than emitting an error event to the caller.
|
|
334
342
|
this._streamerErrorHandler = (err) => {
|
|
335
343
|
if (['Z_BUF_ERROR', 'ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EHOSTUNREACH'].includes(err.code)) {
|
|
336
|
-
this.
|
|
344
|
+
this.closeConnectionAfter();
|
|
337
345
|
return;
|
|
338
346
|
}
|
|
339
347
|
this.log.error({ err, cid: this.id });
|
|
@@ -354,7 +362,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
354
362
|
// only awaited connect(), as an unhandled 'error' that throws.
|
|
355
363
|
if (this._errorReported) {
|
|
356
364
|
this.log.debug({ msg: 'Suppressed error after the connection already reported one', err, cid: this.id });
|
|
357
|
-
this.
|
|
365
|
+
this.closeConnectionAfter();
|
|
358
366
|
return;
|
|
359
367
|
}
|
|
360
368
|
// During a STARTTLS handshake the upgrade owns the single error path (its settle()
|
|
@@ -372,7 +380,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
372
380
|
return;
|
|
373
381
|
}
|
|
374
382
|
this.upgrading = false;
|
|
375
|
-
this.
|
|
383
|
+
this.closeConnectionAfter();
|
|
376
384
|
return;
|
|
377
385
|
}
|
|
378
386
|
// While the initial connect promise is still pending it owns error reporting:
|
|
@@ -383,12 +391,12 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
383
391
|
this.initialResolve = false;
|
|
384
392
|
this.initialReject = false;
|
|
385
393
|
this._errorReported = true;
|
|
386
|
-
this.
|
|
394
|
+
this.closeConnectionAfter();
|
|
387
395
|
reject(err);
|
|
388
396
|
return;
|
|
389
397
|
}
|
|
390
398
|
this._errorReported = true;
|
|
391
|
-
this.
|
|
399
|
+
this.closeConnectionAfter();
|
|
392
400
|
this.emit('error', err);
|
|
393
401
|
}
|
|
394
402
|
/** @internal */
|
|
@@ -414,7 +422,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
414
422
|
}
|
|
415
423
|
if (this.writeSocket.destroyed) {
|
|
416
424
|
this.log.error({ msg: 'Write socket destroyed', cid: this.id });
|
|
417
|
-
this.
|
|
425
|
+
this.closeConnection();
|
|
418
426
|
return;
|
|
419
427
|
}
|
|
420
428
|
// Append CRLF only to the final part of a command. When sending literals,
|
|
@@ -1264,8 +1272,8 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1264
1272
|
this.log.error({ err, cid: this.id });
|
|
1265
1273
|
this.emitError(err);
|
|
1266
1274
|
});
|
|
1267
|
-
this._socketClose = this._socketClose || (() => this.
|
|
1268
|
-
this._socketEnd = this._socketEnd || (() => this.
|
|
1275
|
+
this._socketClose = this._socketClose || (() => this.closeConnection());
|
|
1276
|
+
this._socketEnd = this._socketEnd || (() => this.closeConnection());
|
|
1269
1277
|
/**
|
|
1270
1278
|
* Socket timeout event handler.
|
|
1271
1279
|
*
|
|
@@ -1319,7 +1327,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1319
1327
|
.catch(err => {
|
|
1320
1328
|
this.log.warn({ msg: 'Connection recovery failed after timeout', err, cid: this.id });
|
|
1321
1329
|
if (!this.isClosed) {
|
|
1322
|
-
this.
|
|
1330
|
+
this.closeConnection();
|
|
1323
1331
|
}
|
|
1324
1332
|
})
|
|
1325
1333
|
.finally(() => {
|
|
@@ -1630,7 +1638,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1630
1638
|
// is rejected there instead (with a generic TLS error rather than STARTTLS_INJECTION).
|
|
1631
1639
|
const failSTARTTLSInjection = () => {
|
|
1632
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 });
|
|
1633
|
-
this.
|
|
1641
|
+
this.closeConnectionAfter();
|
|
1634
1642
|
return err;
|
|
1635
1643
|
};
|
|
1636
1644
|
// Check 1: the parser saw more input already buffered right after the tagged OK
|
|
@@ -1698,7 +1706,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1698
1706
|
// Preserve the original error, marked as a TLS failure so callers can tell
|
|
1699
1707
|
// an upgrade failure from an ordinary command failure.
|
|
1700
1708
|
err.tlsFailed = true;
|
|
1701
|
-
this.
|
|
1709
|
+
this.closeConnectionAfter();
|
|
1702
1710
|
return reject(err);
|
|
1703
1711
|
}
|
|
1704
1712
|
resolve(result);
|
|
@@ -1888,7 +1896,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1888
1896
|
.catch(err => {
|
|
1889
1897
|
// The transport goes with the failed attempt: the instance can not be reused, and
|
|
1890
1898
|
// an open socket would only trip the inactivity watchdog later
|
|
1891
|
-
this.
|
|
1899
|
+
this.closeConnectionAfter();
|
|
1892
1900
|
if (typeof this.initialReject === 'function') {
|
|
1893
1901
|
this.log.error({ err, cid: this.id });
|
|
1894
1902
|
(0, tools_js_1.clearTimer)(this.greetingTimeout);
|
|
@@ -1938,7 +1946,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
1938
1946
|
// that answers a LOGOUT is the exception: its tagged completion closes the connection.
|
|
1939
1947
|
const answersLogout = !!this.currentRequest && this.currentRequest.command.toUpperCase() === 'LOGOUT';
|
|
1940
1948
|
if (!this.greetingReceived || !answersLogout) {
|
|
1941
|
-
this.
|
|
1949
|
+
this.closeConnectionAfter();
|
|
1942
1950
|
}
|
|
1943
1951
|
}
|
|
1944
1952
|
// Drops every capability-derived field together - the counterpart of
|
|
@@ -2187,6 +2195,19 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2187
2195
|
connectionBusy() {
|
|
2188
2196
|
return !!(this.currentLock || this.locks.length || this.currentRequest || this.requestQueue.length || this._openDownloads);
|
|
2189
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
|
|
2200
|
+
/** @internal */
|
|
2201
|
+
settleIdleWaiters(err) {
|
|
2202
|
+
for (let { resolve, reject } of this._idleWaiters.splice(0)) {
|
|
2203
|
+
if (err) {
|
|
2204
|
+
reject(err);
|
|
2205
|
+
}
|
|
2206
|
+
else {
|
|
2207
|
+
resolve();
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2190
2211
|
// Timer process-liveness policy: connection establishment and greeting deadlines keep the
|
|
2191
2212
|
// process alive, because a caller is waiting on connect() to settle. Background timers
|
|
2192
2213
|
// (auto-IDLE, IDLE restart, fallback polling, throttle back-off, the held-lock diagnostic) are
|
|
@@ -2194,6 +2215,9 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2194
2215
|
// explicitly on close().
|
|
2195
2216
|
/** @internal */
|
|
2196
2217
|
autoidle() {
|
|
2218
|
+
// every place the connection may have become free calls this, so it is where waiting
|
|
2219
|
+
// idle({ whenFree: true }) calls re-check it, also with auto-IDLE turned off
|
|
2220
|
+
this.settleIdleWaiters();
|
|
2197
2221
|
(0, tools_js_1.clearTimer)(this.idleStartTimer);
|
|
2198
2222
|
if (this.options.disableAutoIdle || !this.usable || this.state !== this.states.SELECTED) {
|
|
2199
2223
|
return;
|
|
@@ -2301,7 +2325,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2301
2325
|
this.connectTimeout = setTimeout(() => {
|
|
2302
2326
|
let err = deadline.error();
|
|
2303
2327
|
this.log.error({ err, cid: this.id });
|
|
2304
|
-
this.
|
|
2328
|
+
this.closeConnectionAfter();
|
|
2305
2329
|
reject(err);
|
|
2306
2330
|
}, deadline.remaining());
|
|
2307
2331
|
let onConnect = () => {
|
|
@@ -2328,7 +2352,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2328
2352
|
}
|
|
2329
2353
|
});
|
|
2330
2354
|
this.log.error({ err, cid: this.id });
|
|
2331
|
-
this.
|
|
2355
|
+
this.closeConnectionAfter();
|
|
2332
2356
|
reject(err);
|
|
2333
2357
|
}, this.options.greetingTimeout || GREETING_TIMEOUT);
|
|
2334
2358
|
const connected = this.socket;
|
|
@@ -2382,7 +2406,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2382
2406
|
catch (err) {
|
|
2383
2407
|
// refused up front: the connection deadline must not stay armed
|
|
2384
2408
|
(0, tools_js_1.clearTimer)(this.connectTimeout);
|
|
2385
|
-
this.
|
|
2409
|
+
this.closeConnectionAfter();
|
|
2386
2410
|
this.log.error({ err, cid: this.id });
|
|
2387
2411
|
reject(err);
|
|
2388
2412
|
return;
|
|
@@ -2396,7 +2420,7 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2396
2420
|
this._connectErrorHandler = (err) => {
|
|
2397
2421
|
(0, tools_js_1.clearTimer)(this.connectTimeout);
|
|
2398
2422
|
(0, tools_js_1.clearTimer)(this.greetingTimeout);
|
|
2399
|
-
this.
|
|
2423
|
+
this.closeConnectionAfter();
|
|
2400
2424
|
this.log.error({ err, cid: this.id });
|
|
2401
2425
|
reject(err);
|
|
2402
2426
|
};
|
|
@@ -2414,6 +2438,9 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2414
2438
|
* await client.logout();
|
|
2415
2439
|
*/
|
|
2416
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();
|
|
2417
2444
|
return await this.run('LOGOUT');
|
|
2418
2445
|
}
|
|
2419
2446
|
/**
|
|
@@ -2422,7 +2449,21 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2422
2449
|
* caller function to proceed, and run `close()` function afterwards.
|
|
2423
2450
|
*/
|
|
2424
2451
|
closeAfter() {
|
|
2425
|
-
|
|
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
|
+
}
|
|
2426
2467
|
}
|
|
2427
2468
|
// Connection-scoped wrapper around the shared stamping helper; see buildConnectionError().
|
|
2428
2469
|
/** @internal */
|
|
@@ -2458,6 +2499,13 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2458
2499
|
* client.close();
|
|
2459
2500
|
*/
|
|
2460
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() {
|
|
2461
2509
|
try {
|
|
2462
2510
|
this.closeTimers();
|
|
2463
2511
|
this.usable = false;
|
|
@@ -2501,6 +2549,9 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2501
2549
|
}
|
|
2502
2550
|
this.closeRequests();
|
|
2503
2551
|
this.closeLocks();
|
|
2552
|
+
if (this._idleWaiters.length) {
|
|
2553
|
+
this.settleIdleWaiters(this.createNoConnectionError(this.byeReason, { rejectedFrom: 'idleWait', command: 'IDLE' }));
|
|
2554
|
+
}
|
|
2504
2555
|
this.closeStreams();
|
|
2505
2556
|
// clear socket handlers
|
|
2506
2557
|
this.clearSocketHandlers();
|
|
@@ -2941,8 +2992,10 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2941
2992
|
* return until IDLE is finished which means you would have to call some other command out of scope.
|
|
2942
2993
|
*
|
|
2943
2994
|
* Unlike auto-IDLE, a direct call does not wait for the connection to be free: a mailbox lock
|
|
2944
|
-
* holder may idle inside its own lock, and idle() can not tell it from another caller.
|
|
2995
|
+
* holder may idle inside its own lock, and idle() can not tell it from another caller. Code that
|
|
2996
|
+
* holds no lock passes `{ whenFree: true }` to wait for the connection to be free first.
|
|
2945
2997
|
*
|
|
2998
|
+
* @param options IDLE options
|
|
2946
2999
|
* @returns `false` if IDLE failed, `undefined` otherwise
|
|
2947
3000
|
*
|
|
2948
3001
|
* @example
|
|
@@ -2950,7 +3003,13 @@ class ImapFlow extends node_events_1.EventEmitter {
|
|
|
2950
3003
|
*
|
|
2951
3004
|
* await client.idle();
|
|
2952
3005
|
*/
|
|
2953
|
-
async idle() {
|
|
3006
|
+
async idle(options) {
|
|
3007
|
+
// Re-checked on every wake, as another IDLE may have started meanwhile
|
|
3008
|
+
while (options?.whenFree && !this.idling && this.usable && this.connectionBusy()) {
|
|
3009
|
+
await new Promise((resolve, reject) => {
|
|
3010
|
+
this._idleWaiters.push({ resolve, reject });
|
|
3011
|
+
});
|
|
3012
|
+
}
|
|
2954
3013
|
if (!this.idling) {
|
|
2955
3014
|
return await this.run('IDLE', this.maxIdleTime);
|
|
2956
3015
|
}
|
package/dist/cjs/package-info.js
CHANGED
package/dist/cjs/types.d.ts
CHANGED
|
@@ -745,6 +745,16 @@ export interface MailboxOpenOptions {
|
|
|
745
745
|
/** QRESYNC (RFC 7162): the UIDVALIDITY known from the previous session, required with `changedSince` */
|
|
746
746
|
uidValidity?: bigint | number | string | undefined;
|
|
747
747
|
}
|
|
748
|
+
export interface IdleOptions {
|
|
749
|
+
/**
|
|
750
|
+
* Wait until the connection is free before starting IDLE: no mailbox lock held or queued, no
|
|
751
|
+
* command running or queued, no download open. Use it from code that does not hold a mailbox
|
|
752
|
+
* lock, so IDLE does not land between another caller's commands. A lock holder must not set it,
|
|
753
|
+
* it would wait for its own release. The returned promise rejects with `NoConnection` when the
|
|
754
|
+
* connection closes while waiting.
|
|
755
|
+
*/
|
|
756
|
+
whenFree?: boolean | undefined;
|
|
757
|
+
}
|
|
748
758
|
export interface MailboxLockOptions extends MailboxOpenOptions {
|
|
749
759
|
/**
|
|
750
760
|
* Optional timeout in milliseconds to wait for the lock to be granted.
|
|
@@ -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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { EventEmitter } from 'node:events';
|
|
5
5
|
import { PassThrough, type Readable } from 'node:stream';
|
|
6
6
|
import { AuthenticationFailure } from './errors.js';
|
|
7
|
-
import type { AppendResponseObject, CopyResponseObject, DownloadManyOptions, DownloadManyResult, DownloadObject, DownloadNotFound, DownloadOptions, ESearchResult, FetchMessageObject, FetchOptions, FetchQueryObject, IdInfoObject, ImapFlowEvents, ImapFlowOptions, InternalLogger, ListOptions, ListResponse, ListTreeResponse, MailboxCreateResponse, MailboxDeleteResponse, MailboxLockObject, MailboxLockOptions, MailboxObject, MailboxOpenOptions, MailboxRenameResponse, MessageRange, MessageRangeOptions, NamespaceObject, NamespacesObject, QuotaResponse, SearchObject, SearchOptions, SearchReturnOption, SequenceString, StatusObject, StatusQuery, StoreOptions, TlsInfo, ImapTransportSocket } from './types.js';
|
|
7
|
+
import type { AppendResponseObject, CopyResponseObject, DownloadManyOptions, DownloadManyResult, DownloadObject, DownloadNotFound, DownloadOptions, ESearchResult, FetchMessageObject, FetchOptions, FetchQueryObject, IdInfoObject, ImapFlowEvents, ImapFlowOptions, InternalLogger, ListOptions, ListResponse, ListTreeResponse, MailboxCreateResponse, MailboxDeleteResponse, MailboxLockObject, MailboxLockOptions, IdleOptions, MailboxObject, MailboxOpenOptions, MailboxRenameResponse, MessageRange, MessageRangeOptions, NamespaceObject, NamespacesObject, QuotaResponse, SearchObject, SearchOptions, SearchReturnOption, SequenceString, StatusObject, StatusQuery, StoreOptions, TlsInfo, ImapTransportSocket } from './types.js';
|
|
8
8
|
export type * from './types.js';
|
|
9
9
|
export type { ImapFlowError } from './errors.js';
|
|
10
10
|
export { AuthenticationFailure, ImapFlowErrorCode } from './errors.js';
|
|
@@ -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 */
|
|
@@ -378,8 +387,10 @@ export declare class ImapFlow extends EventEmitter {
|
|
|
378
387
|
* return until IDLE is finished which means you would have to call some other command out of scope.
|
|
379
388
|
*
|
|
380
389
|
* Unlike auto-IDLE, a direct call does not wait for the connection to be free: a mailbox lock
|
|
381
|
-
* holder may idle inside its own lock, and idle() can not tell it from another caller.
|
|
390
|
+
* holder may idle inside its own lock, and idle() can not tell it from another caller. Code that
|
|
391
|
+
* holds no lock passes `{ whenFree: true }` to wait for the connection to be free first.
|
|
382
392
|
*
|
|
393
|
+
* @param options IDLE options
|
|
383
394
|
* @returns `false` if IDLE failed, `undefined` otherwise
|
|
384
395
|
*
|
|
385
396
|
* @example
|
|
@@ -387,7 +398,7 @@ export declare class ImapFlow extends EventEmitter {
|
|
|
387
398
|
*
|
|
388
399
|
* await client.idle();
|
|
389
400
|
*/
|
|
390
|
-
idle(): Promise<boolean | undefined>;
|
|
401
|
+
idle(options?: IdleOptions | undefined): Promise<boolean | undefined>;
|
|
391
402
|
/**
|
|
392
403
|
* Sets flags for a message or message range
|
|
393
404
|
*
|
package/dist/esm/imap-flow.js
CHANGED
|
@@ -259,7 +259,10 @@ export class ImapFlow extends EventEmitter {
|
|
|
259
259
|
this.context = this.options.context;
|
|
260
260
|
this.connectedAt = null;
|
|
261
261
|
this.lastClientWrite = null;
|
|
262
|
+
this._idleWaiters = [];
|
|
262
263
|
this.idlingAtClose = false;
|
|
264
|
+
this.closedByClient = false;
|
|
265
|
+
this._closePending = false;
|
|
263
266
|
this.emitLogs = !!this.options.emitLogs;
|
|
264
267
|
this.lo = 0;
|
|
265
268
|
this.untaggedHandlers = {};
|
|
@@ -271,6 +274,11 @@ export class ImapFlow extends EventEmitter {
|
|
|
271
274
|
this.idRequested = false;
|
|
272
275
|
this.maxIdleTime = this.options.maxIdleTime || false;
|
|
273
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
|
+
}
|
|
274
282
|
this._lastPollAt = 0;
|
|
275
283
|
this._openDownloads = 0;
|
|
276
284
|
this._recoveryPending = false;
|
|
@@ -290,7 +298,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
290
298
|
// than emitting an error event to the caller.
|
|
291
299
|
this._streamerErrorHandler = (err) => {
|
|
292
300
|
if (['Z_BUF_ERROR', 'ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EHOSTUNREACH'].includes(err.code)) {
|
|
293
|
-
this.
|
|
301
|
+
this.closeConnectionAfter();
|
|
294
302
|
return;
|
|
295
303
|
}
|
|
296
304
|
this.log.error({ err, cid: this.id });
|
|
@@ -311,7 +319,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
311
319
|
// only awaited connect(), as an unhandled 'error' that throws.
|
|
312
320
|
if (this._errorReported) {
|
|
313
321
|
this.log.debug({ msg: 'Suppressed error after the connection already reported one', err, cid: this.id });
|
|
314
|
-
this.
|
|
322
|
+
this.closeConnectionAfter();
|
|
315
323
|
return;
|
|
316
324
|
}
|
|
317
325
|
// During a STARTTLS handshake the upgrade owns the single error path (its settle()
|
|
@@ -329,7 +337,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
329
337
|
return;
|
|
330
338
|
}
|
|
331
339
|
this.upgrading = false;
|
|
332
|
-
this.
|
|
340
|
+
this.closeConnectionAfter();
|
|
333
341
|
return;
|
|
334
342
|
}
|
|
335
343
|
// While the initial connect promise is still pending it owns error reporting:
|
|
@@ -340,12 +348,12 @@ export class ImapFlow extends EventEmitter {
|
|
|
340
348
|
this.initialResolve = false;
|
|
341
349
|
this.initialReject = false;
|
|
342
350
|
this._errorReported = true;
|
|
343
|
-
this.
|
|
351
|
+
this.closeConnectionAfter();
|
|
344
352
|
reject(err);
|
|
345
353
|
return;
|
|
346
354
|
}
|
|
347
355
|
this._errorReported = true;
|
|
348
|
-
this.
|
|
356
|
+
this.closeConnectionAfter();
|
|
349
357
|
this.emit('error', err);
|
|
350
358
|
}
|
|
351
359
|
/** @internal */
|
|
@@ -371,7 +379,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
371
379
|
}
|
|
372
380
|
if (this.writeSocket.destroyed) {
|
|
373
381
|
this.log.error({ msg: 'Write socket destroyed', cid: this.id });
|
|
374
|
-
this.
|
|
382
|
+
this.closeConnection();
|
|
375
383
|
return;
|
|
376
384
|
}
|
|
377
385
|
// Append CRLF only to the final part of a command. When sending literals,
|
|
@@ -1221,8 +1229,8 @@ export class ImapFlow extends EventEmitter {
|
|
|
1221
1229
|
this.log.error({ err, cid: this.id });
|
|
1222
1230
|
this.emitError(err);
|
|
1223
1231
|
});
|
|
1224
|
-
this._socketClose = this._socketClose || (() => this.
|
|
1225
|
-
this._socketEnd = this._socketEnd || (() => this.
|
|
1232
|
+
this._socketClose = this._socketClose || (() => this.closeConnection());
|
|
1233
|
+
this._socketEnd = this._socketEnd || (() => this.closeConnection());
|
|
1226
1234
|
/**
|
|
1227
1235
|
* Socket timeout event handler.
|
|
1228
1236
|
*
|
|
@@ -1276,7 +1284,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1276
1284
|
.catch(err => {
|
|
1277
1285
|
this.log.warn({ msg: 'Connection recovery failed after timeout', err, cid: this.id });
|
|
1278
1286
|
if (!this.isClosed) {
|
|
1279
|
-
this.
|
|
1287
|
+
this.closeConnection();
|
|
1280
1288
|
}
|
|
1281
1289
|
})
|
|
1282
1290
|
.finally(() => {
|
|
@@ -1587,7 +1595,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1587
1595
|
// is rejected there instead (with a generic TLS error rather than STARTTLS_INJECTION).
|
|
1588
1596
|
const failSTARTTLSInjection = () => {
|
|
1589
1597
|
let err = createImapError('Server sent data after the STARTTLS response and before the TLS handshake; possible plaintext-injection attack', 'STARTTLS_INJECTION', { tlsFailed: true });
|
|
1590
|
-
this.
|
|
1598
|
+
this.closeConnectionAfter();
|
|
1591
1599
|
return err;
|
|
1592
1600
|
};
|
|
1593
1601
|
// Check 1: the parser saw more input already buffered right after the tagged OK
|
|
@@ -1655,7 +1663,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1655
1663
|
// Preserve the original error, marked as a TLS failure so callers can tell
|
|
1656
1664
|
// an upgrade failure from an ordinary command failure.
|
|
1657
1665
|
err.tlsFailed = true;
|
|
1658
|
-
this.
|
|
1666
|
+
this.closeConnectionAfter();
|
|
1659
1667
|
return reject(err);
|
|
1660
1668
|
}
|
|
1661
1669
|
resolve(result);
|
|
@@ -1845,7 +1853,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1845
1853
|
.catch(err => {
|
|
1846
1854
|
// The transport goes with the failed attempt: the instance can not be reused, and
|
|
1847
1855
|
// an open socket would only trip the inactivity watchdog later
|
|
1848
|
-
this.
|
|
1856
|
+
this.closeConnectionAfter();
|
|
1849
1857
|
if (typeof this.initialReject === 'function') {
|
|
1850
1858
|
this.log.error({ err, cid: this.id });
|
|
1851
1859
|
clearTimer(this.greetingTimeout);
|
|
@@ -1895,7 +1903,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
1895
1903
|
// that answers a LOGOUT is the exception: its tagged completion closes the connection.
|
|
1896
1904
|
const answersLogout = !!this.currentRequest && this.currentRequest.command.toUpperCase() === 'LOGOUT';
|
|
1897
1905
|
if (!this.greetingReceived || !answersLogout) {
|
|
1898
|
-
this.
|
|
1906
|
+
this.closeConnectionAfter();
|
|
1899
1907
|
}
|
|
1900
1908
|
}
|
|
1901
1909
|
// Drops every capability-derived field together - the counterpart of
|
|
@@ -2144,6 +2152,19 @@ export class ImapFlow extends EventEmitter {
|
|
|
2144
2152
|
connectionBusy() {
|
|
2145
2153
|
return !!(this.currentLock || this.locks.length || this.currentRequest || this.requestQueue.length || this._openDownloads);
|
|
2146
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
|
|
2157
|
+
/** @internal */
|
|
2158
|
+
settleIdleWaiters(err) {
|
|
2159
|
+
for (let { resolve, reject } of this._idleWaiters.splice(0)) {
|
|
2160
|
+
if (err) {
|
|
2161
|
+
reject(err);
|
|
2162
|
+
}
|
|
2163
|
+
else {
|
|
2164
|
+
resolve();
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2147
2168
|
// Timer process-liveness policy: connection establishment and greeting deadlines keep the
|
|
2148
2169
|
// process alive, because a caller is waiting on connect() to settle. Background timers
|
|
2149
2170
|
// (auto-IDLE, IDLE restart, fallback polling, throttle back-off, the held-lock diagnostic) are
|
|
@@ -2151,6 +2172,9 @@ export class ImapFlow extends EventEmitter {
|
|
|
2151
2172
|
// explicitly on close().
|
|
2152
2173
|
/** @internal */
|
|
2153
2174
|
autoidle() {
|
|
2175
|
+
// every place the connection may have become free calls this, so it is where waiting
|
|
2176
|
+
// idle({ whenFree: true }) calls re-check it, also with auto-IDLE turned off
|
|
2177
|
+
this.settleIdleWaiters();
|
|
2154
2178
|
clearTimer(this.idleStartTimer);
|
|
2155
2179
|
if (this.options.disableAutoIdle || !this.usable || this.state !== this.states.SELECTED) {
|
|
2156
2180
|
return;
|
|
@@ -2258,7 +2282,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
2258
2282
|
this.connectTimeout = setTimeout(() => {
|
|
2259
2283
|
let err = deadline.error();
|
|
2260
2284
|
this.log.error({ err, cid: this.id });
|
|
2261
|
-
this.
|
|
2285
|
+
this.closeConnectionAfter();
|
|
2262
2286
|
reject(err);
|
|
2263
2287
|
}, deadline.remaining());
|
|
2264
2288
|
let onConnect = () => {
|
|
@@ -2285,7 +2309,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
2285
2309
|
}
|
|
2286
2310
|
});
|
|
2287
2311
|
this.log.error({ err, cid: this.id });
|
|
2288
|
-
this.
|
|
2312
|
+
this.closeConnectionAfter();
|
|
2289
2313
|
reject(err);
|
|
2290
2314
|
}, this.options.greetingTimeout || GREETING_TIMEOUT);
|
|
2291
2315
|
const connected = this.socket;
|
|
@@ -2339,7 +2363,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
2339
2363
|
catch (err) {
|
|
2340
2364
|
// refused up front: the connection deadline must not stay armed
|
|
2341
2365
|
clearTimer(this.connectTimeout);
|
|
2342
|
-
this.
|
|
2366
|
+
this.closeConnectionAfter();
|
|
2343
2367
|
this.log.error({ err, cid: this.id });
|
|
2344
2368
|
reject(err);
|
|
2345
2369
|
return;
|
|
@@ -2353,7 +2377,7 @@ export class ImapFlow extends EventEmitter {
|
|
|
2353
2377
|
this._connectErrorHandler = (err) => {
|
|
2354
2378
|
clearTimer(this.connectTimeout);
|
|
2355
2379
|
clearTimer(this.greetingTimeout);
|
|
2356
|
-
this.
|
|
2380
|
+
this.closeConnectionAfter();
|
|
2357
2381
|
this.log.error({ err, cid: this.id });
|
|
2358
2382
|
reject(err);
|
|
2359
2383
|
};
|
|
@@ -2371,6 +2395,9 @@ export class ImapFlow extends EventEmitter {
|
|
|
2371
2395
|
* await client.logout();
|
|
2372
2396
|
*/
|
|
2373
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();
|
|
2374
2401
|
return await this.run('LOGOUT');
|
|
2375
2402
|
}
|
|
2376
2403
|
/**
|
|
@@ -2379,7 +2406,21 @@ export class ImapFlow extends EventEmitter {
|
|
|
2379
2406
|
* caller function to proceed, and run `close()` function afterwards.
|
|
2380
2407
|
*/
|
|
2381
2408
|
closeAfter() {
|
|
2382
|
-
|
|
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
|
+
}
|
|
2383
2424
|
}
|
|
2384
2425
|
// Connection-scoped wrapper around the shared stamping helper; see buildConnectionError().
|
|
2385
2426
|
/** @internal */
|
|
@@ -2415,6 +2456,13 @@ export class ImapFlow extends EventEmitter {
|
|
|
2415
2456
|
* client.close();
|
|
2416
2457
|
*/
|
|
2417
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() {
|
|
2418
2466
|
try {
|
|
2419
2467
|
this.closeTimers();
|
|
2420
2468
|
this.usable = false;
|
|
@@ -2458,6 +2506,9 @@ export class ImapFlow extends EventEmitter {
|
|
|
2458
2506
|
}
|
|
2459
2507
|
this.closeRequests();
|
|
2460
2508
|
this.closeLocks();
|
|
2509
|
+
if (this._idleWaiters.length) {
|
|
2510
|
+
this.settleIdleWaiters(this.createNoConnectionError(this.byeReason, { rejectedFrom: 'idleWait', command: 'IDLE' }));
|
|
2511
|
+
}
|
|
2461
2512
|
this.closeStreams();
|
|
2462
2513
|
// clear socket handlers
|
|
2463
2514
|
this.clearSocketHandlers();
|
|
@@ -2898,8 +2949,10 @@ export class ImapFlow extends EventEmitter {
|
|
|
2898
2949
|
* return until IDLE is finished which means you would have to call some other command out of scope.
|
|
2899
2950
|
*
|
|
2900
2951
|
* Unlike auto-IDLE, a direct call does not wait for the connection to be free: a mailbox lock
|
|
2901
|
-
* holder may idle inside its own lock, and idle() can not tell it from another caller.
|
|
2952
|
+
* holder may idle inside its own lock, and idle() can not tell it from another caller. Code that
|
|
2953
|
+
* holds no lock passes `{ whenFree: true }` to wait for the connection to be free first.
|
|
2902
2954
|
*
|
|
2955
|
+
* @param options IDLE options
|
|
2903
2956
|
* @returns `false` if IDLE failed, `undefined` otherwise
|
|
2904
2957
|
*
|
|
2905
2958
|
* @example
|
|
@@ -2907,7 +2960,13 @@ export class ImapFlow extends EventEmitter {
|
|
|
2907
2960
|
*
|
|
2908
2961
|
* await client.idle();
|
|
2909
2962
|
*/
|
|
2910
|
-
async idle() {
|
|
2963
|
+
async idle(options) {
|
|
2964
|
+
// Re-checked on every wake, as another IDLE may have started meanwhile
|
|
2965
|
+
while (options?.whenFree && !this.idling && this.usable && this.connectionBusy()) {
|
|
2966
|
+
await new Promise((resolve, reject) => {
|
|
2967
|
+
this._idleWaiters.push({ resolve, reject });
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2911
2970
|
if (!this.idling) {
|
|
2912
2971
|
return await this.run('IDLE', this.maxIdleTime);
|
|
2913
2972
|
}
|
package/dist/esm/package-info.js
CHANGED
package/dist/esm/types.d.ts
CHANGED
|
@@ -745,6 +745,16 @@ export interface MailboxOpenOptions {
|
|
|
745
745
|
/** QRESYNC (RFC 7162): the UIDVALIDITY known from the previous session, required with `changedSince` */
|
|
746
746
|
uidValidity?: bigint | number | string | undefined;
|
|
747
747
|
}
|
|
748
|
+
export interface IdleOptions {
|
|
749
|
+
/**
|
|
750
|
+
* Wait until the connection is free before starting IDLE: no mailbox lock held or queued, no
|
|
751
|
+
* command running or queued, no download open. Use it from code that does not hold a mailbox
|
|
752
|
+
* lock, so IDLE does not land between another caller's commands. A lock holder must not set it,
|
|
753
|
+
* it would wait for its own release. The returned promise rejects with `NoConnection` when the
|
|
754
|
+
* connection closes while waiting.
|
|
755
|
+
*/
|
|
756
|
+
whenFree?: boolean | undefined;
|
|
757
|
+
}
|
|
748
758
|
export interface MailboxLockOptions extends MailboxOpenOptions {
|
|
749
759
|
/**
|
|
750
760
|
* Optional timeout in milliseconds to wait for the lock to be granted.
|