mail/src/js/bo/outbox.js

266 lines
9.0 KiB
JavaScript

define(function(require) {
'use strict';
var _ = require('underscore'),
str = require('js/app-config').string,
config = require('js/app-config').config,
InvitationDAO = require('js/dao/invitation-dao'),
dbType = 'email_OUTBOX';
/**
* High level business object that orchestrates the local outbox.
* The local outbox takes care of the emails before they are being sent.
* It also checks periodically if there are any mails in the local device storage to be sent.
*/
var OutboxBO = function(emailDao, keychain, devicestorage, invitationDao) {
/** @private */
this._emailDao = emailDao;
/** @private */
this._keychain = keychain;
/** @private */
this._devicestorage = devicestorage;
/** @private */
this._invitationDao = invitationDao;
/**
* Semaphore-esque flag to avoid 'concurrent' calls to _processOutbox when the timeout fires, but a call is still in process.
* @private */
this._outboxBusy = false;
/**
* Pending, unsent emails stored in the outbox. Updated on each call to _processOutbox
* @public */
this.pendingEmails = [];
};
/**
* This function activates the periodic checking of the local device storage for pending mails.
* @param {Function} callback(error, pendingMailsCount) Callback that informs you about the count of pending mails.
*/
OutboxBO.prototype.startChecking = function(callback) {
// start periodic checking of outbox
this._intervalId = setInterval(this._processOutbox.bind(this, callback), config.checkOutboxInterval);
};
/**
* Outbox stops the periodic checking of the local device storage for pending mails.
*/
OutboxBO.prototype.stopChecking = function() {
if (!this._intervalId) {
return;
}
clearInterval(this._intervalId);
delete this._intervalId;
};
/**
* Checks the local device storage for pending mails.
* @param {Function} callback(error, pendingMailsCount) Callback that informs you about the count of pending mails.
*/
OutboxBO.prototype._processOutbox = function(callback) {
var self = this,
emails;
// if a _processOutbox call is still in progress when a new timeout kicks
// in, since sending mails might take time, ignore it. otherwise, mails
// could get sent multiple times
if (self._outboxBusy) {
return;
}
checkStorage();
function checkStorage() {
self._outboxBusy = true;
// get last item from outbox
self._emailDao.list(function(err, pending) {
if (err) {
self._outboxBusy = false;
callback(err);
return;
}
// update outbox folder count
emails = pending;
// keep an independent shallow copy of the pending mails array in the member
self.pendingEmails = pending.slice();
// sending pending mails
processMails();
});
}
// process the next pending mail
function processMails() {
if (emails.length === 0) {
// in the navigation controller, this updates the folder count
self._outboxBusy = false;
callback(null, self.pendingEmails.length);
return;
}
// in the navigation controller, this updates the folder count
callback(null, self.pendingEmails.length);
var email = emails.shift();
checkReceivers(email);
}
// check whether there are unregistered receivers, i.e. receivers without a public key
function checkReceivers(email) {
var unregisteredUsers, receiverChecked;
unregisteredUsers = [];
receiverChecked = _.after(email.to.length, function() {
// invite unregistered users if necessary
if (unregisteredUsers.length > 0) {
invite(unregisteredUsers);
return;
}
sendEncrypted(email);
});
// find out if there are unregistered users
email.to.forEach(function(recipient) {
self._keychain.getReceiverPublicKey(recipient.address, function(err, key) {
if (err) {
self._outboxBusy = false;
callback(err);
return;
}
if (!key) {
unregisteredUsers.push(recipient);
}
receiverChecked();
});
});
}
// invite the unregistered receivers, if necessary
function invite(addresses) {
var sender = self._emailDao._account.emailAddress;
var invitationFinished = _.after(addresses.length, function() {
// after all of the invitations are checked and sent (if necessary),
processMails();
});
// check which of the adresses has pending invitations
addresses.forEach(function(recipient) {
var recipientAddress = recipient.address;
self._invitationDao.check({
recipient: recipientAddress,
sender: sender
}, function(err, status) {
if (err) {
self._outboxBusy = false;
callback(err);
return;
}
if (status === InvitationDAO.INVITE_PENDING) {
// the recipient is already invited, we're done here.
invitationFinished();
return;
}
// the recipient is not yet invited, so let's do that
self._invitationDao.invite({
recipient: recipientAddress,
sender: sender
}, function(err, status) {
if (err) {
self._outboxBusy = false;
callback(err);
return;
}
if (status !== InvitationDAO.INVITE_SUCCESS) {
self._outboxBusy = false;
callback({
errMsg: 'could not successfully invite ' + recipientAddress
});
return;
}
sendInvitationMail(recipient, sender);
});
});
});
// send an invitation to the unregistered user, aka the recipient
function sendInvitationMail(recipient, sender) {
var to = (recipient.name || recipient.address).split('@')[0].split('.')[0].split(' ')[0],
invitationMail = {
from: [sender],
to: [recipient],
subject: str.invitationSubject,
body: 'Hi ' + to + ',\n\n' + str.invitationMessage + '\n\n\n' + str.signature
};
// send invitation mail
self._emailDao.send(invitationMail, function(err) {
if (err) {
self._outboxBusy = false;
callback(err);
return;
}
invitationFinished();
});
}
}
function sendEncrypted(email) {
removeFromPendingMails(email);
self._emailDao.encryptedSend(email, function(err) {
if (err) {
self._outboxBusy = false;
callback(err);
return;
}
removeFromStorage(email.id);
});
}
// update the member so that the outbox can visualize
function removeFromPendingMails(email) {
var i = self.pendingEmails.indexOf(email);
self.pendingEmails.splice(i, 1);
}
function removeFromStorage(id) {
if (!id) {
self._outboxBusy = false;
callback({
errMsg: 'Cannot remove email from storage without a valid id!'
});
return;
}
// delete email from local storage
var key = dbType + '_' + id;
self._devicestorage.removeList(key, function(err) {
if (err) {
self._outboxBusy = false;
callback(err);
return;
}
processMails();
});
}
};
return OutboxBO;
});