mirror of
https://github.com/moparisthebest/mail
synced 2024-11-26 02:42:17 -05:00
Merge pull request #25 from whiteout-io/dev/offline-bug
Dev/offline bug
This commit is contained in:
commit
86c0f04973
16
Gruntfile.js
16
Gruntfile.js
@ -133,15 +133,17 @@ module.exports = function(grunt) {
|
|||||||
'imap-client/node_modules/inbox/node_modules/node-shims/src/*.js',
|
'imap-client/node_modules/inbox/node_modules/node-shims/src/*.js',
|
||||||
'imap-client/node_modules/inbox/node_modules/utf7/src/utf7.js',
|
'imap-client/node_modules/inbox/node_modules/utf7/src/utf7.js',
|
||||||
'imap-client/node_modules/inbox/node_modules/xoauth2/src/xoauth2.js',
|
'imap-client/node_modules/inbox/node_modules/xoauth2/src/xoauth2.js',
|
||||||
'imap-client/node_modules/mimelib/src/mimelib.js',
|
'mailreader/src/*.js',
|
||||||
'imap-client/node_modules/mimelib/node_modules/addressparser/src/addressparser.js',
|
'mailreader/node_modules/mailparser/src/*.js',
|
||||||
'imap-client/node_modules/mimelib/node_modules/encoding/src/encoding.js',
|
'mailreader/node_modules/mailparser/node_modules/encoding/src/encoding.js',
|
||||||
'imap-client/node_modules/mimelib/node_modules/encoding/node_modules/iconv-lite/src/*.js',
|
'mailreader/node_modules/mailparser/node_modules/mimelib/src/mimelib.js',
|
||||||
'imap-client/node_modules/mailparser/src/*.js',
|
'mailreader/node_modules/mailparser/node_modules/mimelib/node_modules/addressparser/src/addressparser.js',
|
||||||
'imap-client/node_modules/mime/src/mime.js',
|
'mailreader/node_modules/mailparser/node_modules/encoding/node_modules/iconv-lite/src/*.js',
|
||||||
|
'mailreader/node_modules/mailparser/node_modules/mime/src/mime.js',
|
||||||
'pgpmailer/src/*.js',
|
'pgpmailer/src/*.js',
|
||||||
'pgpmailer/node_modules/simplesmtp/src/*',
|
'pgpmailer/node_modules/simplesmtp/src/*',
|
||||||
'pgpmailer/node_modules/mailbuilder/src/*.js'
|
'pgpbuilder/src/*.js',
|
||||||
|
'pgpbuilder/node_modules/mailbuilder/src/*.js'
|
||||||
],
|
],
|
||||||
dest: 'src/lib/'
|
dest: 'src/lib/'
|
||||||
},
|
},
|
||||||
|
@ -12,7 +12,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"crypto-lib": "https://github.com/whiteout-io/crypto-lib/tarball/master",
|
"crypto-lib": "https://github.com/whiteout-io/crypto-lib/tarball/master",
|
||||||
"imap-client": "https://github.com/whiteout-io/imap-client/tarball/master",
|
"imap-client": "https://github.com/whiteout-io/imap-client/tarball/master",
|
||||||
|
"mailreader": "https://github.com/whiteout-io/mailreader/tarball/master",
|
||||||
"pgpmailer": "https://github.com/whiteout-io/pgpmailer/tarball/master",
|
"pgpmailer": "https://github.com/whiteout-io/pgpmailer/tarball/master",
|
||||||
|
"pgpbuilder": "https://github.com/whiteout-io/pgpbuilder/tarball/master",
|
||||||
"requirejs": "2.1.10"
|
"requirejs": "2.1.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -33,4 +35,4 @@
|
|||||||
"grunt-contrib-copy": "~0.4.1",
|
"grunt-contrib-copy": "~0.4.1",
|
||||||
"grunt-contrib-compress": "~0.5.2"
|
"grunt-contrib-compress": "~0.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ define(function(require) {
|
|||||||
|
|
||||||
var $ = require('jquery'),
|
var $ = require('jquery'),
|
||||||
ImapClient = require('imap-client'),
|
ImapClient = require('imap-client'),
|
||||||
|
mailreader = require('mailreader'),
|
||||||
PgpMailer = require('pgpmailer'),
|
PgpMailer = require('pgpmailer'),
|
||||||
EmailDAO = require('js/dao/email-dao'),
|
EmailDAO = require('js/dao/email-dao'),
|
||||||
RestDAO = require('js/dao/rest-dao'),
|
RestDAO = require('js/dao/rest-dao'),
|
||||||
@ -16,6 +17,7 @@ define(function(require) {
|
|||||||
InvitationDAO = require('js/dao/invitation-dao'),
|
InvitationDAO = require('js/dao/invitation-dao'),
|
||||||
OutboxBO = require('js/bo/outbox'),
|
OutboxBO = require('js/bo/outbox'),
|
||||||
PGP = require('js/crypto/pgp'),
|
PGP = require('js/crypto/pgp'),
|
||||||
|
PgpBuilder = require('pgpbuilder'),
|
||||||
config = require('js/app-config').config;
|
config = require('js/app-config').config;
|
||||||
|
|
||||||
var self = {};
|
var self = {};
|
||||||
@ -116,8 +118,8 @@ define(function(require) {
|
|||||||
onError: console.error
|
onError: console.error
|
||||||
};
|
};
|
||||||
|
|
||||||
imapClient = new ImapClient(imapOptions);
|
imapClient = new ImapClient(imapOptions, mailreader);
|
||||||
pgpMailer = new PgpMailer(smtpOptions);
|
pgpMailer = new PgpMailer(smtpOptions, self._pgpbuilder);
|
||||||
|
|
||||||
imapClient.onError = function(err) {
|
imapClient.onError = function(err) {
|
||||||
console.log('IMAP error.', err);
|
console.log('IMAP error.', err);
|
||||||
@ -341,7 +343,7 @@ define(function(require) {
|
|||||||
|
|
||||||
self.buildModules = function() {
|
self.buildModules = function() {
|
||||||
var lawnchairDao, restDao, pubkeyDao, invitationDao,
|
var lawnchairDao, restDao, pubkeyDao, invitationDao,
|
||||||
emailDao, keychain, pgp, userStorage;
|
emailDao, keychain, pgp, userStorage, pgpbuilder;
|
||||||
|
|
||||||
// init objects and inject dependencies
|
// init objects and inject dependencies
|
||||||
restDao = new RestDAO();
|
restDao = new RestDAO();
|
||||||
@ -354,7 +356,8 @@ define(function(require) {
|
|||||||
self._keychain = keychain;
|
self._keychain = keychain;
|
||||||
pgp = new PGP();
|
pgp = new PGP();
|
||||||
self._crypto = pgp;
|
self._crypto = pgp;
|
||||||
self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage);
|
self._pgpbuilder = pgpbuilder = new PgpBuilder();
|
||||||
|
self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader);
|
||||||
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage, invitationDao);
|
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage, invitationDao);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -385,9 +388,6 @@ define(function(require) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// init outbox
|
|
||||||
self._outboxBo.init();
|
|
||||||
|
|
||||||
callback(null, keypair);
|
callback(null, keypair);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,10 +2,11 @@ define(function(require) {
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var _ = require('underscore'),
|
var _ = require('underscore'),
|
||||||
|
util = require('cryptoLib/util'),
|
||||||
str = require('js/app-config').string,
|
str = require('js/app-config').string,
|
||||||
config = require('js/app-config').config,
|
config = require('js/app-config').config,
|
||||||
InvitationDAO = require('js/dao/invitation-dao'),
|
InvitationDAO = require('js/dao/invitation-dao'),
|
||||||
dbType = 'email_OUTBOX';
|
outboxDb = 'email_OUTBOX';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* High level business object that orchestrates the local outbox.
|
* High level business object that orchestrates the local outbox.
|
||||||
@ -30,18 +31,6 @@ define(function(require) {
|
|||||||
* Semaphore-esque flag to avoid 'concurrent' calls to _processOutbox when the timeout fires, but a call is still in process.
|
* Semaphore-esque flag to avoid 'concurrent' calls to _processOutbox when the timeout fires, but a call is still in process.
|
||||||
* @private */
|
* @private */
|
||||||
this._outboxBusy = false;
|
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,12 +55,60 @@ define(function(require) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private Api which is called whenever a message has been sent
|
* Put a email dto in the outbox for sending when ready
|
||||||
* The public callback "onSent" can be set by the caller to get notified.
|
* @param {Object} mail The Email DTO
|
||||||
|
* @param {Function} callback Invoked when the object was encrypted and persisted to disk
|
||||||
*/
|
*/
|
||||||
OutboxBO.prototype._onSent = function(message) {
|
OutboxBO.prototype.put = function(mail, callback) {
|
||||||
if (typeof this.onSent === 'function') {
|
var self = this,
|
||||||
this.onSent(message);
|
allReaders = mail.from.concat(mail.to.concat(mail.cc.concat(mail.bcc))); // all the users that should be able to read the mail
|
||||||
|
|
||||||
|
mail.publicKeysArmored = []; // gather the public keys
|
||||||
|
mail.unregisteredUsers = []; // gather the recipients for which no public key is available
|
||||||
|
mail.id = util.UUID(); // the mail needs a random uuid for storage in the database
|
||||||
|
|
||||||
|
checkRecipients(allReaders);
|
||||||
|
|
||||||
|
// check if there are unregistered recipients
|
||||||
|
function checkRecipients(recipients) {
|
||||||
|
var after = _.after(recipients.length, function() {
|
||||||
|
encryptAndPersist();
|
||||||
|
});
|
||||||
|
|
||||||
|
// find out if there are unregistered users
|
||||||
|
recipients.forEach(function(recipient) {
|
||||||
|
self._keychain.getReceiverPublicKey(recipient.address, function(err, key) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if a public key is available, add the recipient's key to the armored public keys,
|
||||||
|
// otherwise remember the recipient as unregistered for later sending
|
||||||
|
if (key) {
|
||||||
|
mail.publicKeysArmored.push(key.publicKey);
|
||||||
|
} else {
|
||||||
|
mail.unregisteredUsers.push(recipient);
|
||||||
|
}
|
||||||
|
|
||||||
|
after();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypts the body and attachments and persists the mail object
|
||||||
|
function encryptAndPersist() {
|
||||||
|
self._emailDao.encrypt({
|
||||||
|
mail: mail,
|
||||||
|
publicKeysArmored: mail.publicKeysArmored
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self._devicestorage.storeList([mail], outboxDb, callback);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,152 +118,213 @@ define(function(require) {
|
|||||||
*/
|
*/
|
||||||
OutboxBO.prototype._processOutbox = function(callback) {
|
OutboxBO.prototype._processOutbox = function(callback) {
|
||||||
var self = this,
|
var self = this,
|
||||||
emails;
|
unsentMails = 0;
|
||||||
|
|
||||||
// if a _processOutbox call is still in progress when a new timeout kicks
|
// also, if a _processOutbox call is still in progress, ignore it.
|
||||||
// in, since sending mails might take time, ignore it. otherwise, mails
|
|
||||||
// could get sent multiple times
|
|
||||||
if (self._outboxBusy) {
|
if (self._outboxBusy) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkStorage();
|
self._outboxBusy = true;
|
||||||
|
|
||||||
function checkStorage() {
|
// get pending mails from the outbox
|
||||||
self._outboxBusy = true;
|
self._devicestorage.listItems(outboxDb, 0, null, function(err, pendingMails) {
|
||||||
|
// error, we're done here
|
||||||
|
if (err) {
|
||||||
|
self._outboxBusy = false;
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// get last item from outbox
|
// if we're not online, don't even bother sending mails.
|
||||||
self._emailDao.listForOutbox(function(err, pending) {
|
if (!self._emailDao._account.online || _.isEmpty(pendingMails)) {
|
||||||
|
self._outboxBusy = false;
|
||||||
|
callback(null, pendingMails.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're done after all the mails have been handled
|
||||||
|
// update the outbox count...
|
||||||
|
var after = _.after(pendingMails.length, function() {
|
||||||
|
self._outboxBusy = false;
|
||||||
|
callback(null, unsentMails);
|
||||||
|
});
|
||||||
|
|
||||||
|
// send pending mails if possible
|
||||||
|
pendingMails.forEach(function(mail) {
|
||||||
|
handleMail(mail, after);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// if we can send the mail, do that. otherwise check if there are users that need to be invited
|
||||||
|
function handleMail(mail, done) {
|
||||||
|
// no unregistered users, go straight to send
|
||||||
|
if (mail.unregisteredUsers.length === 0) {
|
||||||
|
send(mail, done);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var after = _.after(mail.unregisteredUsers.length, function() {
|
||||||
|
// invite unregistered users if necessary
|
||||||
|
if (mail.unregisteredUsers.length > 0) {
|
||||||
|
unsentMails++;
|
||||||
|
self._invite({
|
||||||
|
sender: mail.from[0],
|
||||||
|
recipients: mail.unregisteredUsers
|
||||||
|
}, done);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// there are public keys available for the missing users,
|
||||||
|
// so let's re-encrypt the mail for them and send it
|
||||||
|
reencryptAndSend(mail, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
// find out if the unregistered users have registered in the meantime
|
||||||
|
mail.unregisteredUsers.forEach(function(recipient) {
|
||||||
|
self._keychain.getReceiverPublicKey(recipient.address, function(err, key) {
|
||||||
|
var index;
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
self._outboxBusy = false;
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
// remove the newly joined users from the unregistered users
|
||||||
|
index = mail.unregisteredUsers.indexOf(recipient);
|
||||||
|
mail.unregisteredUsers.splice(index, 1);
|
||||||
|
mail.publicKeysArmored.push(key.publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
after();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// all the recipients have public keys available, so let's re-encrypt the mail
|
||||||
|
// to make it available for them, too
|
||||||
|
function reencryptAndSend(mail, done) {
|
||||||
|
self._emailDao.reEncrypt({
|
||||||
|
mail: mail,
|
||||||
|
publicKeysArmored: mail.publicKeysArmored
|
||||||
|
}, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
self._outboxBusy = false;
|
self._outboxBusy = false;
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update outbox folder count
|
// stores the newly encrypted mail object to disk in case something funky
|
||||||
emails = pending;
|
// happens during sending and we need do re-send the mail later.
|
||||||
|
// avoids doing the encryption twice...
|
||||||
|
self._devicestorage.storeList([mail], outboxDb, function(err) {
|
||||||
|
if (err) {
|
||||||
|
self._outboxBusy = false;
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// fill all the pending mails into the pending mails array
|
send(mail, done);
|
||||||
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
|
// send the encrypted message
|
||||||
if (!self._emailDao._account.online) {
|
function send(mail, done) {
|
||||||
|
self._emailDao.sendEncrypted({
|
||||||
|
email: mail
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
self._outboxBusy = false;
|
self._outboxBusy = false;
|
||||||
callback(null, self.pendingEmails.length);
|
if (err.code === 42) {
|
||||||
|
// offline try again later
|
||||||
|
done();
|
||||||
|
} else {
|
||||||
|
self._outboxBusy = false;
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sending pending mails
|
// remove the pending mail from the storage
|
||||||
processMails();
|
removeFromStorage(mail, done);
|
||||||
|
|
||||||
|
// fire sent notification
|
||||||
|
if (typeof self.onSent === 'function') {
|
||||||
|
self.onSent(mail);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// process the next pending mail
|
// removes the mail object from disk after successfully sending it
|
||||||
function processMails() {
|
function removeFromStorage(mail, done) {
|
||||||
if (emails.length === 0) {
|
self._devicestorage.removeList(outboxDb + '_' + mail.id, function(err) {
|
||||||
// in the navigation controller, this updates the folder count
|
if (err) {
|
||||||
self._outboxBusy = false;
|
self._outboxBusy = false;
|
||||||
callback(null, self.pendingEmails.length);
|
callback(err);
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEncrypted(email);
|
done();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// find out if there are unregistered users
|
/**
|
||||||
email.to.forEach(function(recipient) {
|
* Sends an invitation mail to an array of users that have no public key available yet
|
||||||
self._keychain.getReceiverPublicKey(recipient.address, function(err, key) {
|
* @param {Array} recipients Array of objects with information on the sender (name, address)
|
||||||
if (err) {
|
* @param {Function} callback Invoked when the mail was sent
|
||||||
self._outboxBusy = false;
|
*/
|
||||||
callback(err);
|
OutboxBO.prototype._invite = function(options, callback) {
|
||||||
return;
|
var self = this,
|
||||||
}
|
sender = options.sender;
|
||||||
|
|
||||||
if (!key) {
|
var after = _.after(options.recipients.length, callback);
|
||||||
unregisteredUsers.push(recipient);
|
|
||||||
}
|
|
||||||
|
|
||||||
receiverChecked();
|
options.recipients.forEach(function(recipient) {
|
||||||
});
|
checkInvitationStatus(recipient, after);
|
||||||
|
});
|
||||||
|
|
||||||
|
// checks the invitation status. if an invitation is pending, we do not need to resend the invitation mail
|
||||||
|
function checkInvitationStatus(recipient, done) {
|
||||||
|
self._invitationDao.check({
|
||||||
|
recipient: recipient.address,
|
||||||
|
sender: sender.address
|
||||||
|
}, function(err, status) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === InvitationDAO.INVITE_PENDING) {
|
||||||
|
// the recipient is already invited, we're done here.
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
invite(recipient, done);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// invite the unregistered receivers, if necessary
|
// let's invite the recipient and send him a mail to inform him to join whiteout
|
||||||
function invite(addresses) {
|
function invite(recipient, done) {
|
||||||
var sender = self._emailDao._account.emailAddress;
|
self._invitationDao.invite({
|
||||||
|
recipient: recipient.address,
|
||||||
var invitationFinished = _.after(addresses.length, function() {
|
sender: sender.address
|
||||||
// after all of the invitations are checked and sent (if necessary),
|
}, function(err, status) {
|
||||||
processMails();
|
if (err) {
|
||||||
});
|
callback(err);
|
||||||
|
return;
|
||||||
// check which of the adresses has pending invitations
|
}
|
||||||
addresses.forEach(function(recipient) {
|
if (status !== InvitationDAO.INVITE_SUCCESS) {
|
||||||
var recipientAddress = recipient.address;
|
callback({
|
||||||
|
errMsg: 'Could not successfully invite ' + recipient
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// send an invitation to the unregistered user, aka the recipient
|
|
||||||
function sendInvitationMail(recipient, sender) {
|
|
||||||
var invitationMail = {
|
var invitationMail = {
|
||||||
from: [sender],
|
from: [sender],
|
||||||
to: [recipient],
|
to: [recipient],
|
||||||
@ -239,10 +337,9 @@ define(function(require) {
|
|||||||
email: invitationMail
|
email: invitationMail
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
self._outboxBusy = false;
|
|
||||||
if (err.code === 42) {
|
if (err.code === 42) {
|
||||||
// offline try again later
|
// offline try again later
|
||||||
callback();
|
done();
|
||||||
} else {
|
} else {
|
||||||
callback(err);
|
callback(err);
|
||||||
}
|
}
|
||||||
@ -250,63 +347,12 @@ define(function(require) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fire sent notification
|
// fire sent notification
|
||||||
self._onSent(invitationMail);
|
if (typeof self.onSent === 'function') {
|
||||||
|
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
|
done();
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -59,11 +59,6 @@ define(function(require) {
|
|||||||
//
|
//
|
||||||
|
|
||||||
$scope.getBody = function(email) {
|
$scope.getBody = function(email) {
|
||||||
// don't stream message content of outbox messages...
|
|
||||||
if (getFolder().type === 'Outbox') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
emailDao.getBody({
|
emailDao.getBody({
|
||||||
folder: getFolder().path,
|
folder: getFolder().path,
|
||||||
message: email
|
message: email
|
||||||
@ -78,7 +73,7 @@ define(function(require) {
|
|||||||
|
|
||||||
// automatically decrypt if it's the selected email
|
// automatically decrypt if it's the selected email
|
||||||
if (email === $scope.state.mailList.selected) {
|
if (email === $scope.state.mailList.selected) {
|
||||||
emailDao.decryptMessageContent({
|
emailDao.decryptBody({
|
||||||
message: email
|
message: email
|
||||||
}, $scope.onError);
|
}, $scope.onError);
|
||||||
}
|
}
|
||||||
@ -98,12 +93,9 @@ define(function(require) {
|
|||||||
$scope.state.mailList.selected = email;
|
$scope.state.mailList.selected = email;
|
||||||
$scope.state.read.toggle(true);
|
$scope.state.read.toggle(true);
|
||||||
|
|
||||||
// if we're in the outbox, don't decrypt as usual
|
emailDao.decryptBody({
|
||||||
if (getFolder().type !== 'Outbox') {
|
message: email
|
||||||
emailDao.decryptMessageContent({
|
}, $scope.onError);
|
||||||
message: email
|
|
||||||
}, $scope.onError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the email is unread, please sync the new state.
|
// if the email is unread, please sync the new state.
|
||||||
// otherweise forget about it.
|
// otherweise forget about it.
|
||||||
@ -127,19 +119,21 @@ define(function(require) {
|
|||||||
* Synchronize the selected imap folder to local storage
|
* Synchronize the selected imap folder to local storage
|
||||||
*/
|
*/
|
||||||
$scope.synchronize = function(callback) {
|
$scope.synchronize = function(callback) {
|
||||||
// if we're in the outbox, don't do an imap sync
|
|
||||||
if (getFolder().type === 'Outbox') {
|
|
||||||
updateStatus('Last update: ', new Date());
|
|
||||||
selectFirstMessage(outboxBo.pendingEmails);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStatus('Syncing ...');
|
updateStatus('Syncing ...');
|
||||||
|
|
||||||
// let email dao handle sync transparently
|
// let email dao handle sync transparently
|
||||||
emailDao.sync({
|
if ($scope.state.nav.currentFolder.type === 'Outbox') {
|
||||||
folder: getFolder().path
|
emailDao.syncOutbox({
|
||||||
}, function(err) {
|
folder: getFolder().path
|
||||||
|
}, done);
|
||||||
|
} else {
|
||||||
|
emailDao.sync({
|
||||||
|
folder: getFolder().path
|
||||||
|
}, done);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function done(err) {
|
||||||
if (err && err.code === 409) {
|
if (err && err.code === 409) {
|
||||||
// sync still busy
|
// sync still busy
|
||||||
return;
|
return;
|
||||||
@ -167,7 +161,7 @@ define(function(require) {
|
|||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -178,17 +172,7 @@ define(function(require) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var index, currentFolder, outboxFolder;
|
if (getFolder().type === 'Outbox') {
|
||||||
|
|
||||||
currentFolder = getFolder();
|
|
||||||
// trashFolder = _.findWhere($scope.folders, {
|
|
||||||
// type: 'Trash'
|
|
||||||
// });
|
|
||||||
outboxFolder = _.findWhere($scope.account.folders, {
|
|
||||||
type: 'Outbox'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (currentFolder === outboxFolder) {
|
|
||||||
$scope.onError({
|
$scope.onError({
|
||||||
errMsg: 'Deleting messages from the outbox is not yet supported.'
|
errMsg: 'Deleting messages from the outbox is not yet supported.'
|
||||||
});
|
});
|
||||||
@ -199,7 +183,7 @@ define(function(require) {
|
|||||||
$scope.synchronize();
|
$scope.synchronize();
|
||||||
|
|
||||||
function removeAndShowNext() {
|
function removeAndShowNext() {
|
||||||
index = getFolder().messages.indexOf(email);
|
var index = getFolder().messages.indexOf(email);
|
||||||
// show the next mail
|
// show the next mail
|
||||||
if (getFolder().messages.length > 1) {
|
if (getFolder().messages.length > 1) {
|
||||||
// if we're about to delete the last entry of the array, show the previous (i.e. the one below in the list),
|
// if we're about to delete the last entry of the array, show the previous (i.e. the one below in the list),
|
||||||
@ -242,13 +226,6 @@ define(function(require) {
|
|||||||
|
|
||||||
// production... in chrome packaged app
|
// production... in chrome packaged app
|
||||||
|
|
||||||
// if we're in the outbox, read directly from there.
|
|
||||||
if (getFolder().type === 'Outbox') {
|
|
||||||
updateStatus('Last update: ', new Date());
|
|
||||||
selectFirstMessage(outboxBo.pendingEmails);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// unselect selection from old folder
|
// unselect selection from old folder
|
||||||
$scope.select();
|
$scope.select();
|
||||||
// display and select first
|
// display and select first
|
||||||
|
@ -42,51 +42,24 @@ define(function(require) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.onOutboxUpdate = function(err, count) {
|
$scope.onOutboxUpdate = function(err, count) {
|
||||||
var outbox, mail;
|
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
$scope.onError(err);
|
$scope.onError(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
outbox = _.findWhere($scope.account.folders, {
|
// update the outbox mail count. this should normally happen during the delta sync
|
||||||
|
// problem is that the outbox continuously retries in the background, whereas the delta sync only runs
|
||||||
|
// when the outbox is currently viewed...
|
||||||
|
var outbox = _.findWhere($scope.account.folders, {
|
||||||
type: 'Outbox'
|
type: 'Outbox'
|
||||||
});
|
});
|
||||||
// update the outbox mail count
|
|
||||||
outbox.count = count;
|
|
||||||
|
|
||||||
// if we're NOT viewing the outbox or we're not looking at any mail now, we're done
|
if (outbox === $scope.state.nav.currentFolder) {
|
||||||
if ($scope.state.nav.currentFolder !== outbox || !$scope.state.mailList.selected) {
|
$scope.state.mailList.synchronize();
|
||||||
$scope.$apply();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// so we're currently in the outbox.
|
|
||||||
// if the currently selected mail is still among the pending mails, re-select it, since the object has changed.
|
|
||||||
// however, if the mail is NOT in the outbox anymore, select another pending mail
|
|
||||||
//
|
|
||||||
// this is a workaround due to the fact that the outbox loads pending messages from the indexedDB,
|
|
||||||
// where object identity is broken when you read an object twice, which happens upon the next retry
|
|
||||||
// to send the pending messages...
|
|
||||||
|
|
||||||
mail = _.findWhere(outbox.messages, {
|
|
||||||
id: $scope.state.mailList.selected.id
|
|
||||||
});
|
|
||||||
|
|
||||||
if (mail) {
|
|
||||||
// select the 'new old' mail
|
|
||||||
$scope.state.mailList.selected = mail;
|
|
||||||
} else {
|
} else {
|
||||||
if (outbox.messages.length) {
|
outbox.count = count;
|
||||||
// there are more mails to show, select another one in the list
|
$scope.$apply();
|
||||||
$scope.state.mailList.selected = outbox.messages[0];
|
|
||||||
} else {
|
|
||||||
// no mails to show, don't select anything...
|
|
||||||
$scope.state.mailList.selected = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.$apply();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -6,7 +6,7 @@ define(function(require) {
|
|||||||
aes = require('cryptoLib/aes-cbc'),
|
aes = require('cryptoLib/aes-cbc'),
|
||||||
util = require('cryptoLib/util'),
|
util = require('cryptoLib/util'),
|
||||||
str = require('js/app-config').string,
|
str = require('js/app-config').string,
|
||||||
crypto, emailDao;
|
crypto, emailDao, outbox;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Controller
|
// Controller
|
||||||
@ -14,7 +14,8 @@ define(function(require) {
|
|||||||
|
|
||||||
var WriteCtrl = function($scope, $filter) {
|
var WriteCtrl = function($scope, $filter) {
|
||||||
crypto = appController._crypto;
|
crypto = appController._crypto;
|
||||||
emailDao = appController._emailDao;
|
emailDao = appController._emailDao,
|
||||||
|
outbox = appController._outboxBo;
|
||||||
|
|
||||||
// set default value so that the popover height is correct on init
|
// set default value so that the popover height is correct on init
|
||||||
$scope.keyId = 'XXXXXXXX';
|
$scope.keyId = 'XXXXXXXX';
|
||||||
@ -211,83 +212,63 @@ define(function(require) {
|
|||||||
|
|
||||||
// build email model for smtp-client
|
// build email model for smtp-client
|
||||||
email = {
|
email = {
|
||||||
to: [],
|
from: [{
|
||||||
cc: [],
|
address: emailDao._account.emailAddress
|
||||||
bcc: [],
|
}],
|
||||||
|
to: $scope.to.filter(filterEmptyAddresses),
|
||||||
|
cc: $scope.cc.filter(filterEmptyAddresses),
|
||||||
|
bcc: $scope.bcc.filter(filterEmptyAddresses),
|
||||||
subject: $scope.subject.trim() ? $scope.subject.trim() : str.fallbackSubject, // Subject line, or the fallback subject, if nothing valid was entered
|
subject: $scope.subject.trim() ? $scope.subject.trim() : str.fallbackSubject, // Subject line, or the fallback subject, if nothing valid was entered
|
||||||
body: $scope.body.trim() // use parsed plaintext body
|
body: $scope.body.trim(), // use parsed plaintext body
|
||||||
|
attachments: $scope.attachments
|
||||||
};
|
};
|
||||||
email.from = [{
|
|
||||||
name: '',
|
|
||||||
address: emailDao._account.emailAddress
|
|
||||||
}];
|
|
||||||
|
|
||||||
// validate recipients and gather public keys
|
// persist the email to disk for later sending
|
||||||
email.receiverKeys = []; // gather public keys for emailDao._encrypt
|
outbox.put(email, function(err) {
|
||||||
|
|
||||||
appendReceivers($scope.to, email.to);
|
|
||||||
appendReceivers($scope.cc, email.cc);
|
|
||||||
appendReceivers($scope.bcc, email.bcc);
|
|
||||||
|
|
||||||
function appendReceivers(srcField, destField) {
|
|
||||||
srcField.forEach(function(recipient) {
|
|
||||||
// validate address
|
|
||||||
if (!util.validateEmailAddress(recipient.address)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// append address to email model
|
|
||||||
destField.push({
|
|
||||||
address: recipient.address
|
|
||||||
});
|
|
||||||
|
|
||||||
// add public key to list of recipient keys
|
|
||||||
if (recipient.key && recipient.key.publicKey) {
|
|
||||||
email.receiverKeys.push(recipient.key.publicKey);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// add attachment to email object
|
|
||||||
if ($scope.attachments.length > 0) {
|
|
||||||
email.attachments = $scope.attachments;
|
|
||||||
}
|
|
||||||
|
|
||||||
// persist the email locally for later smtp transmission
|
|
||||||
emailDao.storeForOutbox(email, function(err) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
$scope.onError(err);
|
$scope.onError(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helper flag to remember if we need to sync back to imap
|
||||||
|
// in case the replyTo.answered changed
|
||||||
|
var needsSync = false;
|
||||||
|
|
||||||
|
// mark replyTo as answered, if necessary
|
||||||
|
if ($scope.replyTo && !$scope.replyTo.answered) {
|
||||||
|
$scope.replyTo.answered = true;
|
||||||
|
needsSync = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// close the writer
|
||||||
$scope.state.writer.close();
|
$scope.state.writer.close();
|
||||||
|
|
||||||
|
// update the ui the scope
|
||||||
$scope.$apply();
|
$scope.$apply();
|
||||||
$scope.emptyOutbox($scope.onOutboxUpdate);
|
|
||||||
|
|
||||||
markAnswered();
|
// if we need to synchronize replyTo.answered, let's do that.
|
||||||
});
|
// otherwise, we're done
|
||||||
};
|
if (!needsSync) {
|
||||||
|
|
||||||
function markAnswered() {
|
|
||||||
// mark replyTo as answered
|
|
||||||
if (!$scope.replyTo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark list object
|
|
||||||
$scope.replyTo.answered = true;
|
|
||||||
emailDao.sync({
|
|
||||||
folder: $scope.state.nav.currentFolder.path
|
|
||||||
}, function(err) {
|
|
||||||
if (err && err.code === 42) {
|
|
||||||
// offline
|
|
||||||
$scope.onError();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.onError(err);
|
emailDao.sync({
|
||||||
|
folder: $scope.state.nav.currentFolder.path
|
||||||
|
}, function(err) {
|
||||||
|
if (err && err.code === 42) {
|
||||||
|
// offline
|
||||||
|
$scope.onError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.onError(err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
function filterEmptyAddresses(addr) {
|
||||||
|
return !!addr.address;
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -6,12 +6,12 @@ define(function(require) {
|
|||||||
str = require('js/app-config').string,
|
str = require('js/app-config').string,
|
||||||
config = require('js/app-config').config;
|
config = require('js/app-config').config;
|
||||||
|
|
||||||
var EmailDAO = function(keychain, crypto, devicestorage) {
|
var EmailDAO = function(keychain, crypto, devicestorage, pgpbuilder, mailreader) {
|
||||||
var self = this;
|
this._keychain = keychain;
|
||||||
|
this._crypto = crypto;
|
||||||
self._keychain = keychain;
|
this._devicestorage = devicestorage;
|
||||||
self._crypto = crypto;
|
this._pgpbuilder = pgpbuilder;
|
||||||
self._devicestorage = devicestorage;
|
this._mailreader = mailreader;
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -74,10 +74,6 @@ define(function(require) {
|
|||||||
|
|
||||||
self._imapClient = options.imapClient;
|
self._imapClient = options.imapClient;
|
||||||
self._pgpMailer = options.pgpMailer;
|
self._pgpMailer = options.pgpMailer;
|
||||||
// set private key
|
|
||||||
if (self._crypto && self._crypto._privateKey) {
|
|
||||||
self._pgpMailer._privateKey = self._crypto._privateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
// delegation-esque pattern to mitigate between node-style events and plain js
|
// delegation-esque pattern to mitigate between node-style events and plain js
|
||||||
self._imapClient.onIncomingMessage = function(message) {
|
self._imapClient.onIncomingMessage = function(message) {
|
||||||
@ -137,9 +133,16 @@ define(function(require) {
|
|||||||
passphrase: options.passphrase,
|
passphrase: options.passphrase,
|
||||||
privateKeyArmored: options.keypair.privateKey.encryptedKey,
|
privateKeyArmored: options.keypair.privateKey.encryptedKey,
|
||||||
publicKeyArmored: options.keypair.publicKey.publicKey
|
publicKeyArmored: options.keypair.publicKey.publicKey
|
||||||
}, callback);
|
}, function(err) {
|
||||||
// set decrypted privateKey to pgpMailer
|
if (err) {
|
||||||
self._pgpMailer._privateKey = self._crypto._privateKey;
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set decrypted privateKey to pgpMailer
|
||||||
|
self._pgpbuilder._privateKey = self._crypto._privateKey;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,11 +185,88 @@ define(function(require) {
|
|||||||
encryptedKey: generatedKeypair.privateKeyArmored
|
encryptedKey: generatedKeypair.privateKeyArmored
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self._keychain.putUserKeyPair(newKeypair, callback);
|
self._keychain.putUserKeyPair(newKeypair, function(err) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set decrypted privateKey to pgpMailer
|
||||||
|
self._pgpbuilder._privateKey = self._crypto._privateKey;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs outbox content from disk to memory, not vice-versa
|
||||||
|
*/
|
||||||
|
EmailDAO.prototype.syncOutbox = function(options, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// check busy status
|
||||||
|
if (self._account.busy) {
|
||||||
|
callback({
|
||||||
|
errMsg: 'Sync aborted: Previous sync still in progress',
|
||||||
|
code: 409
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure two syncs for the same folder don't interfere
|
||||||
|
self._account.busy = true;
|
||||||
|
|
||||||
|
var folder = _.findWhere(self._account.folders, {
|
||||||
|
path: options.folder
|
||||||
|
});
|
||||||
|
|
||||||
|
folder.messages = folder.messages || [];
|
||||||
|
|
||||||
|
self._localListMessages({
|
||||||
|
folder: folder.path
|
||||||
|
}, function(err, storedMessages) {
|
||||||
|
if (err) {
|
||||||
|
self._account.busy = false;
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the diffs between memory and disk
|
||||||
|
var storedIds = _.pluck(storedMessages, 'id'),
|
||||||
|
inMemoryIds = _.pluck(folder.messages, 'id'),
|
||||||
|
newIds = _.difference(storedIds, inMemoryIds),
|
||||||
|
removedIds = _.difference(inMemoryIds, storedIds);
|
||||||
|
|
||||||
|
// which messages are new on the disk that are not yet in memory?
|
||||||
|
var newMessages = _.filter(storedMessages, function(msg) {
|
||||||
|
return _.contains(newIds, msg.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// which messages are no longer on disk, i.e. have been sent
|
||||||
|
var removedMessages = _.filter(folder.messages, function(msg) {
|
||||||
|
return _.contains(removedIds, msg.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// add the new messages to memory
|
||||||
|
newMessages.forEach(function(newMessage) {
|
||||||
|
folder.messages.push(newMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove the sent messages from memory
|
||||||
|
removedMessages.forEach(function(removedMessage) {
|
||||||
|
var index = folder.messages.indexOf(removedMessage);
|
||||||
|
folder.messages.splice(index, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// update the folder count and we're done.
|
||||||
|
folder.count = folder.messages.length;
|
||||||
|
self._account.busy = false;
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
EmailDAO.prototype.sync = function(options, callback) {
|
EmailDAO.prototype.sync = function(options, callback) {
|
||||||
/*
|
/*
|
||||||
* Here's how delta sync works:
|
* Here's how delta sync works:
|
||||||
@ -206,8 +286,7 @@ define(function(require) {
|
|||||||
* deltaF4: imap > memory => we changed flags directly on the remote, sync them to the storage and memory
|
* deltaF4: imap > memory => we changed flags directly on the remote, sync them to the storage and memory
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var self = this,
|
var self = this;
|
||||||
folder, isFolderInitialized;
|
|
||||||
|
|
||||||
// validate options
|
// validate options
|
||||||
if (!options.folder) {
|
if (!options.folder) {
|
||||||
@ -226,16 +305,18 @@ define(function(require) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// not busy -> set busy
|
// make sure two syncs for the same folder don't interfere
|
||||||
self._account.busy = true;
|
self._account.busy = true;
|
||||||
|
|
||||||
folder = _.findWhere(self._account.folders, {
|
var folder = _.findWhere(self._account.folders, {
|
||||||
path: options.folder
|
path: options.folder
|
||||||
});
|
});
|
||||||
isFolderInitialized = !! folder.messages;
|
|
||||||
|
|
||||||
// initial filling from local storage is an exception from the normal sync.
|
/*
|
||||||
// after reading from local storage, do imap sync
|
* if the folder is not initialized with the messages from the memory, we need to fill it first, otherwise the delta sync obviously breaks.
|
||||||
|
* initial filling from local storage is an exception from the normal sync. after reading from local storage, do imap sync
|
||||||
|
*/
|
||||||
|
var isFolderInitialized = !! folder.messages;
|
||||||
if (!isFolderInitialized) {
|
if (!isFolderInitialized) {
|
||||||
initFolderMessages();
|
initFolderMessages();
|
||||||
return;
|
return;
|
||||||
@ -293,6 +374,7 @@ define(function(require) {
|
|||||||
storedMessageUids = _.pluck(storedMessages, 'uid'),
|
storedMessageUids = _.pluck(storedMessages, 'uid'),
|
||||||
delta1 = _.difference(storedMessageUids, inMemoryUids); // delta1 contains only uids
|
delta1 = _.difference(storedMessageUids, inMemoryUids); // delta1 contains only uids
|
||||||
|
|
||||||
|
// if we're we are done here
|
||||||
if (_.isEmpty(delta1)) {
|
if (_.isEmpty(delta1)) {
|
||||||
doDeltaF2();
|
doDeltaF2();
|
||||||
return;
|
return;
|
||||||
@ -664,19 +746,22 @@ define(function(require) {
|
|||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function finishSync() {
|
|
||||||
// after all the tags are up to date, let's adjust the unread mail count
|
|
||||||
folder.count = _.filter(folder.messages, function(msg) {
|
|
||||||
return msg.unread === true;
|
|
||||||
}).length;
|
|
||||||
// allow the next sync to take place
|
|
||||||
self._account.busy = false;
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function finishSync() {
|
||||||
|
// whereas normal folders show the unread messages count only,
|
||||||
|
// the outbox shows the total count
|
||||||
|
// after all the tags are up to date, let's adjust the unread mail count
|
||||||
|
folder.count = _.filter(folder.messages, function(msg) {
|
||||||
|
return msg.unread === true;
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
// allow the next sync to take place
|
||||||
|
self._account.busy = false;
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* checks if there are some flags that have changed in a and b
|
* checks if there are some flags that have changed in a and b
|
||||||
*/
|
*/
|
||||||
@ -867,7 +952,7 @@ define(function(require) {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EmailDAO.prototype.decryptMessageContent = function(options, callback) {
|
EmailDAO.prototype.decryptBody = function(options, callback) {
|
||||||
var self = this,
|
var self = this,
|
||||||
message = options.message;
|
message = options.message;
|
||||||
|
|
||||||
@ -900,7 +985,7 @@ define(function(require) {
|
|||||||
decrypted = decrypted || err.errMsg || 'Error occurred during decryption';
|
decrypted = decrypted || err.errMsg || 'Error occurred during decryption';
|
||||||
|
|
||||||
// this is a very primitive detection if we have PGP/MIME or PGP/INLINE
|
// this is a very primitive detection if we have PGP/MIME or PGP/INLINE
|
||||||
if (decrypted.indexOf('Content-Transfer-Encoding:') === -1 && decrypted.indexOf('Content-Type:') === -1) {
|
if (!self._mailreader.isRfc(decrypted)) {
|
||||||
message.body = decrypted;
|
message.body = decrypted;
|
||||||
message.decrypted = true;
|
message.decrypted = true;
|
||||||
message.decryptingBody = false;
|
message.decryptingBody = false;
|
||||||
@ -911,7 +996,7 @@ define(function(require) {
|
|||||||
// parse the decrypted MIME message
|
// parse the decrypted MIME message
|
||||||
self._imapParseMessageBlock({
|
self._imapParseMessageBlock({
|
||||||
message: message,
|
message: message,
|
||||||
block: decrypted
|
raw: decrypted
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
message.decryptingBody = false;
|
message.decryptingBody = false;
|
||||||
@ -934,39 +1019,6 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
EmailDAO.prototype._imapMark = function(options, callback) {
|
|
||||||
if (!this._account.online) {
|
|
||||||
callback({
|
|
||||||
errMsg: 'Client is currently offline!',
|
|
||||||
code: 42
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._imapClient.updateFlags({
|
|
||||||
path: options.folder,
|
|
||||||
uid: options.uid,
|
|
||||||
unread: options.unread,
|
|
||||||
answered: options.answered
|
|
||||||
}, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
EmailDAO.prototype.move = function(options, callback) {
|
|
||||||
if (!this._account.online) {
|
|
||||||
callback({
|
|
||||||
errMsg: 'Client is currently offline!',
|
|
||||||
code: 42
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._imapClient.moveMessage({
|
|
||||||
path: options.folder,
|
|
||||||
uid: options.uid,
|
|
||||||
destination: options.destination
|
|
||||||
}, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
EmailDAO.prototype.getAttachment = function(options, callback) {
|
EmailDAO.prototype.getAttachment = function(options, callback) {
|
||||||
if (!this._account.online) {
|
if (!this._account.online) {
|
||||||
callback({
|
callback({
|
||||||
@ -980,8 +1032,7 @@ define(function(require) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
EmailDAO.prototype.sendEncrypted = function(options, callback) {
|
EmailDAO.prototype.sendEncrypted = function(options, callback) {
|
||||||
var self = this,
|
var self = this;
|
||||||
email = options.email;
|
|
||||||
|
|
||||||
if (!this._account.online) {
|
if (!this._account.online) {
|
||||||
callback({
|
callback({
|
||||||
@ -991,35 +1042,16 @@ define(function(require) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate the email input
|
// add whiteout tag to subject
|
||||||
if (!email.to || !email.from || !email.to[0].address || !email.from[0].address || !Array.isArray(email.receiverKeys)) {
|
options.email.subject = str.subjectPrefix + options.email.subject;
|
||||||
callback({
|
|
||||||
errMsg: 'Invalid email object!'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get own public key so send message can be read
|
// mime encode, sign, encrypt and send email via smtp
|
||||||
self._crypto.exportKeys(function(err, ownKeys) {
|
self._pgpMailer.send({
|
||||||
if (err) {
|
encrypt: true,
|
||||||
callback(err);
|
cleartextMessage: str.message,
|
||||||
return;
|
mail: options.email,
|
||||||
}
|
publicKeysArmored: options.email.publicKeysArmored
|
||||||
|
}, callback);
|
||||||
// add own public key to receiver list
|
|
||||||
email.receiverKeys.push(ownKeys.publicKeyArmored);
|
|
||||||
|
|
||||||
// add whiteout tag to subject
|
|
||||||
email.subject = str.subjectPrefix + email.subject;
|
|
||||||
|
|
||||||
// mime encode, sign, encrypt and send email via smtp
|
|
||||||
self._pgpMailer.send({
|
|
||||||
encrypt: true,
|
|
||||||
cleartextMessage: str.message,
|
|
||||||
mail: email,
|
|
||||||
publicKeysArmored: email.receiverKeys
|
|
||||||
}, callback);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EmailDAO.prototype.sendPlaintext = function(options, callback) {
|
EmailDAO.prototype.sendPlaintext = function(options, callback) {
|
||||||
@ -1040,6 +1072,14 @@ define(function(require) {
|
|||||||
}, callback);
|
}, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
EmailDAO.prototype.encrypt = function(options, callback) {
|
||||||
|
this._pgpbuilder.encrypt(options, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
EmailDAO.prototype.reEncrypt = function(options, callback) {
|
||||||
|
this._pgpbuilder.reEncrypt(options, callback);
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Internal API
|
// Internal API
|
||||||
//
|
//
|
||||||
@ -1080,9 +1120,28 @@ define(function(require) {
|
|||||||
this._pgpMailer.login();
|
this._pgpMailer.login();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// IMAP API
|
// IMAP API
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark imap messages as un-/read or un-/answered
|
||||||
|
*/
|
||||||
|
EmailDAO.prototype._imapMark = function(options, callback) {
|
||||||
|
if (!this._account.online) {
|
||||||
|
callback({
|
||||||
|
errMsg: 'Client is currently offline!',
|
||||||
|
code: 42
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._imapClient.updateFlags({
|
||||||
|
path: options.folder,
|
||||||
|
uid: options.uid,
|
||||||
|
unread: options.unread,
|
||||||
|
answered: options.answered
|
||||||
|
}, callback);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login the imap client
|
* Login the imap client
|
||||||
*/
|
*/
|
||||||
@ -1161,7 +1220,7 @@ define(function(require) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
EmailDAO.prototype._imapParseMessageBlock = function(options, callback) {
|
EmailDAO.prototype._imapParseMessageBlock = function(options, callback) {
|
||||||
this._imapClient.parseDecryptedMessageBlock(options, callback);
|
this._mailreader.parseRfc(options, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1277,81 +1336,5 @@ define(function(require) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Persists an email object for the outbox, encrypted with the user's public key
|
|
||||||
* @param {Object} email The email object
|
|
||||||
* @param {Function} callback(error) Invoked when the email was encrypted and persisted, contains information in case of an error
|
|
||||||
*/
|
|
||||||
EmailDAO.prototype.storeForOutbox = function(email, callback) {
|
|
||||||
var self = this,
|
|
||||||
dbType = 'email_OUTBOX',
|
|
||||||
plaintext = email.body;
|
|
||||||
|
|
||||||
// give the email a random identifier (used for storage)
|
|
||||||
email.id = util.UUID();
|
|
||||||
|
|
||||||
// get own public key so send message can be read
|
|
||||||
self._crypto.exportKeys(function(err, ownKeys) {
|
|
||||||
if (err) {
|
|
||||||
callback(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// encrypt the email with the user's public key
|
|
||||||
self._crypto.encrypt(plaintext, [ownKeys.publicKeyArmored], function(err, ciphertext) {
|
|
||||||
if (err) {
|
|
||||||
callback(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace plaintext body with pgp message
|
|
||||||
email.body = ciphertext;
|
|
||||||
|
|
||||||
// store to local storage
|
|
||||||
self._devicestorage.storeList([email], dbType, callback);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads and decrypts persisted email objects for the outbox
|
|
||||||
* @param {Function} callback(error, emails) Invoked when the email was encrypted and persisted, contains information in case of an error
|
|
||||||
*/
|
|
||||||
EmailDAO.prototype.listForOutbox = function(callback) {
|
|
||||||
var self = this,
|
|
||||||
dbType = 'email_OUTBOX';
|
|
||||||
|
|
||||||
self._devicestorage.listItems(dbType, 0, null, function(err, mails) {
|
|
||||||
if (err) {
|
|
||||||
callback(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mails.length === 0) {
|
|
||||||
callback(null, []);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self._crypto.exportKeys(function(err, ownKeys) {
|
|
||||||
if (err) {
|
|
||||||
callback(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var after = _.after(mails.length, function() {
|
|
||||||
callback(null, mails);
|
|
||||||
});
|
|
||||||
|
|
||||||
mails.forEach(function(mail) {
|
|
||||||
self._crypto.decrypt(mail.body, ownKeys.publicKeyArmored, function(err, decrypted) {
|
|
||||||
mail.body = err ? err.errMsg : decrypted;
|
|
||||||
after();
|
|
||||||
});
|
|
||||||
mail.encrypted = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return EmailDAO;
|
return EmailDAO;
|
||||||
});
|
});
|
@ -261,13 +261,11 @@ define(function(require) {
|
|||||||
emailDaoStub.init.yields(null, {});
|
emailDaoStub.init.yields(null, {});
|
||||||
|
|
||||||
onConnectStub.yields();
|
onConnectStub.yields();
|
||||||
outboxStub.init.returns();
|
|
||||||
|
|
||||||
controller.init({}, function(err, keypair) {
|
controller.init({}, function(err, keypair) {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(keypair).to.exist;
|
expect(keypair).to.exist;
|
||||||
expect(onConnectStub.calledOnce).to.be.true;
|
expect(onConnectStub.calledOnce).to.be.true;
|
||||||
expect(outboxStub.init.calledOnce).to.be.true;
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,18 +5,20 @@ define(function(require) {
|
|||||||
KeychainDAO = require('js/dao/keychain-dao'),
|
KeychainDAO = require('js/dao/keychain-dao'),
|
||||||
ImapClient = require('imap-client'),
|
ImapClient = require('imap-client'),
|
||||||
PgpMailer = require('pgpmailer'),
|
PgpMailer = require('pgpmailer'),
|
||||||
|
PgpBuilder = require('pgpbuilder'),
|
||||||
PGP = require('js/crypto/pgp'),
|
PGP = require('js/crypto/pgp'),
|
||||||
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
||||||
|
mailreader = require('mailreader'),
|
||||||
str = require('js/app-config').string,
|
str = require('js/app-config').string,
|
||||||
expect = chai.expect;
|
expect = chai.expect;
|
||||||
|
|
||||||
chai.Assertion.includeStack = true;
|
chai.Assertion.includeStack = true;
|
||||||
|
|
||||||
describe('Email DAO unit tests', function() {
|
describe('Email DAO unit tests', function() {
|
||||||
var dao, keychainStub, imapClientStub, pgpMailerStub, pgpStub, devicestorageStub;
|
var dao, keychainStub, imapClientStub, pgpMailerStub, pgpBuilderStub, pgpStub, devicestorageStub;
|
||||||
|
|
||||||
var emailAddress, passphrase, asymKeySize, mockkeyId, dummyEncryptedMail,
|
var emailAddress, passphrase, asymKeySize, mockkeyId, dummyEncryptedMail,
|
||||||
dummyDecryptedMail, dummyLegacyDecryptedMail, mockKeyPair, account, publicKey, verificationMail, verificationUuid,
|
dummyDecryptedMail, mockKeyPair, account, verificationMail, verificationUuid,
|
||||||
corruptedVerificationMail, corruptedVerificationUuid,
|
corruptedVerificationMail, corruptedVerificationUuid,
|
||||||
nonWhitelistedMail;
|
nonWhitelistedMail;
|
||||||
|
|
||||||
@ -78,21 +80,6 @@ define(function(require) {
|
|||||||
body: 'Content-Type: multipart/signed;\r\n boundary="Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2";\r\n protocol="application/pgp-signature";\r\n micalg=pgp-sha512\r\n\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2\r\nContent-Type: multipart/mixed;\r\n boundary="Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA"\r\n\r\n\r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/plain;\r\n charset=us-ascii\r\n\r\nasdasd \r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA\r\nContent-Disposition: attachment;\r\n filename=dummy.txt\r\nContent-Type: text/plain;\r\n name="dummy.txt"\r\nContent-Transfer-Encoding: 7bit\r\n\r\noaudbcoaurbvosuabvlasdjbfalwubjvawvb\r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA--\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2\r\nContent-Transfer-Encoding: 7bit\r\nContent-Disposition: attachment;\r\n filename=signature.asc\r\nContent-Type: application/pgp-signature;\r\n name=signature.asc\r\nContent-Description: Message signed with OpenPGP using GPGMail\r\n\r\n-----BEGIN PGP SIGNATURE-----\r\nComment: GPGTools - https://gpgtools.org\r\n\r\niQEcBAEBCgAGBQJS2kO1AAoJEDzmUwH7XO/cP+YH/2PSBxX1ZZd83Uf9qBGDY807\r\niHOdgPFXm64YjSnohO7XsPcnmihqP1ipS2aaCXFC3/Vgb9nc4isQFS+i1VdPwfuR\r\n1Pd2l3dC4/nD4xO9h/W6JW7Yd24NS5TJD5cA7LYwQ8LF+rOzByMatiTMmecAUCe8\r\nEEalEjuogojk4IacA8dg/bfLqQu9E+0GYUJBcI97dx/0jZ0qMOxbWOQLsJ3DnUnV\r\nOad7pAIbHEO6T0EBsH7TyTj4RRHkP6SKE0mm6ZYUC7KCk2Z3MtkASTxUrnqW5qZ5\r\noaXUO9GEc8KZcmbCdhZY2Y5h+dmucaO0jpbeSKkvtYyD4KZrSvt7NTb/0dSLh4Y=\r\n=G8km\r\n-----END PGP SIGNATURE-----\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2--\r\n',
|
body: 'Content-Type: multipart/signed;\r\n boundary="Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2";\r\n protocol="application/pgp-signature";\r\n micalg=pgp-sha512\r\n\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2\r\nContent-Type: multipart/mixed;\r\n boundary="Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA"\r\n\r\n\r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/plain;\r\n charset=us-ascii\r\n\r\nasdasd \r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA\r\nContent-Disposition: attachment;\r\n filename=dummy.txt\r\nContent-Type: text/plain;\r\n name="dummy.txt"\r\nContent-Transfer-Encoding: 7bit\r\n\r\noaudbcoaurbvosuabvlasdjbfalwubjvawvb\r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA--\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2\r\nContent-Transfer-Encoding: 7bit\r\nContent-Disposition: attachment;\r\n filename=signature.asc\r\nContent-Type: application/pgp-signature;\r\n name=signature.asc\r\nContent-Description: Message signed with OpenPGP using GPGMail\r\n\r\n-----BEGIN PGP SIGNATURE-----\r\nComment: GPGTools - https://gpgtools.org\r\n\r\niQEcBAEBCgAGBQJS2kO1AAoJEDzmUwH7XO/cP+YH/2PSBxX1ZZd83Uf9qBGDY807\r\niHOdgPFXm64YjSnohO7XsPcnmihqP1ipS2aaCXFC3/Vgb9nc4isQFS+i1VdPwfuR\r\n1Pd2l3dC4/nD4xO9h/W6JW7Yd24NS5TJD5cA7LYwQ8LF+rOzByMatiTMmecAUCe8\r\nEEalEjuogojk4IacA8dg/bfLqQu9E+0GYUJBcI97dx/0jZ0qMOxbWOQLsJ3DnUnV\r\nOad7pAIbHEO6T0EBsH7TyTj4RRHkP6SKE0mm6ZYUC7KCk2Z3MtkASTxUrnqW5qZ5\r\noaXUO9GEc8KZcmbCdhZY2Y5h+dmucaO0jpbeSKkvtYyD4KZrSvt7NTb/0dSLh4Y=\r\n=G8km\r\n-----END PGP SIGNATURE-----\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2--\r\n',
|
||||||
unread: false,
|
unread: false,
|
||||||
answered: false,
|
answered: false,
|
||||||
receiverKeys: ['-----BEGIN PGP PUBLIC KEY-----\nasd\n-----END PGP PUBLIC KEY-----']
|
|
||||||
};
|
|
||||||
dummyLegacyDecryptedMail = {
|
|
||||||
uid: 1234,
|
|
||||||
from: [{
|
|
||||||
address: 'asd@asd.de'
|
|
||||||
}],
|
|
||||||
to: [{
|
|
||||||
address: 'qwe@qwe.de'
|
|
||||||
}],
|
|
||||||
subject: 'qweasd',
|
|
||||||
body: 'asd',
|
|
||||||
unread: false,
|
|
||||||
answered: false,
|
|
||||||
receiverKeys: ['-----BEGIN PGP PUBLIC KEY-----\nasd\n-----END PGP PUBLIC KEY-----']
|
|
||||||
};
|
};
|
||||||
nonWhitelistedMail = {
|
nonWhitelistedMail = {
|
||||||
uid: 1234,
|
uid: 1234,
|
||||||
@ -122,15 +109,15 @@ define(function(require) {
|
|||||||
asymKeySize: asymKeySize,
|
asymKeySize: asymKeySize,
|
||||||
busy: false
|
busy: false
|
||||||
};
|
};
|
||||||
publicKey = "-----BEGIN PUBLIC KEY-----\r\n" + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxy+Te5dyeWd7g0P+8LNO7fZDQ\r\n" + "g96xTb1J6pYE/pPTMlqhB6BRItIYjZ1US5q2vk5Zk/5KasBHAc9RbCqvh9v4XFEY\r\n" + "JVmTXC4p8ft1LYuNWIaDk+R3dyYXmRNct/JC4tks2+8fD3aOvpt0WNn3R75/FGBt\r\n" + "h4BgojAXDE+PRQtcVQIDAQAB\r\n" + "-----END PUBLIC KEY-----";
|
|
||||||
|
|
||||||
keychainStub = sinon.createStubInstance(KeychainDAO);
|
keychainStub = sinon.createStubInstance(KeychainDAO);
|
||||||
imapClientStub = sinon.createStubInstance(ImapClient);
|
imapClientStub = sinon.createStubInstance(ImapClient);
|
||||||
pgpMailerStub = sinon.createStubInstance(PgpMailer);
|
pgpMailerStub = sinon.createStubInstance(PgpMailer);
|
||||||
|
pgpBuilderStub = sinon.createStubInstance(PgpBuilder);
|
||||||
pgpStub = sinon.createStubInstance(PGP);
|
pgpStub = sinon.createStubInstance(PGP);
|
||||||
devicestorageStub = sinon.createStubInstance(DeviceStorageDAO);
|
devicestorageStub = sinon.createStubInstance(DeviceStorageDAO);
|
||||||
|
|
||||||
dao = new EmailDAO(keychainStub, pgpStub, devicestorageStub);
|
dao = new EmailDAO(keychainStub, pgpStub, devicestorageStub, pgpBuilderStub, mailreader);
|
||||||
dao._account = account;
|
dao._account = account;
|
||||||
|
|
||||||
expect(dao._keychain).to.equal(keychainStub);
|
expect(dao._keychain).to.equal(keychainStub);
|
||||||
@ -369,14 +356,15 @@ define(function(require) {
|
|||||||
|
|
||||||
describe('unlock', function() {
|
describe('unlock', function() {
|
||||||
it('should unlock', function(done) {
|
it('should unlock', function(done) {
|
||||||
var importMatcher = sinon.match(function(o) {
|
dao._pgpMailer = {
|
||||||
expect(o.passphrase).to.equal(passphrase);
|
_pgpbuilder: {}
|
||||||
expect(o.privateKeyArmored).to.equal(mockKeyPair.privateKey.encryptedKey);
|
};
|
||||||
expect(o.publicKeyArmored).to.equal(mockKeyPair.publicKey.publicKey);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
pgpStub.importKeys.withArgs(importMatcher).yields();
|
pgpStub.importKeys.withArgs({
|
||||||
|
passphrase: passphrase,
|
||||||
|
privateKeyArmored: mockKeyPair.privateKey.encryptedKey,
|
||||||
|
publicKeyArmored: mockKeyPair.publicKey.publicKey
|
||||||
|
}).yields();
|
||||||
|
|
||||||
dao.unlock({
|
dao.unlock({
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
@ -391,33 +379,29 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should generate a keypair and unlock', function(done) {
|
it('should generate a keypair and unlock', function(done) {
|
||||||
var genKeysMatcher, persistKeysMatcher, importMatcher, keypair;
|
var keypair;
|
||||||
|
|
||||||
|
dao._pgpMailer = {
|
||||||
|
_pgpbuilder: {}
|
||||||
|
};
|
||||||
|
|
||||||
keypair = {
|
keypair = {
|
||||||
keyId: 123,
|
keyId: 123,
|
||||||
publicKeyArmored: mockKeyPair.publicKey.publicKey,
|
publicKeyArmored: mockKeyPair.publicKey.publicKey,
|
||||||
privateKeyArmored: mockKeyPair.privateKey.encryptedKey
|
privateKeyArmored: mockKeyPair.privateKey.encryptedKey
|
||||||
};
|
};
|
||||||
genKeysMatcher = sinon.match(function(o) {
|
|
||||||
expect(o.emailAddress).to.equal(emailAddress);
|
|
||||||
expect(o.keySize).to.equal(asymKeySize);
|
|
||||||
expect(o.passphrase).to.equal(passphrase);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
importMatcher = sinon.match(function(o) {
|
|
||||||
expect(o.passphrase).to.equal(passphrase);
|
|
||||||
expect(o.privateKeyArmored).to.equal(mockKeyPair.privateKey.encryptedKey);
|
|
||||||
expect(o.publicKeyArmored).to.equal(mockKeyPair.publicKey.publicKey);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
persistKeysMatcher = sinon.match(function(o) {
|
|
||||||
expect(o).to.deep.equal(mockKeyPair);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
pgpStub.generateKeys.withArgs({
|
||||||
|
emailAddress: emailAddress,
|
||||||
|
keySize: asymKeySize,
|
||||||
|
passphrase: passphrase
|
||||||
|
}).yields(null, keypair);
|
||||||
|
|
||||||
pgpStub.generateKeys.withArgs(genKeysMatcher).yields(null, keypair);
|
pgpStub.importKeys.withArgs({
|
||||||
pgpStub.importKeys.withArgs(importMatcher).yields();
|
passphrase: passphrase,
|
||||||
|
privateKeyArmored: mockKeyPair.privateKey.encryptedKey,
|
||||||
|
publicKeyArmored: mockKeyPair.publicKey.publicKey
|
||||||
|
}).yields();
|
||||||
keychainStub.putUserKeyPair.withArgs().yields();
|
keychainStub.putUserKeyPair.withArgs().yields();
|
||||||
|
|
||||||
dao.unlock({
|
dao.unlock({
|
||||||
@ -495,14 +479,12 @@ define(function(require) {
|
|||||||
|
|
||||||
describe('_imapParseMessageBlock', function() {
|
describe('_imapParseMessageBlock', function() {
|
||||||
it('should parse a message', function(done) {
|
it('should parse a message', function(done) {
|
||||||
imapClientStub.parseDecryptedMessageBlock.yields(null, {});
|
var parseRfc = sinon.stub(mailreader, 'parseRfc').withArgs({}).yields();
|
||||||
|
|
||||||
dao._imapParseMessageBlock(function(err, msg) {
|
dao._imapParseMessageBlock({}, function() {
|
||||||
expect(err).to.not.exist;
|
expect(parseRfc.calledOnce).to.be.true;
|
||||||
expect(msg).to.exist;
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1185,13 +1167,13 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('decryptMessageContent', function() {
|
describe('decryptBody', function() {
|
||||||
it('should not do anything when the message is not encrypted', function() {
|
it('should not do anything when the message is not encrypted', function() {
|
||||||
var message = {
|
var message = {
|
||||||
encrypted: false
|
encrypted: false
|
||||||
};
|
};
|
||||||
|
|
||||||
dao.decryptMessageContent({
|
dao.decryptBody({
|
||||||
message: message
|
message: message
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1204,7 +1186,7 @@ define(function(require) {
|
|||||||
decrypted: true
|
decrypted: true
|
||||||
};
|
};
|
||||||
|
|
||||||
dao.decryptMessageContent({
|
dao.decryptBody({
|
||||||
message: message
|
message: message
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1223,20 +1205,20 @@ define(function(require) {
|
|||||||
body: '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----'
|
body: '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----'
|
||||||
};
|
};
|
||||||
|
|
||||||
mimeBody = 'Content-Transfer-Encoding: Content-Type:';
|
mimeBody = 'Content-Type: asdasdasd';
|
||||||
parsedBody = 'body? yes.';
|
parsedBody = 'body? yes.';
|
||||||
|
|
||||||
keychainStub.getReceiverPublicKey.withArgs(message.from[0].address).yieldsAsync(null, mockKeyPair.publicKey);
|
keychainStub.getReceiverPublicKey.withArgs(message.from[0].address).yieldsAsync(null, mockKeyPair.publicKey);
|
||||||
pgpStub.decrypt.withArgs(message.body, mockKeyPair.publicKey.publicKey).yieldsAsync(null, mimeBody);
|
pgpStub.decrypt.withArgs(message.body, mockKeyPair.publicKey.publicKey).yieldsAsync(null, mimeBody);
|
||||||
parseStub = sinon.stub(dao, '_imapParseMessageBlock', function(o, cb) {
|
parseStub = sinon.stub(dao, '_imapParseMessageBlock', function(o, cb) {
|
||||||
expect(o.message).to.equal(message);
|
expect(o.message).to.equal(message);
|
||||||
expect(o.block).to.equal(mimeBody);
|
expect(o.raw).to.equal(mimeBody);
|
||||||
|
|
||||||
o.message.body = parsedBody;
|
o.message.body = parsedBody;
|
||||||
cb(null, o.message);
|
cb(null, o.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
dao.decryptMessageContent({
|
dao.decryptBody({
|
||||||
message: message
|
message: message
|
||||||
}, function(error, msg) {
|
}, function(error, msg) {
|
||||||
expect(error).to.not.exist;
|
expect(error).to.not.exist;
|
||||||
@ -1274,7 +1256,7 @@ define(function(require) {
|
|||||||
pgpStub.decrypt.withArgs(message.body, mockKeyPair.publicKey.publicKey).yieldsAsync(null, plaintextBody);
|
pgpStub.decrypt.withArgs(message.body, mockKeyPair.publicKey.publicKey).yieldsAsync(null, plaintextBody);
|
||||||
parseStub = sinon.stub(dao, '_imapParseMessageBlock');
|
parseStub = sinon.stub(dao, '_imapParseMessageBlock');
|
||||||
|
|
||||||
dao.decryptMessageContent({
|
dao.decryptBody({
|
||||||
message: message
|
message: message
|
||||||
}, function(error, msg) {
|
}, function(error, msg) {
|
||||||
expect(error).to.not.exist;
|
expect(error).to.not.exist;
|
||||||
@ -1314,7 +1296,7 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
parseStub = sinon.stub(dao, '_imapParseMessageBlock');
|
parseStub = sinon.stub(dao, '_imapParseMessageBlock');
|
||||||
|
|
||||||
dao.decryptMessageContent({
|
dao.decryptBody({
|
||||||
message: message
|
message: message
|
||||||
}, function(error, msg) {
|
}, function(error, msg) {
|
||||||
expect(error).to.not.exist;
|
expect(error).to.not.exist;
|
||||||
@ -1347,7 +1329,7 @@ define(function(require) {
|
|||||||
keychainStub.getReceiverPublicKey.yields({});
|
keychainStub.getReceiverPublicKey.yields({});
|
||||||
parseStub = sinon.stub(dao, '_imapParseMessageBlock');
|
parseStub = sinon.stub(dao, '_imapParseMessageBlock');
|
||||||
|
|
||||||
dao.decryptMessageContent({
|
dao.decryptBody({
|
||||||
message: message
|
message: message
|
||||||
}, function(error, msg) {
|
}, function(error, msg) {
|
||||||
expect(error).to.exist;
|
expect(error).to.exist;
|
||||||
@ -2566,26 +2548,6 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('move', function() {
|
|
||||||
it('should work', function(done) {
|
|
||||||
imapClientStub.moveMessage.withArgs({
|
|
||||||
path: 'asdf',
|
|
||||||
uid: 1,
|
|
||||||
destination: 'asdasd'
|
|
||||||
}).yields();
|
|
||||||
|
|
||||||
dao.move({
|
|
||||||
folder: 'asdf',
|
|
||||||
uid: 1,
|
|
||||||
destination: 'asdasd'
|
|
||||||
}, function(err) {
|
|
||||||
expect(imapClientStub.moveMessage.calledOnce).to.be.true;
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('sendPlaintext', function() {
|
describe('sendPlaintext', function() {
|
||||||
it('should work', function(done) {
|
it('should work', function(done) {
|
||||||
pgpMailerStub.send.withArgs({
|
pgpMailerStub.send.withArgs({
|
||||||
@ -2616,16 +2578,14 @@ define(function(require) {
|
|||||||
|
|
||||||
describe('sendEncrypted', function() {
|
describe('sendEncrypted', function() {
|
||||||
it('should work', function(done) {
|
it('should work', function(done) {
|
||||||
pgpStub.exportKeys.yields(null, {
|
var publicKeys = ["PUBLIC KEY"];
|
||||||
privateKeyArmored: mockKeyPair.privateKey.encryptedKey,
|
dummyDecryptedMail.publicKeysArmored = publicKeys;
|
||||||
publicKeyArmored: mockKeyPair.publicKey.publicKey
|
|
||||||
});
|
|
||||||
|
|
||||||
pgpMailerStub.send.withArgs({
|
pgpMailerStub.send.withArgs({
|
||||||
encrypt: true,
|
encrypt: true,
|
||||||
cleartextMessage: str.message,
|
cleartextMessage: str.message,
|
||||||
mail: dummyDecryptedMail,
|
mail: dummyDecryptedMail,
|
||||||
publicKeysArmored: dummyDecryptedMail.receiverKeys
|
publicKeysArmored: publicKeys
|
||||||
}).yields();
|
}).yields();
|
||||||
|
|
||||||
dao.sendEncrypted({
|
dao.sendEncrypted({
|
||||||
@ -2633,17 +2593,14 @@ define(function(require) {
|
|||||||
}, function(err) {
|
}, function(err) {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
|
|
||||||
expect(pgpStub.exportKeys.calledOnce).to.be.true;
|
|
||||||
expect(pgpMailerStub.send.calledOnce).to.be.true;
|
expect(pgpMailerStub.send.calledOnce).to.be.true;
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should not work when pgpmailer fails', function(done) {
|
it('should not work when pgpmailer fails', function(done) {
|
||||||
pgpStub.exportKeys.yields(null, {
|
var publicKeys = ["PUBLIC KEY"];
|
||||||
privateKeyArmored: mockKeyPair.privateKey.encryptedKey,
|
dummyDecryptedMail.publicKeysArmored = publicKeys;
|
||||||
publicKeyArmored: mockKeyPair.publicKey.publicKey
|
|
||||||
});
|
|
||||||
pgpMailerStub.send.yields({});
|
pgpMailerStub.send.yields({});
|
||||||
|
|
||||||
dao.sendEncrypted({
|
dao.sendEncrypted({
|
||||||
@ -2651,176 +2608,53 @@ define(function(require) {
|
|||||||
}, function(err) {
|
}, function(err) {
|
||||||
expect(err).to.exist;
|
expect(err).to.exist;
|
||||||
|
|
||||||
expect(pgpStub.exportKeys.calledOnce).to.be.true;
|
|
||||||
expect(pgpMailerStub.send.calledOnce).to.be.true;
|
expect(pgpMailerStub.send.calledOnce).to.be.true;
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should not work when sender key export fails', function(done) {
|
});
|
||||||
pgpStub.exportKeys.yields({});
|
|
||||||
|
|
||||||
dao.sendEncrypted({
|
describe('encrypt', function() {
|
||||||
email: dummyDecryptedMail
|
it('should encrypt', function(done) {
|
||||||
|
pgpBuilderStub.encrypt.yields();
|
||||||
|
|
||||||
|
dao.encrypt({}, function() {
|
||||||
|
expect(pgpBuilderStub.encrypt.calledOnce).to.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('reEncrypt', function() {
|
||||||
|
it('should re-encrypt', function(done) {
|
||||||
|
pgpBuilderStub.reEncrypt.yields();
|
||||||
|
|
||||||
|
dao.reEncrypt({}, function() {
|
||||||
|
expect(pgpBuilderStub.reEncrypt.calledOnce).to.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('syncOutbox', function() {
|
||||||
|
it('should sync the outbox', function(done) {
|
||||||
|
var folder = 'FOLDAAAA';
|
||||||
|
dao._account.folders = [{
|
||||||
|
type: 'Folder',
|
||||||
|
path: folder
|
||||||
|
}];
|
||||||
|
|
||||||
|
var localListStub = sinon.stub(dao, '_localListMessages').withArgs({
|
||||||
|
folder: folder
|
||||||
|
}).yields(null, [dummyEncryptedMail]);
|
||||||
|
|
||||||
|
dao.syncOutbox({
|
||||||
|
folder: folder
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
expect(err).to.exist;
|
|
||||||
|
|
||||||
expect(pgpStub.exportKeys.calledOnce).to.be.true;
|
|
||||||
expect(pgpMailerStub.send.calledOnce).to.be.false;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('storeForOutbox', function() {
|
|
||||||
it('should work', function(done) {
|
|
||||||
var key = 'omgsocrypto';
|
|
||||||
|
|
||||||
pgpStub.exportKeys.yields(null, {
|
|
||||||
publicKeyArmored: key
|
|
||||||
});
|
|
||||||
pgpStub.encrypt.withArgs(dummyDecryptedMail.body, [key]).yields(null, 'asdfasfd');
|
|
||||||
devicestorageStub.storeList.withArgs([dummyDecryptedMail], 'email_OUTBOX').yields();
|
|
||||||
|
|
||||||
dao.storeForOutbox(dummyDecryptedMail, function(err) {
|
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
|
expect(localListStub.calledOnce).to.be.true;
|
||||||
expect(pgpStub.exportKeys.calledOnce).to.be.true;
|
expect(dao._account.folders[0].messages.length).to.equal(1);
|
||||||
expect(pgpStub.encrypt.calledOnce).to.be.true;
|
|
||||||
expect(devicestorageStub.storeList.calledOnce).to.be.true;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work when store fails', function(done) {
|
|
||||||
var key = 'omgsocrypto';
|
|
||||||
|
|
||||||
pgpStub.exportKeys.yields(null, {
|
|
||||||
publicKeyArmored: key
|
|
||||||
});
|
|
||||||
pgpStub.encrypt.yields(null, 'asdfasfd');
|
|
||||||
devicestorageStub.storeList.yields({});
|
|
||||||
|
|
||||||
dao.storeForOutbox(dummyDecryptedMail, function(err) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
|
|
||||||
expect(pgpStub.exportKeys.calledOnce).to.be.true;
|
|
||||||
expect(pgpStub.encrypt.calledOnce).to.be.true;
|
|
||||||
expect(devicestorageStub.storeList.calledOnce).to.be.true;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work when encryption fails', function(done) {
|
|
||||||
var key = 'omgsocrypto';
|
|
||||||
|
|
||||||
pgpStub.exportKeys.yields(null, {
|
|
||||||
publicKeyArmored: key
|
|
||||||
});
|
|
||||||
pgpStub.encrypt.yields({});
|
|
||||||
|
|
||||||
dao.storeForOutbox(dummyDecryptedMail, function(err) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
|
|
||||||
expect(pgpStub.exportKeys.calledOnce).to.be.true;
|
|
||||||
expect(pgpStub.encrypt.calledOnce).to.be.true;
|
|
||||||
expect(devicestorageStub.storeList.called).to.be.false;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work when key export fails', function(done) {
|
|
||||||
pgpStub.exportKeys.yields({});
|
|
||||||
|
|
||||||
dao.storeForOutbox(dummyDecryptedMail, function(err) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
|
|
||||||
expect(pgpStub.exportKeys.calledOnce).to.be.true;
|
|
||||||
expect(pgpStub.encrypt.called).to.be.false;
|
|
||||||
expect(devicestorageStub.storeList.called).to.be.false;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('listForOutbox', function() {
|
|
||||||
it('should work', function(done) {
|
|
||||||
var key = 'omgsocrypto';
|
|
||||||
|
|
||||||
devicestorageStub.listItems.withArgs('email_OUTBOX', 0, null).yields(null, [dummyEncryptedMail]);
|
|
||||||
pgpStub.exportKeys.yields(null, {
|
|
||||||
publicKeyArmored: key
|
|
||||||
});
|
|
||||||
pgpStub.decrypt.withArgs(dummyEncryptedMail.body, key).yields(null, dummyDecryptedMail.body);
|
|
||||||
|
|
||||||
dao.listForOutbox(function(err, mails) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
|
|
||||||
expect(devicestorageStub.listItems.calledOnce).to.be.true;
|
|
||||||
expect(pgpStub.exportKeys.calledOnce).to.be.true;
|
|
||||||
expect(pgpStub.decrypt.calledOnce).to.be.true;
|
|
||||||
expect(mails.length).to.equal(1);
|
|
||||||
expect(mails[0].body).to.equal(dummyDecryptedMail.body);
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not work when decryption fails', function(done) {
|
|
||||||
var key = 'omgsocrypto',
|
|
||||||
errMsg = 'THIS IS AN ERROR!';
|
|
||||||
|
|
||||||
devicestorageStub.listItems.yields(null, [dummyEncryptedMail]);
|
|
||||||
pgpStub.exportKeys.yields(null, {
|
|
||||||
publicKeyArmored: key
|
|
||||||
});
|
|
||||||
pgpStub.decrypt.yields({
|
|
||||||
errMsg: errMsg
|
|
||||||
});
|
|
||||||
|
|
||||||
dao.listForOutbox(function(err, mails) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(mails[0].body).to.equal(errMsg);
|
|
||||||
|
|
||||||
expect(devicestorageStub.listItems.calledOnce).to.be.true;
|
|
||||||
expect(pgpStub.exportKeys.calledOnce).to.be.true;
|
|
||||||
expect(pgpStub.decrypt.calledOnce).to.be.true;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not work when key export fails', function(done) {
|
|
||||||
devicestorageStub.listItems.yields(null, [dummyEncryptedMail]);
|
|
||||||
pgpStub.exportKeys.yields({});
|
|
||||||
|
|
||||||
dao.listForOutbox(function(err, mails) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
expect(mails).to.not.exist;
|
|
||||||
|
|
||||||
expect(devicestorageStub.listItems.calledOnce).to.be.true;
|
|
||||||
expect(pgpStub.exportKeys.calledOnce).to.be.true;
|
|
||||||
expect(pgpStub.decrypt.called).to.be.false;
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not work when list from storage fails', function(done) {
|
|
||||||
devicestorageStub.listItems.yields({});
|
|
||||||
|
|
||||||
dao.listForOutbox(function(err, mails) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
expect(mails).to.not.exist;
|
|
||||||
|
|
||||||
expect(devicestorageStub.listItems.calledOnce).to.be.true;
|
|
||||||
expect(pgpStub.exportKeys.called).to.be.false;
|
|
||||||
expect(pgpStub.decrypt.called).to.be.false;
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -205,24 +205,6 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should read directly from outbox instead of doing a full imap sync', function() {
|
|
||||||
scope._stopWatchTask();
|
|
||||||
|
|
||||||
var currentFolder = {
|
|
||||||
type: 'Outbox'
|
|
||||||
};
|
|
||||||
scope.folders = [currentFolder];
|
|
||||||
scope.state.nav = {
|
|
||||||
currentFolder: currentFolder
|
|
||||||
};
|
|
||||||
scope.state.mailList.selected = undefined;
|
|
||||||
|
|
||||||
scope.synchronize();
|
|
||||||
|
|
||||||
// emails array is also used as the outbox's pending mail
|
|
||||||
expect(scope.state.mailList.selected).to.deep.equal(emails[emails.length - 1]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getBody', function() {
|
describe('getBody', function() {
|
||||||
@ -260,7 +242,7 @@ define(function(require) {
|
|||||||
|
|
||||||
scope.select(mail);
|
scope.select(mail);
|
||||||
|
|
||||||
expect(emailDaoMock.decryptMessageContent.calledOnce).to.be.true;
|
expect(emailDaoMock.decryptBody.calledOnce).to.be.true;
|
||||||
expect(synchronizeMock.calledOnce).to.be.true;
|
expect(synchronizeMock.calledOnce).to.be.true;
|
||||||
expect(scope.state.mailList.selected).to.equal(mail);
|
expect(scope.state.mailList.selected).to.equal(mail);
|
||||||
|
|
||||||
@ -288,7 +270,7 @@ define(function(require) {
|
|||||||
|
|
||||||
scope.select(mail);
|
scope.select(mail);
|
||||||
|
|
||||||
expect(emailDaoMock.decryptMessageContent.calledOnce).to.be.true;
|
expect(emailDaoMock.decryptBody.calledOnce).to.be.true;
|
||||||
expect(synchronizeMock.called).to.be.false;
|
expect(synchronizeMock.called).to.be.false;
|
||||||
expect(scope.state.mailList.selected).to.equal(mail);
|
expect(scope.state.mailList.selected).to.equal(mail);
|
||||||
|
|
||||||
|
@ -2,13 +2,14 @@ define(function(require) {
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var expect = chai.expect,
|
var expect = chai.expect,
|
||||||
_ = require('underscore'),
|
|
||||||
OutboxBO = require('js/bo/outbox'),
|
OutboxBO = require('js/bo/outbox'),
|
||||||
KeychainDAO = require('js/dao/keychain-dao'),
|
KeychainDAO = require('js/dao/keychain-dao'),
|
||||||
EmailDAO = require('js/dao/email-dao'),
|
EmailDAO = require('js/dao/email-dao'),
|
||||||
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
||||||
InvitationDAO = require('js/dao/invitation-dao');
|
InvitationDAO = require('js/dao/invitation-dao');
|
||||||
|
|
||||||
|
chai.Assertion.includeStack = true;
|
||||||
|
|
||||||
describe('Outbox Business Object unit test', function() {
|
describe('Outbox Business Object unit test', function() {
|
||||||
var outbox, emailDaoStub, devicestorageStub, invitationDaoStub, keychainStub,
|
var outbox, emailDaoStub, devicestorageStub, invitationDaoStub, keychainStub,
|
||||||
dummyUser = 'spiderpig@springfield.com';
|
dummyUser = 'spiderpig@springfield.com';
|
||||||
@ -26,24 +27,10 @@ define(function(require) {
|
|||||||
keychainStub = sinon.createStubInstance(KeychainDAO);
|
keychainStub = sinon.createStubInstance(KeychainDAO);
|
||||||
invitationDaoStub = sinon.createStubInstance(InvitationDAO);
|
invitationDaoStub = sinon.createStubInstance(InvitationDAO);
|
||||||
outbox = new OutboxBO(emailDaoStub, keychainStub, devicestorageStub, invitationDaoStub);
|
outbox = new OutboxBO(emailDaoStub, keychainStub, devicestorageStub, invitationDaoStub);
|
||||||
outbox.init();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {});
|
afterEach(function() {});
|
||||||
|
|
||||||
describe('init', function() {
|
|
||||||
it('should work', function() {
|
|
||||||
expect(outbox).to.exist;
|
|
||||||
expect(outbox._emailDao).to.equal(emailDaoStub);
|
|
||||||
expect(outbox._keychain).to.equal(keychainStub);
|
|
||||||
expect(outbox._devicestorage).to.equal(devicestorageStub);
|
|
||||||
expect(outbox._invitationDao).to.equal(invitationDaoStub);
|
|
||||||
expect(outbox._outboxBusy).to.be.false;
|
|
||||||
expect(outbox.pendingEmails).to.be.empty;
|
|
||||||
expect(emailDaoStub._account.folders[0].messages).to.equal(outbox.pendingEmails);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('start/stop checking', function() {
|
describe('start/stop checking', function() {
|
||||||
it('should work', function() {
|
it('should work', function() {
|
||||||
function onOutboxUpdate(err) {
|
function onOutboxUpdate(err) {
|
||||||
@ -58,77 +45,171 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('process outbox', function() {
|
describe('put', function() {
|
||||||
it('should send to registered users and update pending mails', function(done) {
|
it('should encrypt and store a mail', function(done) {
|
||||||
var member, invited, notinvited, dummyMails, unsentCount;
|
var mail, senderKey, receiverKey;
|
||||||
|
|
||||||
member = {
|
senderKey = {
|
||||||
id: '123',
|
publicKey: 'SENDER PUBLIC KEY'
|
||||||
to: [{
|
};
|
||||||
|
receiverKey = {
|
||||||
|
publicKey: 'RECEIVER PUBLIC KEY'
|
||||||
|
};
|
||||||
|
mail = {
|
||||||
|
from: [{
|
||||||
name: 'member',
|
name: 'member',
|
||||||
address: 'member@whiteout.io'
|
address: 'member@whiteout.io'
|
||||||
}]
|
}],
|
||||||
|
to: [{
|
||||||
|
name: 'member',
|
||||||
|
address: 'member'
|
||||||
|
}, {
|
||||||
|
name: 'notamember',
|
||||||
|
address: 'notamember'
|
||||||
|
}],
|
||||||
|
cc: [],
|
||||||
|
bcc: []
|
||||||
|
};
|
||||||
|
|
||||||
|
keychainStub.getReceiverPublicKey.withArgs(mail.from[0].address).yieldsAsync(null, senderKey);
|
||||||
|
keychainStub.getReceiverPublicKey.withArgs(mail.to[0].address).yieldsAsync(null, receiverKey);
|
||||||
|
keychainStub.getReceiverPublicKey.withArgs(mail.to[1].address).yieldsAsync();
|
||||||
|
|
||||||
|
emailDaoStub.encrypt.withArgs({
|
||||||
|
mail: mail,
|
||||||
|
publicKeysArmored: [senderKey.publicKey, receiverKey.publicKey]
|
||||||
|
}).yieldsAsync();
|
||||||
|
|
||||||
|
devicestorageStub.storeList.withArgs([mail]).yieldsAsync();
|
||||||
|
|
||||||
|
outbox.put(mail, function(error) {
|
||||||
|
expect(error).to.not.exist;
|
||||||
|
|
||||||
|
expect(mail.publicKeysArmored.length).to.equal(2);
|
||||||
|
expect(mail.unregisteredUsers.length).to.equal(1);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('process outbox', function() {
|
||||||
|
it('should send to registered users and update pending mails', function(done) {
|
||||||
|
var from, member, invited, notinvited, newlyjoined, dummyMails, newlyjoinedKey;
|
||||||
|
|
||||||
|
from = [{
|
||||||
|
name: 'member',
|
||||||
|
address: 'member@whiteout.io'
|
||||||
|
}];
|
||||||
|
member = {
|
||||||
|
id: '12',
|
||||||
|
from: from,
|
||||||
|
to: [{
|
||||||
|
name: 'member',
|
||||||
|
address: 'member'
|
||||||
|
}],
|
||||||
|
publicKeysArmored: ['ARMORED KEY OF MEMBER'],
|
||||||
|
unregisteredUsers: []
|
||||||
};
|
};
|
||||||
invited = {
|
invited = {
|
||||||
id: '456',
|
id: '34',
|
||||||
|
from: from,
|
||||||
to: [{
|
to: [{
|
||||||
name: 'invited',
|
name: 'invited',
|
||||||
address: 'invited@whiteout.io'
|
address: 'invited'
|
||||||
|
}],
|
||||||
|
publicKeysArmored: [],
|
||||||
|
unregisteredUsers: [{
|
||||||
|
name: 'invited',
|
||||||
|
address: 'invited'
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
notinvited = {
|
notinvited = {
|
||||||
id: '789',
|
id: '56',
|
||||||
|
from: from,
|
||||||
to: [{
|
to: [{
|
||||||
name: 'notinvited',
|
name: 'notinvited',
|
||||||
address: 'notinvited@whiteout.io'
|
address: 'notinvited'
|
||||||
|
}],
|
||||||
|
publicKeysArmored: [],
|
||||||
|
unregisteredUsers: [{
|
||||||
|
name: 'notinvited',
|
||||||
|
address: 'notinvited'
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
dummyMails = [member, invited, notinvited];
|
newlyjoined = {
|
||||||
|
id: '78',
|
||||||
|
from: from,
|
||||||
|
to: [{
|
||||||
|
name: 'newlyjoined',
|
||||||
|
address: 'newlyjoined'
|
||||||
|
}],
|
||||||
|
publicKeysArmored: [],
|
||||||
|
unregisteredUsers: [{
|
||||||
|
name: 'newlyjoined',
|
||||||
|
address: 'newlyjoined'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
newlyjoinedKey = {
|
||||||
|
publicKey: 'THIS IS THE NEWLY JOINED PUBLIC KEY!'
|
||||||
|
};
|
||||||
|
|
||||||
|
dummyMails = [member, invited, notinvited, newlyjoined];
|
||||||
|
|
||||||
|
devicestorageStub.listItems.yieldsAsync(null, dummyMails);
|
||||||
|
|
||||||
|
keychainStub.getReceiverPublicKey.withArgs(invited.unregisteredUsers[0].address).yieldsAsync();
|
||||||
|
keychainStub.getReceiverPublicKey.withArgs(notinvited.unregisteredUsers[0].address).yieldsAsync();
|
||||||
|
keychainStub.getReceiverPublicKey.withArgs(newlyjoined.unregisteredUsers[0].address).yieldsAsync(null, newlyjoinedKey);
|
||||||
|
|
||||||
|
invitationDaoStub.check.withArgs({
|
||||||
|
recipient: invited.to[0].address,
|
||||||
|
sender: invited.from[0].address
|
||||||
|
}).yieldsAsync(null, InvitationDAO.INVITE_PENDING);
|
||||||
|
|
||||||
|
invitationDaoStub.check.withArgs({
|
||||||
|
recipient: notinvited.to[0].address,
|
||||||
|
sender: notinvited.from[0].address
|
||||||
|
}).yieldsAsync(null, InvitationDAO.INVITE_MISSING);
|
||||||
|
|
||||||
|
invitationDaoStub.invite.withArgs({
|
||||||
|
recipient: notinvited.to[0].address,
|
||||||
|
sender: notinvited.from[0].address
|
||||||
|
}).yieldsAsync(null, InvitationDAO.INVITE_SUCCESS);
|
||||||
|
|
||||||
emailDaoStub.listForOutbox.yieldsAsync(null, dummyMails);
|
|
||||||
emailDaoStub.sendEncrypted.withArgs(sinon.match(function(opts) {
|
|
||||||
return typeof opts.email !== 'undefined' && opts.email.to.address === member.to.address;
|
|
||||||
})).yieldsAsync();
|
|
||||||
emailDaoStub.sendPlaintext.yieldsAsync();
|
emailDaoStub.sendPlaintext.yieldsAsync();
|
||||||
devicestorageStub.removeList.yieldsAsync();
|
|
||||||
invitationDaoStub.check.withArgs(sinon.match(function(o) {
|
|
||||||
return o.recipient === 'invited@whiteout.io';
|
|
||||||
})).yieldsAsync(null, InvitationDAO.INVITE_PENDING);
|
|
||||||
invitationDaoStub.check.withArgs(sinon.match(function(o) {
|
|
||||||
return o.recipient === 'notinvited@whiteout.io';
|
|
||||||
})).yieldsAsync(null, InvitationDAO.INVITE_MISSING);
|
|
||||||
invitationDaoStub.invite.withArgs(sinon.match(function(o) {
|
|
||||||
return o.recipient === 'notinvited@whiteout.io';
|
|
||||||
})).yieldsAsync(null, InvitationDAO.INVITE_SUCCESS);
|
|
||||||
keychainStub.getReceiverPublicKey.withArgs(sinon.match(function(o) {
|
|
||||||
return o === 'member@whiteout.io';
|
|
||||||
})).yieldsAsync(null, 'this is not the key you are looking for...');
|
|
||||||
keychainStub.getReceiverPublicKey.withArgs(sinon.match(function(o) {
|
|
||||||
return o === 'invited@whiteout.io' || o === 'notinvited@whiteout.io';
|
|
||||||
})).yieldsAsync();
|
|
||||||
|
|
||||||
var check = _.after(dummyMails.length + 1, function() {
|
emailDaoStub.reEncrypt.withArgs({
|
||||||
expect(outbox._outboxBusy).to.be.false;
|
mail: newlyjoined,
|
||||||
|
publicKeysArmored: [newlyjoinedKey.publicKey]
|
||||||
|
}).yieldsAsync(null, newlyjoined);
|
||||||
|
|
||||||
expect(unsentCount).to.equal(2);
|
emailDaoStub.sendEncrypted.withArgs({
|
||||||
expect(emailDaoStub.listForOutbox.callCount).to.equal(1);
|
email: newlyjoined
|
||||||
expect(emailDaoStub.sendEncrypted.callCount).to.equal(1);
|
}).yieldsAsync();
|
||||||
expect(emailDaoStub.sendPlaintext.callCount).to.equal(1);
|
|
||||||
expect(devicestorageStub.removeList.callCount).to.equal(1);
|
|
||||||
expect(invitationDaoStub.check.callCount).to.equal(2);
|
|
||||||
expect(invitationDaoStub.invite.callCount).to.equal(1);
|
|
||||||
|
|
||||||
expect(outbox.pendingEmails.length).to.equal(2);
|
emailDaoStub.sendEncrypted.withArgs({
|
||||||
expect(outbox.pendingEmails).to.contain(invited);
|
email: member
|
||||||
expect(outbox.pendingEmails).to.contain(notinvited);
|
}).yieldsAsync();
|
||||||
done();
|
|
||||||
});
|
devicestorageStub.storeList.withArgs([newlyjoined]).yieldsAsync();
|
||||||
|
|
||||||
|
devicestorageStub.removeList.withArgs('email_OUTBOX_' + member.id).yieldsAsync();
|
||||||
|
devicestorageStub.removeList.withArgs('email_OUTBOX_' + newlyjoined.id).yieldsAsync();
|
||||||
|
|
||||||
function onOutboxUpdate(err, count) {
|
function onOutboxUpdate(err, count) {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(count).to.exist;
|
expect(count).to.equal(2);
|
||||||
unsentCount = count;
|
|
||||||
check();
|
expect(outbox._outboxBusy).to.be.false;
|
||||||
|
expect(emailDaoStub.sendEncrypted.calledTwice).to.be.true;
|
||||||
|
expect(emailDaoStub.reEncrypt.calledOnce).to.be.true;
|
||||||
|
expect(emailDaoStub.sendPlaintext.calledOnce).to.be.true;
|
||||||
|
expect(devicestorageStub.listItems.calledOnce).to.be.true;
|
||||||
|
expect(keychainStub.getReceiverPublicKey.calledThrice).to.be.true;
|
||||||
|
expect(invitationDaoStub.check.calledTwice).to.be.true;
|
||||||
|
|
||||||
|
done();
|
||||||
}
|
}
|
||||||
|
|
||||||
outbox._processOutbox(onOutboxUpdate);
|
outbox._processOutbox(onOutboxUpdate);
|
||||||
@ -136,31 +217,16 @@ define(function(require) {
|
|||||||
|
|
||||||
it('should not process outbox in offline mode', function(done) {
|
it('should not process outbox in offline mode', function(done) {
|
||||||
emailDaoStub._account.online = false;
|
emailDaoStub._account.online = false;
|
||||||
emailDaoStub.listForOutbox.yieldsAsync(null, [{
|
devicestorageStub.listItems.yieldsAsync(null, [{}]);
|
||||||
id: '123',
|
|
||||||
to: [{
|
|
||||||
name: 'member',
|
|
||||||
address: 'member@whiteout.io'
|
|
||||||
}]
|
|
||||||
}]);
|
|
||||||
|
|
||||||
outbox._processOutbox(function(err, count) {
|
outbox._processOutbox(function(err, count) {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(count).to.equal(1);
|
expect(count).to.equal(1);
|
||||||
expect(emailDaoStub.listForOutbox.callCount).to.equal(1);
|
expect(devicestorageStub.listItems.callCount).to.equal(1);
|
||||||
expect(outbox._outboxBusy).to.be.false;
|
expect(outbox._outboxBusy).to.be.false;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fire notification', function(done) {
|
|
||||||
outbox.onSent = function(email) {
|
|
||||||
expect(email).to.exist;
|
|
||||||
done();
|
|
||||||
};
|
|
||||||
|
|
||||||
outbox._onSent({});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -6,14 +6,24 @@ define(function(require) {
|
|||||||
mocks = require('angularMocks'),
|
mocks = require('angularMocks'),
|
||||||
WriteCtrl = require('js/controller/write'),
|
WriteCtrl = require('js/controller/write'),
|
||||||
EmailDAO = require('js/dao/email-dao'),
|
EmailDAO = require('js/dao/email-dao'),
|
||||||
|
OutboxBO = require('js/bo/outbox'),
|
||||||
KeychainDAO = require('js/dao/keychain-dao'),
|
KeychainDAO = require('js/dao/keychain-dao'),
|
||||||
appController = require('js/app-controller');
|
appController = require('js/app-controller');
|
||||||
|
|
||||||
describe('Write controller unit test', function() {
|
describe('Write controller unit test', function() {
|
||||||
var ctrl, scope, origEmailDao, emailDaoMock, keychainMock, emailAddress;
|
var ctrl, scope,
|
||||||
|
origEmailDao, origOutbox,
|
||||||
|
emailDaoMock, keychainMock, outboxMock, emailAddress;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
// the app controller is a singleton, we need to remember the
|
||||||
|
// outbox and email dao to restore it after the tests
|
||||||
origEmailDao = appController._emailDao;
|
origEmailDao = appController._emailDao;
|
||||||
|
origOutbox = appController._outboxBo;
|
||||||
|
|
||||||
|
outboxMock = sinon.createStubInstance(OutboxBO);
|
||||||
|
appController._outboxBo = outboxMock;
|
||||||
|
|
||||||
emailDaoMock = sinon.createStubInstance(EmailDAO);
|
emailDaoMock = sinon.createStubInstance(EmailDAO);
|
||||||
appController._emailDao = emailDaoMock;
|
appController._emailDao = emailDaoMock;
|
||||||
|
|
||||||
@ -37,8 +47,9 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
// restore the module
|
// restore the app controller
|
||||||
appController._emailDao = origEmailDao;
|
appController._emailDao = origEmailDao;
|
||||||
|
appController._outboxBo = origOutbox;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('scope variables', function() {
|
describe('scope variables', function() {
|
||||||
@ -259,127 +270,38 @@ define(function(require) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('send to outbox', function() {
|
describe('send to outbox', function() {
|
||||||
it('should work when offline', function(done) {
|
it('should work', function() {
|
||||||
var re = {
|
scope.from = [{
|
||||||
from: [{
|
address: 'pity@dafool'
|
||||||
address: 'pity@dafool'
|
}];
|
||||||
}],
|
scope.to = [{
|
||||||
subject: 'Ermahgerd!',
|
address: 'pity@dafool'
|
||||||
sentDate: new Date(),
|
}];
|
||||||
body: 'so much body!'
|
scope.cc = [];
|
||||||
};
|
scope.bcc = [];
|
||||||
|
scope.subject = 'Ermahgerd!';
|
||||||
|
scope.body = 'wow. much body! very text!';
|
||||||
|
scope.attachments = [];
|
||||||
scope.state.nav = {
|
scope.state.nav = {
|
||||||
currentFolder: 'currentFolder'
|
currentFolder: 'currentFolder'
|
||||||
};
|
};
|
||||||
|
|
||||||
scope.emptyOutbox = function() {};
|
scope.replyTo = {};
|
||||||
scope.onError = function(err) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(scope.state.writer.open).to.be.false;
|
|
||||||
expect(emailDaoMock.storeForOutbox.calledOnce).to.be.true;
|
|
||||||
expect(emailDaoMock.sync.calledOnce).to.be.true;
|
|
||||||
|
|
||||||
done();
|
outboxMock.put.yields();
|
||||||
};
|
|
||||||
|
|
||||||
emailDaoMock.storeForOutbox.yields();
|
|
||||||
emailDaoMock.sync.yields({
|
|
||||||
code: 42
|
|
||||||
});
|
|
||||||
|
|
||||||
scope.state.writer.write(re);
|
|
||||||
scope.sendToOutbox();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work', function(done) {
|
|
||||||
var re = {
|
|
||||||
from: [{
|
|
||||||
address: 'pity@dafool'
|
|
||||||
}],
|
|
||||||
subject: 'Ermahgerd!',
|
|
||||||
sentDate: new Date(),
|
|
||||||
body: 'so much body!'
|
|
||||||
};
|
|
||||||
|
|
||||||
scope.state.nav = {
|
|
||||||
currentFolder: 'currentFolder'
|
|
||||||
};
|
|
||||||
|
|
||||||
scope.emptyOutbox = function() {};
|
|
||||||
scope.onError = function(err) {
|
|
||||||
expect(err).to.not.exist;
|
|
||||||
expect(scope.state.writer.open).to.be.false;
|
|
||||||
expect(emailDaoMock.storeForOutbox.calledOnce).to.be.true;
|
|
||||||
expect(emailDaoMock.sync.calledOnce).to.be.true;
|
|
||||||
|
|
||||||
done();
|
|
||||||
};
|
|
||||||
|
|
||||||
emailDaoMock.storeForOutbox.yields();
|
|
||||||
emailDaoMock.sync.yields();
|
emailDaoMock.sync.yields();
|
||||||
|
|
||||||
scope.state.writer.write(re);
|
|
||||||
scope.sendToOutbox();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail', function(done) {
|
|
||||||
var re = {
|
|
||||||
from: [{
|
|
||||||
address: 'pity@dafool'
|
|
||||||
}],
|
|
||||||
subject: 'Ermahgerd!',
|
|
||||||
sentDate: new Date(),
|
|
||||||
body: 'so much body!'
|
|
||||||
};
|
|
||||||
|
|
||||||
scope.state.nav = {
|
|
||||||
currentFolder: 'currentFolder'
|
|
||||||
};
|
|
||||||
|
|
||||||
scope.emptyOutbox = function() {};
|
|
||||||
scope.onError = function(err) {
|
scope.onError = function(err) {
|
||||||
expect(err).to.exist;
|
expect(err).to.not.exist;
|
||||||
expect(scope.state.writer.open).to.be.false;
|
|
||||||
expect(emailDaoMock.storeForOutbox.calledOnce).to.be.true;
|
|
||||||
expect(emailDaoMock.sync.calledOnce).to.be.true;
|
|
||||||
|
|
||||||
done();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
emailDaoMock.storeForOutbox.yields();
|
|
||||||
emailDaoMock.sync.yields({});
|
|
||||||
|
|
||||||
scope.state.writer.write(re);
|
|
||||||
scope.sendToOutbox();
|
scope.sendToOutbox();
|
||||||
});
|
|
||||||
|
|
||||||
it('should not work and not close the write view', function(done) {
|
expect(outboxMock.put.calledOnce).to.be.true;
|
||||||
scope.state.writer.write();
|
expect(emailDaoMock.sync.calledOnce).to.be.true;
|
||||||
|
|
||||||
scope.to = [{
|
expect(scope.state.writer.open).to.be.false;
|
||||||
address: 'pity@dafool.de',
|
expect(scope.replyTo.answered).to.be.true;
|
||||||
key: {
|
|
||||||
publicKey: '----- PGP Stuff -----'
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
scope.body = 'asd';
|
|
||||||
scope.subject = 'yaddablabla';
|
|
||||||
scope.toKey = 'Public Key';
|
|
||||||
|
|
||||||
emailDaoMock.storeForOutbox.withArgs(sinon.match(function(mail) {
|
|
||||||
return mail.from[0].address === emailAddress && mail.to.length === 1 && mail.receiverKeys.length === 1;
|
|
||||||
})).yields({
|
|
||||||
errMsg: 'snafu'
|
|
||||||
});
|
|
||||||
|
|
||||||
scope.onError = function(err) {
|
|
||||||
expect(err).to.exist;
|
|
||||||
expect(scope.state.writer.open).to.be.true;
|
|
||||||
expect(emailDaoMock.storeForOutbox.calledOnce).to.be.true;
|
|
||||||
done();
|
|
||||||
};
|
|
||||||
scope.sendToOutbox();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user