imapflow 1.0.193 → 1.0.194

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/lib/imap-flow.js CHANGED
@@ -369,7 +369,8 @@ class ImapFlow extends EventEmitter {
369
369
  this.streamer.on('error', err => {
370
370
  if (['Z_BUF_ERROR', 'ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'EHOSTUNREACH'].includes(err.code)) {
371
371
  // just close the connection, usually nothing but noise
372
- return setImmediate(() => this.close());
372
+ this.closeAfter();
373
+ return;
373
374
  }
374
375
 
375
376
  this.log.error({ err, cid: this.id });
@@ -386,7 +387,7 @@ class ImapFlow extends EventEmitter {
386
387
  }
387
388
  err._connId = err._connId || this.id;
388
389
 
389
- setImmediate(() => this.close());
390
+ this.closeAfter();
390
391
  this.emit('error', err);
391
392
  }
392
393
 
@@ -764,16 +765,8 @@ class ImapFlow extends EventEmitter {
764
765
  this.log.error({ err, cid: this.id });
765
766
  this.emitError(err);
766
767
  });
767
- this._socketClose =
768
- this._socketClose ||
769
- (() => {
770
- this.close();
771
- });
772
- this._socketEnd =
773
- this._socketEnd ||
774
- (() => {
775
- this.close();
776
- });
768
+ this._socketClose = this._socketClose || (() => this.close());
769
+ this._socketEnd = this._socketEnd || (() => this.close());
777
770
 
778
771
  /**
779
772
  * Socket timeout event handler.
@@ -929,21 +922,25 @@ class ImapFlow extends EventEmitter {
929
922
  // use normal pipes for this.writeSocket -> this._deflate -> this.socket
930
923
  let reading = false;
931
924
  let readNext = () => {
932
- reading = true;
925
+ try {
926
+ reading = true;
933
927
 
934
- let chunk;
935
- while ((chunk = this.writeSocket.read()) !== null) {
936
- if (this._deflate && this._deflate.write(chunk) === false) {
937
- return this._deflate.once('drain', readNext);
928
+ let chunk;
929
+ while ((chunk = this.writeSocket.read()) !== null) {
930
+ if (this._deflate && this._deflate.write(chunk) === false) {
931
+ return this._deflate.once('drain', readNext);
932
+ }
938
933
  }
939
- }
940
934
 
941
- // flush data to socket
942
- if (this._deflate) {
943
- this._deflate.flush();
944
- }
935
+ // flush data to socket
936
+ if (this._deflate) {
937
+ this._deflate.flush();
938
+ }
945
939
 
946
- reading = false;
940
+ reading = false;
941
+ } catch (ex) {
942
+ this.emitError(ex);
943
+ }
947
944
  };
948
945
 
949
946
  this.writeSocket.on('readable', () => {
@@ -1024,7 +1021,7 @@ class ImapFlow extends EventEmitter {
1024
1021
  // don't care anymore
1025
1022
  return;
1026
1023
  }
1027
- setImmediate(() => this.close());
1024
+ this.closeAfter();
1028
1025
  this.upgrading = false;
1029
1026
  err.tlsFailed = true;
1030
1027
  reject(err);
@@ -1034,7 +1031,7 @@ class ImapFlow extends EventEmitter {
1034
1031
  if (!this.upgrading) {
1035
1032
  return;
1036
1033
  }
1037
- setImmediate(() => this.close());
1034
+ this.closeAfter();
1038
1035
  let err = new Error('Failed to upgrade connection in required time');
1039
1036
  err.tlsFailed = true;
1040
1037
  err.code = 'UPGRADE_TIMEOUT';
@@ -1043,30 +1040,34 @@ class ImapFlow extends EventEmitter {
1043
1040
 
1044
1041
  this.upgrading = true;
1045
1042
  this.socket = tls.connect(opts, () => {
1046
- clearTimeout(this.upgradeTimeout);
1047
- if (this.isClosed) {
1048
- // not sure if this is possible?
1049
- return this.close();
1050
- }
1043
+ try {
1044
+ clearTimeout(this.upgradeTimeout);
1045
+ if (this.isClosed) {
1046
+ // not sure if this is possible?
1047
+ return this.close();
1048
+ }
1051
1049
 
1052
- this.secureConnection = true;
1053
- this.upgrading = false;
1054
- this.streamer.secureConnection = true;
1055
- this.socket.pipe(this.streamer);
1056
- this.tls = typeof this.socket.getCipher === 'function' ? this.socket.getCipher() : false;
1057
- if (this.tls) {
1058
- this.tls.authorized = this.socket.authorized;
1059
- this.log.info({
1060
- src: 'tls',
1061
- msg: 'Established TLS session',
1062
- cid: this.id,
1063
- authorized: this.tls.authorized,
1064
- algo: this.tls.standardName || this.tls.name,
1065
- version: this.tls.version
1066
- });
1067
- }
1050
+ this.secureConnection = true;
1051
+ this.upgrading = false;
1052
+ this.streamer.secureConnection = true;
1053
+ this.socket.pipe(this.streamer);
1054
+ this.tls = typeof this.socket.getCipher === 'function' ? this.socket.getCipher() : false;
1055
+ if (this.tls) {
1056
+ this.tls.authorized = this.socket.authorized;
1057
+ this.log.info({
1058
+ src: 'tls',
1059
+ msg: 'Established TLS session',
1060
+ cid: this.id,
1061
+ authorized: this.tls.authorized,
1062
+ algo: this.tls.standardName || this.tls.name,
1063
+ version: this.tls.version
1064
+ });
1065
+ }
1068
1066
 
1069
- return resolve(true);
1067
+ return resolve(true);
1068
+ } catch (ex) {
1069
+ this.emitError(ex);
1070
+ }
1070
1071
  });
1071
1072
 
1072
1073
  this.writeSocket = this.socket;
@@ -1214,7 +1215,7 @@ class ImapFlow extends EventEmitter {
1214
1215
  return reject(err);
1215
1216
  }
1216
1217
 
1217
- setImmediate(() => this.close());
1218
+ this.closeAfter();
1218
1219
  });
1219
1220
  }
1220
1221
 
@@ -1517,55 +1518,62 @@ class ImapFlow extends EventEmitter {
1517
1518
  connectionTimeout: this.options.connectionTimeout || CONNECT_TIMEOUT
1518
1519
  };
1519
1520
  this.log.error({ err, cid: this.id });
1520
- setImmediate(() => this.close());
1521
+ this.closeAfter();
1521
1522
  reject(err);
1522
1523
  }, this.options.connectionTimeout || CONNECT_TIMEOUT);
1523
1524
 
1524
1525
  let onConnect = () => {
1525
- clearTimeout(this.connectTimeout);
1526
- this.socket.setKeepAlive(true, 5 * 1000);
1527
- this.socket.setTimeout(this.options.socketTimeout || SOCKET_TIMEOUT);
1528
-
1529
- this.greetingTimeout = setTimeout(() => {
1530
- let err = new Error(`Failed to receive greeting from server in required time${!this.secureConnection ? '. Maybe should use TLS?' : ''}`);
1531
- err.code = 'GREETING_TIMEOUT';
1532
- err.details = {
1533
- greetingTimeout: this.options.greetingTimeout || GREETING_TIMEOUT
1526
+ try {
1527
+ clearTimeout(this.connectTimeout);
1528
+ this.socket.setKeepAlive(true, 5 * 1000);
1529
+ this.socket.setTimeout(this.options.socketTimeout || SOCKET_TIMEOUT);
1530
+
1531
+ this.greetingTimeout = setTimeout(() => {
1532
+ let err = new Error(
1533
+ `Failed to receive greeting from server in required time${!this.secureConnection ? '. Maybe should use TLS?' : ''}`
1534
+ );
1535
+ err.code = 'GREETING_TIMEOUT';
1536
+ err.details = {
1537
+ greetingTimeout: this.options.greetingTimeout || GREETING_TIMEOUT
1538
+ };
1539
+ this.log.error({ err, cid: this.id });
1540
+ this.closeAfter();
1541
+ reject(err);
1542
+ }, this.options.greetingTimeout || GREETING_TIMEOUT);
1543
+
1544
+ this.tls = typeof this.socket.getCipher === 'function' ? this.socket.getCipher() : false;
1545
+
1546
+ let logInfo = {
1547
+ src: 'connection',
1548
+ msg: `Established ${this.tls ? 'secure ' : ''}TCP connection`,
1549
+ cid: this.id,
1550
+ secure: !!this.tls,
1551
+ host: this.host,
1552
+ servername: this.servername,
1553
+ port: this.socket.remotePort,
1554
+ address: this.socket.remoteAddress,
1555
+ localAddress: this.socket.localAddress,
1556
+ localPort: this.socket.localPort
1534
1557
  };
1535
- this.log.error({ err, cid: this.id });
1536
- setImmediate(() => this.close());
1537
- reject(err);
1538
- }, this.options.greetingTimeout || GREETING_TIMEOUT);
1539
-
1540
- this.tls = typeof this.socket.getCipher === 'function' ? this.socket.getCipher() : false;
1541
-
1542
- let logInfo = {
1543
- src: 'connection',
1544
- msg: `Established ${this.tls ? 'secure ' : ''}TCP connection`,
1545
- cid: this.id,
1546
- secure: !!this.tls,
1547
- host: this.host,
1548
- servername: this.servername,
1549
- port: this.socket.remotePort,
1550
- address: this.socket.remoteAddress,
1551
- localAddress: this.socket.localAddress,
1552
- localPort: this.socket.localPort
1553
- };
1554
1558
 
1555
- if (this.tls) {
1556
- logInfo.authorized = this.tls.authorized = this.socket.authorized;
1557
- logInfo.algo = this.tls.standardName || this.tls.name;
1558
- logInfo.version = this.tls.version;
1559
- }
1559
+ if (this.tls) {
1560
+ logInfo.authorized = this.tls.authorized = this.socket.authorized;
1561
+ logInfo.algo = this.tls.standardName || this.tls.name;
1562
+ logInfo.version = this.tls.version;
1563
+ }
1560
1564
 
1561
- this.log.info(logInfo);
1565
+ this.log.info(logInfo);
1562
1566
 
1563
- this.setSocketHandlers();
1564
- this.socket.pipe(this.streamer);
1567
+ this.setSocketHandlers();
1568
+ this.socket.pipe(this.streamer);
1565
1569
 
1566
- // executed by initial "* OK"
1567
- this.initialResolve = resolve;
1568
- this.initialReject = reject;
1570
+ // executed by initial "* OK"
1571
+ this.initialResolve = resolve;
1572
+ this.initialReject = reject;
1573
+ } catch (ex) {
1574
+ // connect failed
1575
+ reject(ex);
1576
+ }
1569
1577
  };
1570
1578
 
1571
1579
  if (socket) {
@@ -1588,7 +1596,7 @@ class ImapFlow extends EventEmitter {
1588
1596
  this.socket.on('error', err => {
1589
1597
  clearTimeout(this.connectTimeout);
1590
1598
  clearTimeout(this.greetingTimeout);
1591
- setImmediate(() => this.close());
1599
+ this.closeAfter();
1592
1600
  this.log.error({ err, cid: this.id });
1593
1601
  reject(err);
1594
1602
  });
@@ -1611,6 +1619,15 @@ class ImapFlow extends EventEmitter {
1611
1619
  return await this.run('LOGOUT');
1612
1620
  }
1613
1621
 
1622
+ /**
1623
+ * Close the TCP connection.
1624
+ * Unlike `close()`, return immediately from this function, allowing the
1625
+ * caller function to proceed, and run `close()` function afterwards.
1626
+ */
1627
+ closeAfter() {
1628
+ setImmediate(() => this.close());
1629
+ }
1630
+
1614
1631
  /**
1615
1632
  * Closes TCP connection without notifying the server.
1616
1633
  *
@@ -1621,101 +1638,114 @@ class ImapFlow extends EventEmitter {
1621
1638
  * client.close();
1622
1639
  */
1623
1640
  close() {
1624
- // clear pending timers
1625
- clearTimeout(this.idleStartTimer);
1626
- clearTimeout(this.upgradeTimeout);
1627
- clearTimeout(this.connectTimeout);
1628
- clearTimeout(this.greetingTimeout);
1629
-
1630
- this.usable = false;
1631
- this.idling = false;
1632
-
1633
- if (typeof this.initialReject === 'function' && !this.options.verifyOnly) {
1641
+ try {
1642
+ // clear pending timers
1643
+ clearTimeout(this.idleStartTimer);
1644
+ clearTimeout(this.upgradeTimeout);
1645
+ clearTimeout(this.connectTimeout);
1634
1646
  clearTimeout(this.greetingTimeout);
1635
- let reject = this.initialReject;
1636
- this.initialResolve = false;
1637
- this.initialReject = false;
1638
- let err = new Error('Unexpected close');
1639
- err.code = `ClosedAfterConnect${this.secureConnection ? 'TLS' : 'Text'}`;
1640
- // still has to go through the logic below
1641
- setImmediate(() => reject(err));
1642
- }
1643
1647
 
1644
- if (typeof this.preCheck === 'function') {
1645
- this.preCheck().catch(err => this.log.warn({ err, cid: this.id }));
1646
- }
1648
+ this.usable = false;
1649
+ this.idling = false;
1647
1650
 
1648
- // reject command that is currently processed
1649
- if (this.currentRequest && this.requestTagMap.has(this.currentRequest.tag)) {
1650
- let request = this.requestTagMap.get(this.currentRequest.tag);
1651
- if (request) {
1652
- this.requestTagMap.delete(request.tag);
1653
- const error = new Error('Connection not available');
1654
- error.code = 'NoConnection';
1655
- request.reject(error);
1651
+ if (typeof this.initialReject === 'function' && !this.options.verifyOnly) {
1652
+ clearTimeout(this.greetingTimeout);
1653
+ let reject = this.initialReject;
1654
+ this.initialResolve = false;
1655
+ this.initialReject = false;
1656
+ let err = new Error('Unexpected close');
1657
+ err.code = `ClosedAfterConnect${this.secureConnection ? 'TLS' : 'Text'}`;
1658
+ // still has to go through the logic below
1659
+ setImmediate(() => reject(err));
1656
1660
  }
1657
- this.currentRequest = false;
1658
- }
1659
1661
 
1660
- // reject all other pending commands
1661
- while (this.requestQueue.length) {
1662
- let req = this.requestQueue.shift();
1663
- if (req && this.requestTagMap.has(req.tag)) {
1664
- let request = this.requestTagMap.get(req.tag);
1662
+ if (typeof this.preCheck === 'function') {
1663
+ this.preCheck().catch(err => this.log.warn({ err, cid: this.id }));
1664
+ }
1665
+
1666
+ // reject command that is currently processed
1667
+ if (this.currentRequest && this.requestTagMap.has(this.currentRequest.tag)) {
1668
+ let request = this.requestTagMap.get(this.currentRequest.tag);
1665
1669
  if (request) {
1666
1670
  this.requestTagMap.delete(request.tag);
1667
1671
  const error = new Error('Connection not available');
1668
1672
  error.code = 'NoConnection';
1669
1673
  request.reject(error);
1670
1674
  }
1675
+ this.currentRequest = false;
1676
+ }
1677
+
1678
+ // reject all other pending commands
1679
+ while (this.requestQueue.length) {
1680
+ let req = this.requestQueue.shift();
1681
+ if (req && this.requestTagMap.has(req.tag)) {
1682
+ let request = this.requestTagMap.get(req.tag);
1683
+ if (request) {
1684
+ this.requestTagMap.delete(request.tag);
1685
+ const error = new Error('Connection not available');
1686
+ error.code = 'NoConnection';
1687
+ request.reject(error);
1688
+ }
1689
+ }
1671
1690
  }
1672
- }
1673
1691
 
1674
- this.state = this.states.LOGOUT;
1675
- if (this.isClosed) {
1676
- return;
1677
- }
1678
- this.isClosed = true;
1692
+ // cleanup compression streams if they exist
1693
+ if (this._inflate) {
1694
+ try {
1695
+ this._inflate.unpipe();
1696
+ this._inflate.destroy();
1697
+ this._inflate = null;
1698
+ } catch (err) {
1699
+ this.log.error({ err, info: 'Failed to destroy inflate stream', cid: this.id });
1700
+ }
1701
+ }
1679
1702
 
1680
- if (this.writeSocket && !this.writeSocket.destroyed) {
1681
- try {
1682
- this.writeSocket.destroy();
1683
- } catch (err) {
1684
- this.log.error({ err, cid: this.id });
1703
+ if (this._deflate) {
1704
+ try {
1705
+ this._deflate.unpipe();
1706
+ this._deflate.destroy();
1707
+ this._deflate = null;
1708
+ } catch (err) {
1709
+ this.log.error({ err, info: 'Failed to destroy deflate stream', cid: this.id });
1710
+ }
1685
1711
  }
1686
- }
1687
1712
 
1688
- // cleanup compression streams if they exist
1689
- if (this._inflate) {
1690
- try {
1691
- this._inflate.unpipe();
1692
- this._inflate.destroy();
1693
- this._inflate = null;
1694
- } catch (err) {
1695
- this.log.error({ err, info: 'Failed to destroy inflate stream', cid: this.id });
1713
+ this.state = this.states.LOGOUT;
1714
+ if (this.isClosed) {
1715
+ return;
1696
1716
  }
1697
- }
1698
1717
 
1699
- if (this._deflate) {
1700
- try {
1701
- this._deflate.unpipe();
1702
- this._deflate.destroy();
1703
- this._deflate = null;
1704
- } catch (err) {
1705
- this.log.error({ err, info: 'Failed to destroy deflate stream', cid: this.id });
1718
+ if (this.socket && !this.socket.destroyed && this.writeSocket !== this.socket) {
1719
+ try {
1720
+ this.socket.destroy();
1721
+ } catch (err) {
1722
+ this.log.error({ err, cid: this.id });
1723
+ }
1706
1724
  }
1707
- }
1725
+ this.isClosed = true;
1708
1726
 
1709
- if (this.socket && !this.socket.destroyed && this.writeSocket !== this.socket) {
1710
- try {
1711
- this.socket.destroy();
1712
- } catch (err) {
1713
- this.log.error({ err, cid: this.id });
1727
+ if (this.writeSocket && !this.writeSocket.destroyed) {
1728
+ try {
1729
+ this.writeSocket.destroy();
1730
+ } catch (err) {
1731
+ this.log.error({ err, cid: this.id });
1732
+ }
1714
1733
  }
1715
- }
1716
1734
 
1717
- this.log.trace({ msg: 'Connection closed', cid: this.id });
1718
- this.emit('close');
1735
+ if (this.socket && !this.socket.destroyed && this.writeSocket !== this.socket) {
1736
+ try {
1737
+ this.socket.destroy();
1738
+ } catch (err) {
1739
+ this.log.error({ err, cid: this.id });
1740
+ }
1741
+ }
1742
+
1743
+ this.log.trace({ msg: 'Connection closed', cid: this.id });
1744
+ this.emit('close');
1745
+ } catch (ex) {
1746
+ // close failed
1747
+ this.log.error(ex);
1748
+ }
1719
1749
  }
1720
1750
 
1721
1751
  /**
@@ -2973,7 +3003,18 @@ class ImapFlow extends EventEmitter {
2973
3003
  };
2974
3004
 
2975
3005
  setImmediate(() => {
2976
- if (!writeChunk(chunk)) {
3006
+ let writeResult;
3007
+ try {
3008
+ writeResult = writeChunk(chunk);
3009
+ } catch (err) {
3010
+ stream.emit('error', err);
3011
+ if (!fetchAborted && stream && !stream.destroyed) {
3012
+ stream.end();
3013
+ }
3014
+ return;
3015
+ }
3016
+
3017
+ if (!writeResult) {
2977
3018
  // Initial chunk filled the buffer, wait for drain
2978
3019
  stream.once('drain', () => {
2979
3020
  if (!fetchAborted) {
@@ -3172,126 +3213,126 @@ class ImapFlow extends EventEmitter {
3172
3213
  return result;
3173
3214
  }
3174
3215
 
3175
- async processLocks(force) {
3176
- if (!force) {
3177
- // Atomic test-and-set to prevent race condition
3178
- const wasProcessing = this.processingLock;
3179
- this.processingLock = true;
3180
-
3181
- if (wasProcessing) {
3182
- // Another processor is already running, reset and exit
3183
- this.processingLock = wasProcessing;
3184
- this.log.trace({
3185
- msg: 'Mailbox locking queued',
3186
- path: this.mailbox && this.mailbox.path,
3187
- pending: this.locks.length,
3188
- idling: this.idling,
3189
- activeLock: this.currentLock
3190
- ? {
3191
- lockId: this.currentLock.lockId,
3192
- ...(this.currentLock.options?.description && { description: this.currentLock.options?.description })
3193
- }
3194
- : null
3195
- });
3196
- return;
3197
- }
3198
- } else {
3199
- this.processingLock = true;
3216
+ async processLocks() {
3217
+ // Atomic test-and-set to prevent race condition
3218
+ const wasProcessing = this.processingLock;
3219
+ if (wasProcessing) {
3220
+ // Another processor is already running, just exit
3221
+ this.log.trace({
3222
+ msg: 'Mailbox locking queued',
3223
+ path: this.mailbox && this.mailbox.path,
3224
+ pending: this.locks.length,
3225
+ idling: this.idling,
3226
+ activeLock: this.currentLock
3227
+ ? {
3228
+ lockId: this.currentLock.lockId,
3229
+ ...(this.currentLock.options?.description && { description: this.currentLock.options?.description })
3230
+ }
3231
+ : null
3232
+ });
3233
+ return;
3200
3234
  }
3235
+ this.processingLock = true;
3201
3236
 
3202
3237
  try {
3203
- if (!this.locks.length) {
3204
- this.log.trace({
3205
- msg: 'Mailbox locking queue processed',
3206
- idling: this.idling
3207
- });
3208
- return;
3209
- }
3210
-
3211
- const release = () => {
3212
- if (this.currentLock) {
3238
+ // Process all locks in queue until empty
3239
+ while (this.locks.length > 0) {
3240
+ if (!this.locks.length) {
3213
3241
  this.log.trace({
3214
- msg: 'Mailbox lock released',
3215
- lockId: this.currentLock.lockId,
3216
- path: this.mailbox && this.mailbox.path,
3217
- pending: this.locks.length,
3242
+ msg: 'Mailbox locking queue processed',
3218
3243
  idling: this.idling
3219
3244
  });
3220
- this.currentLock = false;
3245
+ return;
3221
3246
  }
3222
- this.processLocks(true).catch(err => this.log.error({ err, cid: this.id }));
3223
- };
3224
3247
 
3225
- const lock = this.locks.shift();
3226
- const { resolve, reject, path, options, lockId } = lock;
3227
-
3228
- if (!this.usable || !this.socket || this.socket.destroyed) {
3229
- this.log.trace({ msg: 'Failed to acquire mailbox lock', path, lockId, idling: this.idling });
3230
- // reject all
3231
- let error = new Error('Connection not available');
3232
- error.code = 'NoConnection';
3233
- reject(error);
3234
- // Process remaining locks
3235
- await this.processLocks(true);
3236
- return;
3237
- }
3248
+ const release = () => {
3249
+ if (this.currentLock) {
3250
+ this.log.trace({
3251
+ msg: 'Mailbox lock released',
3252
+ lockId: this.currentLock.lockId,
3253
+ path: this.mailbox && this.mailbox.path,
3254
+ pending: this.locks.length,
3255
+ idling: this.idling
3256
+ });
3257
+ this.currentLock = false;
3258
+ }
3259
+ // Use setImmediate to avoid stack overflow
3260
+ setImmediate(() => {
3261
+ this.processLocks().catch(err => this.log.error({ err, cid: this.id }));
3262
+ });
3263
+ };
3238
3264
 
3239
- if (this.mailbox && this.mailbox.path === path && !!this.mailbox.readOnly === !!options.readOnly) {
3240
- // nothing to do here, already selected
3241
- this.log.trace({
3242
- msg: 'Mailbox lock acquired [existing]',
3243
- path,
3244
- lockId,
3245
- idling: this.idling,
3246
- ...(options.description && { description: options.description })
3247
- });
3248
- this.currentLock = lock;
3249
- return resolve({ path, release });
3250
- } else {
3251
- try {
3252
- // Try to open. Throws if mailbox does not exists or can't open
3253
- await this.mailboxOpen(path, options);
3265
+ const lock = this.locks.shift();
3266
+ const { resolve, reject, path, options, lockId } = lock;
3267
+
3268
+ if (!this.usable || !this.socket || this.socket.destroyed) {
3269
+ this.log.trace({ msg: 'Failed to acquire mailbox lock', path, lockId, idling: this.idling });
3270
+ let error = new Error('Connection not available');
3271
+ error.code = 'NoConnection';
3272
+ reject(error);
3273
+ continue; // Process next lock in queue
3274
+ }
3275
+
3276
+ if (this.mailbox && this.mailbox.path === path && !!this.mailbox.readOnly === !!options.readOnly) {
3277
+ // nothing to do here, already selected
3254
3278
  this.log.trace({
3255
- msg: 'Mailbox lock acquired [selected]',
3279
+ msg: 'Mailbox lock acquired [existing]',
3256
3280
  path,
3257
3281
  lockId,
3258
3282
  idling: this.idling,
3259
3283
  ...(options.description && { description: options.description })
3260
3284
  });
3261
3285
  this.currentLock = lock;
3262
- return resolve({ path, release });
3263
- } catch (err) {
3264
- if (err.responseStatus === 'NO') {
3265
- try {
3266
- let folders = await this.run('LIST', '', path, { listOnly: true });
3267
- if (!folders || !folders.length) {
3268
- err.mailboxMissing = true;
3286
+ resolve({ path, release });
3287
+ break; // Wait for this lock to be released
3288
+ } else {
3289
+ try {
3290
+ // Try to open. Throws if mailbox does not exists or can't open
3291
+ await this.mailboxOpen(path, options);
3292
+ this.log.trace({
3293
+ msg: 'Mailbox lock acquired [selected]',
3294
+ path,
3295
+ lockId,
3296
+ idling: this.idling,
3297
+ ...(options.description && { description: options.description })
3298
+ });
3299
+ this.currentLock = lock;
3300
+ resolve({ path, release });
3301
+ break; // Wait for this lock to be released
3302
+ } catch (err) {
3303
+ if (err.responseStatus === 'NO') {
3304
+ try {
3305
+ let folders = await this.run('LIST', '', path, { listOnly: true });
3306
+ if (!folders || !folders.length) {
3307
+ err.mailboxMissing = true;
3308
+ }
3309
+ } catch (E) {
3310
+ this.log.trace({ msg: 'Failed to verify failed mailbox', path, err: E });
3269
3311
  }
3270
- } catch (E) {
3271
- this.log.trace({ msg: 'Failed to verify failed mailbox', path, err: E });
3272
3312
  }
3273
- }
3274
3313
 
3275
- this.log.trace({
3276
- msg: 'Failed to acquire mailbox lock',
3277
- path,
3278
- lockId,
3279
- idling: this.idling,
3280
- ...(options.description && { description: options.description }),
3281
- err
3282
- });
3283
- reject(err);
3284
- // Process remaining locks
3285
- await this.processLocks(true);
3314
+ this.log.trace({
3315
+ msg: 'Failed to acquire mailbox lock',
3316
+ path,
3317
+ lockId,
3318
+ idling: this.idling,
3319
+ ...(options.description && { description: options.description }),
3320
+ err
3321
+ });
3322
+ reject(err);
3323
+ // Continue to next lock in queue
3324
+ }
3286
3325
  }
3287
3326
  }
3288
3327
  } finally {
3289
3328
  this.processingLock = false;
3290
3329
 
3291
3330
  // Check if new locks were added while we were processing
3292
- if (this.locks.length && !this.processingLock) {
3293
- // Recursively process any new locks that were added
3294
- this.processLocks(true).catch(err => this.log.error({ err, cid: this.id }));
3331
+ if (this.locks.length && !this.currentLock) {
3332
+ // Process any remaining locks
3333
+ setImmediate(() => {
3334
+ this.processLocks().catch(err => this.log.error({ err, cid: this.id }));
3335
+ });
3295
3336
  }
3296
3337
  }
3297
3338
  }