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 = []; }; OutboxBO.prototype.init = function() { var outboxFolder = _.findWhere(this._emailDao._account.folders, { type: 'Outbox' }); outboxFolder.messages = 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; }; /** * Private Api which is called whenever a message has been sent * The public callback "onSent" can be set by the caller to get notified. */ OutboxBO.prototype._onSent = function(message) { if (typeof this.onSent === 'function') { this.onSent(message); } }; /** * 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; // fill all the pending mails into the pending mails array self.pendingEmails.length = 0; //fastest way to empty an array pending.forEach(function(i) { self.pendingEmails.push(i); }); // we're not online, don't even bother sending mails if (!self._emailDao._account.online) { self._outboxBusy = false; callback(null, self.pendingEmails.length); return; } // 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' }; // send invitation mail self._emailDao.sendPlaintext({ email: invitationMail }, function(err) { if (err) { self._outboxBusy = false; if (err.code === 42) { // offline try again later callback(); } else { callback(err); } return; } // fire sent notification self._onSent(invitationMail); invitationFinished(); }); } } function sendEncrypted(email) { self._emailDao.sendEncrypted({ email: email }, function(err) { if (err) { self._outboxBusy = false; if (err.code === 42) { // offline try again later callback(); } else { callback(err); } return; } // the email has been sent, remove from pending mails removeFromPendingMails(email); // fire sent notification self._onSent(email); 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; });