Fix error handling on connection close

This commit is contained in:
Ben Gotow 2016-07-08 16:59:00 -07:00
parent 016bad67b9
commit 14b5bef0a7
2 changed files with 49 additions and 34 deletions

View file

@ -4,6 +4,14 @@ const _ = require('underscore');
const xoauth2 = require('xoauth2');
const EventEmitter = require('events');
class IMAPConnectionNotReadyError extends Error {
constructor(funcName) {
super(`${funcName} - You must call connect() first.`);
// hack so that the error matches the ones used by node-imap
this.source = 'socket';
}
}
class IMAPBox {
@ -123,28 +131,28 @@ class IMAPBox {
addFlags(range, flags) {
if (!this._imap) {
throw new Error(`IMAPBox::addFlags - You need to call connect() first.`)
throw new IMAPConnectionNotReadyError(`IMAPBox::addFlags`)
}
return this._imap.addFlagsAsync(range, flags)
}
delFlags(range, flags) {
if (!this._imap) {
throw new Error(`IMAPBox::delFlags - You need to call connect() first.`)
throw new IMAPConnectionNotReadyError(`IMAPBox::delFlags`)
}
return this._imap.delFlagsAsync(range, flags)
}
moveFromBox(range, folderName) {
if (!this._imap) {
throw new Error(`IMAPBox::moveFromBox - You need to call connect() first.`)
throw new IMAPConnectionNotReadyError(`IMAPBox::moveFromBox`)
}
return this._imap.moveAsync(range, folderName)
}
closeBox({expunge = true} = {}) {
if (!this._imap) {
throw new Error(`IMAPBox::closeBox - You need to call connect() first.`)
throw new IMAPConnectionNotReadyError(`IMAPBox::closeBox`)
}
return this._imap.closeBoxAsync(expunge)
}
@ -174,7 +182,8 @@ class IMAPConnection extends EventEmitter {
this._queue = [];
this._currentOperation = null;
this._settings = settings;
this._imap = null
this._imap = null;
this._connectPromise = null;
}
connect() {
@ -222,7 +231,9 @@ class IMAPConnection extends EventEmitter {
this._imap = Promise.promisifyAll(new Imap(settings));
this._imap.once('end', () => {
console.log('Connection ended');
console.log('Underlying IMAP Connection ended');
this._connectPromise = null;
this._imap = null;
});
this._imap.on('alert', (msg) => {
@ -255,11 +266,12 @@ class IMAPConnection extends EventEmitter {
this._queue = [];
this._imap.end();
this._imap = null;
this._connectPromise = null;
}
serverSupports(capability) {
if (!this._imap) {
throw new Error(`IMAPConnection::serverSupports - You need to call connect() first.`)
throw new IMAPConnectionNotReadyError(`IMAPConnection::serverSupports`)
}
this._imap.serverSupports(capability);
}
@ -269,7 +281,7 @@ class IMAPConnection extends EventEmitter {
*/
openBox(folderName, {readOnly = false} = {}) {
if (!this._imap) {
throw new Error(`IMAPConnection::openBox - You need to call connect() first.`)
throw new IMAPConnectionNotReadyError(`IMAPConnection::openBox`)
}
return this._imap.openBoxAsync(folderName, readOnly).then((box) =>
new IMAPBox(this._imap, box)
@ -278,35 +290,35 @@ class IMAPConnection extends EventEmitter {
getBoxes() {
if (!this._imap) {
throw new Error(`IMAPConnection::getBoxes - You need to call connect() first.`)
throw new IMAPConnectionNotReadyError(`IMAPConnection::getBoxes`)
}
return this._imap.getBoxesAsync()
}
addBox(folderName) {
if (!this._imap) {
throw new Error(`IMAPConnection::addBox - You need to call connect() first.`)
throw new IMAPConnectionNotReadyError(`IMAPConnection::addBox`)
}
return this._imap.addBoxAsync(folderName)
}
renameBox(oldFolderName, newFolderName) {
if (!this._imap) {
throw new Error(`IMAPConnection::renameBox - You need to call connect() first.`)
throw new IMAPConnectionNotReadyError(`IMAPConnection::renameBox`)
}
return this._imap.renameBoxAsync(oldFolderName, newFolderName)
}
delBox(folderName) {
if (!this._imap) {
throw new Error(`IMAPConnection::delBox - You need to call connect() first.`)
throw new IMAPConnectionNotReadyError(`IMAPConnection::delBox`)
}
return this._imap.delBoxAsync(folderName)
}
runOperation(operation) {
if (!this._imap) {
throw new Error(`IMAPConnection::runOperation - You need to call connect() first.`)
throw new IMAPConnectionNotReadyError(`IMAPConnection::runOperation`)
}
return new Promise((resolve, reject) => {
this._queue.push({operation, resolve, reject});
@ -317,11 +329,13 @@ class IMAPConnection extends EventEmitter {
}
processNextOperation() {
if (this._currentOperation) { return }
if (this._currentOperation) {
return;
}
this._currentOperation = this._queue.shift();
if (!this._currentOperation) {
this.emit('queue-empty');
return
return;
}
const {operation, resolve, reject} = this._currentOperation;
@ -329,8 +343,8 @@ class IMAPConnection extends EventEmitter {
if (result instanceof Promise === false) {
reject(new Error(`Expected ${operation.constructor.name} to return promise.`))
}
result
.then(() => {
result.then(() => {
this._currentOperation = null;
console.log(`Finished task: ${operation.description()}`)
resolve();
@ -344,6 +358,6 @@ class IMAPConnection extends EventEmitter {
})
}
}
IMAPConnection.Capabilities = Capabilities;
IMAPConnection.Capabilities = Capabilities;
module.exports = IMAPConnection

View file

@ -45,7 +45,6 @@ class SyncWorker {
if (this._conn) {
this._conn.end();
}
this._conn = null
}
_onMessage(msg) {
@ -61,15 +60,19 @@ class SyncWorker {
}
_onAccountUpdated() {
if (this.isNextSyncScheduled()) {
this._getAccount().then((account) => {
this._account = account;
this.syncNow({reason: 'Account Modification'});
});
if (!this.isWaitingForNextSync()) {
return;
}
this._getAccount().then((account) => {
this._account = account;
this.syncNow({reason: 'Account Modification'});
});
}
_onConnectionIdleUpdate() {
if (!this.isWaitingForNextSync()) {
return;
}
this.syncNow({reason: 'IMAP IDLE Fired'});
}
@ -127,7 +130,8 @@ class SyncWorker {
.catch((error) => {
syncbackRequest.error = error
syncbackRequest.status = "FAILED"
}).finally(() => syncbackRequest.save())
})
.finally(() => syncbackRequest.save())
}
syncAllCategories() {
@ -146,12 +150,6 @@ class SyncWorker {
});
}
performSync() {
return this.syncbackMessageActions()
.then(() => this._conn.runOperation(new FetchFolderList(this._account.provider)))
.then(() => this.syncAllCategories())
}
syncNow({reason} = {}) {
clearTimeout(this._syncTimer);
this._syncTimer = null;
@ -164,7 +162,9 @@ class SyncWorker {
this.ensureConnection()
.then(() => this._account.update({syncError: null}))
.then(() => this.performSync())
.then(() => this.syncbackMessageActions())
.then(() => this._conn.runOperation(new FetchFolderList(this._account.provider)))
.then(() => this.syncAllCategories())
.then(() => this.onSyncDidComplete())
.catch((error) => this.onSyncError(error))
.finally(() => {
@ -177,10 +177,11 @@ class SyncWorker {
console.error(`SyncWorker: Error while syncing account ${this._account.emailAddress} (${this._account.id})`, error)
this.closeConnection()
if (error.source === 'socket') {
if (error.source.includes('socket') || error.source.includes('timeout')) {
// Continue to retry if it was a network error
return Promise.resolve()
}
this._account.syncError = jsonError(error)
return this._account.save()
}
@ -219,7 +220,7 @@ class SyncWorker {
throw new Error(`SyncWorker.onSyncDidComplete: Unknown afterSync behavior: ${afterSync}. Closing connection`)
}
isNextSyncScheduled() {
isWaitingForNextSync() {
return this._syncTimer != null;
}