mirror of
https://github.com/moparisthebest/mail
synced 2025-02-07 02:20:14 -05:00
switching between offline and online state works
This commit is contained in:
parent
f5b7b61e45
commit
d08321d345
@ -244,7 +244,7 @@ module.exports = function(grunt) {
|
||||
|
||||
// Test/Dev tasks
|
||||
grunt.registerTask('dev', ['connect:dev']);
|
||||
grunt.registerTask('test', ['jshint', 'connect:test', 'mocha', 'qunit']);
|
||||
grunt.registerTask('test', ['jshint', 'connect:test', 'mocha']);
|
||||
grunt.registerTask('prod', ['connect:prod']);
|
||||
|
||||
//
|
||||
|
Binary file not shown.
@ -18,4 +18,5 @@
|
||||
<glyph unicode="" d="M0 960l1024-1024h-1024z" />
|
||||
<glyph unicode="" d="M681.984 655.104v-119.744h-153.6v119.744c0 81.472-64.768 147.712-144.384 147.712s-144.384-66.24-144.384-147.712v-119.744h-153.6v119.744c-0.064 168.064 133.696 304.896 297.984 304.896 164.352 0 297.984-136.832 297.984-304.896zM0 476.096v-540.096h768v540.096h-768zM308.224-0.896l24.192 195.968 5.12 41.792c-21.632 15.232-35.712 40.64-35.712 69.44 0 11.136 2.112 21.696 5.888 31.296 12.096 30.848 41.664 52.736 76.288 52.736 34.624 0 64.192-21.888 76.288-52.736 3.776-9.664 5.888-20.224 5.888-31.296 0-28.8-14.208-54.272-35.84-69.312l5.184-41.856 24.064-195.968h-151.36z" horiz-adv-x="768" />
|
||||
<glyph unicode="" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512 32c-229.75 0-416 186.25-416 416s186.25 416 416 416 416-186.25 416-416-186.25-416-416-416zM448 704h128v-128h-128zM640 192h-256v64h64v192h-64v64h192v-256h64z" />
|
||||
<glyph unicode="" d="M768 320.032l-182.82 182.822 438.82 329.15-128.010 127.996-548.52-219.442-172.7 172.706c-49.78 49.778-119.302 61.706-154.502 26.508-35.198-35.198-23.268-104.726 26.51-154.5l172.686-172.684-219.464-548.582 127.99-128.006 329.19 438.868 182.826-182.828v-255.98h127.994l63.992 191.988 191.988 63.996v127.992l-255.98-0.004z" />
|
||||
</font></defs></svg>
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
Binary file not shown.
@ -24,7 +24,7 @@ define(function(require) {
|
||||
/**
|
||||
* Start the application
|
||||
*/
|
||||
self.start = function(callback) {
|
||||
self.start = function(options, callback) {
|
||||
// are we running in native app or in browser?
|
||||
if (document.URL.indexOf("http") === 0 || document.URL.indexOf("app") === 0 || document.URL.indexOf("chrome") === 0) {
|
||||
console.log('Assuming Browser environment...');
|
||||
@ -36,12 +36,132 @@ define(function(require) {
|
||||
|
||||
function onDeviceReady() {
|
||||
console.log('Starting app.');
|
||||
|
||||
// Handle offline and online gracefully
|
||||
window.addEventListener('online', self.onConnect.bind(self, options.onError));
|
||||
window.addEventListener('offline', self.onDisconnect.bind(self, options.onError));
|
||||
|
||||
// init app config storage
|
||||
self._appConfigStore = new DeviceStorageDAO(new LawnchairDAO());
|
||||
self._appConfigStore.init('app-config', callback);
|
||||
}
|
||||
};
|
||||
|
||||
self.onDisconnect = function(callback) {
|
||||
if (!self._emailDao) {
|
||||
// the following code only makes sense if the email dao has been initialized
|
||||
return;
|
||||
}
|
||||
|
||||
self._emailDao.onDisconnect(null, callback);
|
||||
};
|
||||
|
||||
self.onConnect = function(callback) {
|
||||
if (!self._emailDao) {
|
||||
// the following code only makes sense if the email dao has been initialized
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.isOnline()) {
|
||||
// prevent connection infinite loop
|
||||
console.log('Not connecting since user agent is offline.');
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// fetch pinned local ssl certificate
|
||||
self.getCertficate(function(err, certificate) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// get a fresh oauth token
|
||||
self.fetchOAuthToken(function(err, oauth) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
initClients(oauth, certificate);
|
||||
});
|
||||
});
|
||||
|
||||
function initClients(oauth, certificate) {
|
||||
var auth, imapOptions, imapClient, smtpOptions, smtpClient;
|
||||
|
||||
auth = {
|
||||
XOAuth2: {
|
||||
user: oauth.emailAddress,
|
||||
clientId: config.gmail.clientId,
|
||||
accessToken: oauth.token
|
||||
}
|
||||
};
|
||||
imapOptions = {
|
||||
secure: config.gmail.imap.secure,
|
||||
port: config.gmail.imap.port,
|
||||
host: config.gmail.imap.host,
|
||||
auth: auth,
|
||||
ca: [certificate]
|
||||
};
|
||||
smtpOptions = {
|
||||
secure: config.gmail.smtp.secure,
|
||||
port: config.gmail.smtp.port,
|
||||
host: config.gmail.smtp.host,
|
||||
auth: auth,
|
||||
ca: [certificate]
|
||||
};
|
||||
|
||||
imapClient = new ImapClient(imapOptions);
|
||||
smtpClient = new SmtpClient(smtpOptions);
|
||||
|
||||
imapClient.onError = function(err) {
|
||||
console.log('IMAP error... reconnecting.', err);
|
||||
// re-init client modules on error
|
||||
self.onConnect(callback);
|
||||
};
|
||||
|
||||
// connect to clients
|
||||
self._emailDao.onConnect({
|
||||
imapClient: imapClient,
|
||||
smtpClient: smtpClient
|
||||
}, callback);
|
||||
}
|
||||
};
|
||||
|
||||
self.getCertficate = function(localCallback) {
|
||||
var xhr;
|
||||
|
||||
if (self.certificate) {
|
||||
localCallback(null, self.certificate);
|
||||
return;
|
||||
}
|
||||
|
||||
// fetch pinned local ssl certificate
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/ca/Google_Internet_Authority_G2.pem');
|
||||
xhr.onload = function() {
|
||||
if (xhr.readyState === 4 && xhr.status === 200 && xhr.responseText) {
|
||||
self.certificate = xhr.responseText;
|
||||
localCallback(null, self.certificate);
|
||||
} else {
|
||||
localCallback({
|
||||
errMsg: 'Could not fetch pinned certificate!'
|
||||
});
|
||||
}
|
||||
};
|
||||
xhr.onerror = function() {
|
||||
localCallback({
|
||||
errMsg: 'Could not fetch pinned certificate!'
|
||||
});
|
||||
};
|
||||
xhr.send();
|
||||
};
|
||||
|
||||
self.isOnline = function() {
|
||||
return navigator.onLine;
|
||||
};
|
||||
|
||||
self.checkForUpdate = function() {
|
||||
if (!chrome || !chrome.runtime || !chrome.runtime.onUpdateAvailable) {
|
||||
return;
|
||||
@ -63,6 +183,121 @@ define(function(require) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Gracefully try to fetch the user's email address from local storage.
|
||||
* If not yet stored, handle online/offline cases on first use.
|
||||
*/
|
||||
self.getEmailAddress = function(callback) {
|
||||
// try to fetch email address from local storage
|
||||
self.getEmailAddressFromConfig(function(err, cachedEmailAddress) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cachedEmailAddress) {
|
||||
// not first time login... address cached
|
||||
callback(null, cachedEmailAddress);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cachedEmailAddress && !self.isOnline()) {
|
||||
// first time login... must be online
|
||||
callback({
|
||||
errMsg: 'The app must be online on first use!'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
self.fetchOAuthToken(function(err, oauth) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, oauth.emailAddress);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the user's email address from local storage
|
||||
*/
|
||||
self.getEmailAddressFromConfig = function(callback) {
|
||||
self._appConfigStore.listItems('emailaddress', 0, null, function(err, cachedItems) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// no email address is cached yet
|
||||
if (!cachedItems || cachedItems.length < 1) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, cachedItems[0]);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Lookup the user's email address. Check local cache if available
|
||||
* otherwise query google's token info api to learn the user's email address
|
||||
*/
|
||||
self.queryEmailAddress = function(token, callback) {
|
||||
var itemKey = 'emailaddress';
|
||||
|
||||
self.getEmailAddressFromConfig(function(err, cachedEmailAddress) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// do roundtrip to google api if no email address is cached yet
|
||||
if (!cachedEmailAddress) {
|
||||
queryGoogleApi();
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, cachedEmailAddress);
|
||||
});
|
||||
|
||||
function queryGoogleApi() {
|
||||
if (!token) {
|
||||
callback({
|
||||
errMsg: 'Invalid OAuth token!'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// fetch gmail user's email address from the Google Authorization Server endpoint
|
||||
$.ajax({
|
||||
url: 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + token,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(info) {
|
||||
if (!info || !info.email) {
|
||||
callback({
|
||||
errMsg: 'Error looking up email address on google api!'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// cache the email address on the device
|
||||
self._appConfigStore.storeList([info.email], itemKey, function(err) {
|
||||
callback(err, info.email);
|
||||
});
|
||||
},
|
||||
error: function(xhr, textStatus, err) {
|
||||
callback({
|
||||
errMsg: xhr.status + ': ' + xhr.statusText,
|
||||
err: err
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Request an OAuth token from chrome for gmail users
|
||||
*/
|
||||
@ -100,138 +335,59 @@ define(function(require) {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Lookup the user's email address. Check local cache if available, otherwise query google's token info api to learn the user's email address
|
||||
*/
|
||||
self.queryEmailAddress = function(token, callback) {
|
||||
var itemKey = 'emailaddress';
|
||||
self.buildModules = function() {
|
||||
var lawnchairDao, restDao, pubkeyDao, invitationDao,
|
||||
emailDao, keychain, pgp, userStorage;
|
||||
|
||||
self._appConfigStore.listItems(itemKey, 0, null, function(err, cachedItems) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
// init objects and inject dependencies
|
||||
restDao = new RestDAO();
|
||||
pubkeyDao = new PublicKeyDAO(restDao);
|
||||
invitationDao = new InvitationDAO(restDao);
|
||||
lawnchairDao = new LawnchairDAO();
|
||||
userStorage = new DeviceStorageDAO(lawnchairDao);
|
||||
|
||||
// do roundtrip to google api if no email address is cached yet
|
||||
if (!cachedItems || cachedItems.length < 1) {
|
||||
queryGoogleApi();
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, cachedItems[0]);
|
||||
});
|
||||
|
||||
function queryGoogleApi() {
|
||||
// fetch gmail user's email address from the Google Authorization Server endpoint
|
||||
$.ajax({
|
||||
url: 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + token,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(info) {
|
||||
if (!info || !info.email) {
|
||||
callback({
|
||||
errMsg: 'Error looking up email address on google api!'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// cache the email address on the device
|
||||
self._appConfigStore.storeList([info.email], itemKey, function(err) {
|
||||
callback(err, info.email);
|
||||
});
|
||||
},
|
||||
error: function(xhr, textStatus, err) {
|
||||
callback({
|
||||
errMsg: xhr.status + ': ' + xhr.statusText,
|
||||
err: err
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
keychain = new KeychainDAO(lawnchairDao, pubkeyDao);
|
||||
self._keychain = keychain;
|
||||
pgp = new PGP();
|
||||
self._crypto = pgp;
|
||||
self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage);
|
||||
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage, invitationDao);
|
||||
};
|
||||
|
||||
/**
|
||||
* Instanciate the mail email data access object and its dependencies. Login to imap on init.
|
||||
*/
|
||||
self.init = function(userId, token, callback) {
|
||||
var auth, imapOptions, smtpOptions, certificate,
|
||||
lawnchairDao, restDao, pubkeyDao, invitationDao, emailDao,
|
||||
keychain, imapClient, smtpClient, pgp, userStorage, xhr;
|
||||
self.init = function(options, callback) {
|
||||
self.buildModules();
|
||||
|
||||
// fetch pinned local ssl certificate
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/ca/Google_Internet_Authority_G2.pem');
|
||||
xhr.onload = function() {
|
||||
if (xhr.readyState === 4 && xhr.status === 200 && xhr.responseText) {
|
||||
certificate = xhr.responseText;
|
||||
setupDaos();
|
||||
} else {
|
||||
callback({
|
||||
errMsg: 'Could not fetch pinned certificate!'
|
||||
});
|
||||
// init email dao
|
||||
var account = {
|
||||
emailAddress: options.emailAddress,
|
||||
asymKeySize: config.asymKeySize
|
||||
};
|
||||
|
||||
self._emailDao.init({
|
||||
account: account
|
||||
}, function(err, keypair) {
|
||||
// dont handle offline case this time
|
||||
if (err && err.code !== 42) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
xhr.onerror = function() {
|
||||
callback({
|
||||
errMsg: 'Could not fetch pinned certificate!'
|
||||
});
|
||||
};
|
||||
xhr.send();
|
||||
|
||||
function setupDaos() {
|
||||
// create mail credentials objects for imap/smtp
|
||||
auth = {
|
||||
XOAuth2: {
|
||||
user: userId,
|
||||
clientId: config.gmail.clientId,
|
||||
accessToken: token
|
||||
// connect tcp clients on first startup
|
||||
self.onConnect(function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
imapOptions = {
|
||||
secure: config.gmail.imap.secure,
|
||||
port: config.gmail.imap.port,
|
||||
host: config.gmail.imap.host,
|
||||
auth: auth,
|
||||
ca: [certificate]
|
||||
};
|
||||
smtpOptions = {
|
||||
secure: config.gmail.smtp.secure,
|
||||
port: config.gmail.smtp.port,
|
||||
host: config.gmail.smtp.host,
|
||||
auth: auth,
|
||||
ca: [certificate]
|
||||
};
|
||||
|
||||
// init objects and inject dependencies
|
||||
restDao = new RestDAO();
|
||||
pubkeyDao = new PublicKeyDAO(restDao);
|
||||
lawnchairDao = new LawnchairDAO();
|
||||
keychain = new KeychainDAO(lawnchairDao, pubkeyDao);
|
||||
self._keychain = keychain;
|
||||
imapClient = new ImapClient(imapOptions);
|
||||
smtpClient = new SmtpClient(smtpOptions);
|
||||
pgp = new PGP();
|
||||
self._crypto = pgp;
|
||||
userStorage = new DeviceStorageDAO(lawnchairDao);
|
||||
self._emailDao = emailDao = new EmailDAO(keychain, imapClient, smtpClient, pgp, userStorage);
|
||||
|
||||
invitationDao = new InvitationDAO(restDao);
|
||||
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage, invitationDao);
|
||||
|
||||
// init email dao
|
||||
var account = {
|
||||
emailAddress: userId,
|
||||
asymKeySize: config.asymKeySize
|
||||
};
|
||||
|
||||
self._emailDao.init({
|
||||
account: account
|
||||
}, function(err, keypair) {
|
||||
// init outbox
|
||||
self._outboxBo.init();
|
||||
callback(err, keypair);
|
||||
});
|
||||
|
||||
}
|
||||
callback(null, keypair);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return self;
|
||||
|
@ -232,7 +232,12 @@ define(function(require) {
|
||||
self._emailDao.sendPlaintext(invitationMail, function(err) {
|
||||
if (err) {
|
||||
self._outboxBusy = false;
|
||||
callback(err);
|
||||
if (err.code === 42) {
|
||||
// offline try again later
|
||||
callback();
|
||||
} else {
|
||||
callback(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -251,7 +256,12 @@ define(function(require) {
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
self._outboxBusy = false;
|
||||
callback(err);
|
||||
if (err.code === 42) {
|
||||
// offline try again later
|
||||
callback();
|
||||
} else {
|
||||
callback(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -14,32 +14,29 @@ define(function(require) {
|
||||
appController.checkForUpdate();
|
||||
|
||||
// start main application controller
|
||||
appController.start(function(err) {
|
||||
appController.start({
|
||||
onError: $scope.onError
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!window.chrome || !chrome.identity) {
|
||||
$location.path('/desktop');
|
||||
$scope.$apply();
|
||||
return;
|
||||
}
|
||||
|
||||
// login to imap
|
||||
initializeUser();
|
||||
});
|
||||
|
||||
function initializeUser() {
|
||||
// get OAuth token from chrome
|
||||
appController.fetchOAuthToken(function(err, auth) {
|
||||
appController.getEmailAddress(function(err, emailAddress) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// initiate controller by creating email dao
|
||||
appController.init(auth.emailAddress, auth.token, function(err, availableKeys) {
|
||||
appController.init({
|
||||
emailAddress: emailAddress
|
||||
}, function(err, availableKeys) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
|
@ -91,6 +91,13 @@ define(function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (err && err.code === 42) {
|
||||
// offline
|
||||
updateStatus('Offline mode');
|
||||
$scope.$apply();
|
||||
return;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
updateStatus('Error on sync!');
|
||||
$scope.onError(err);
|
||||
@ -150,6 +157,19 @@ define(function(require) {
|
||||
}
|
||||
};
|
||||
|
||||
// share local scope functions with root state
|
||||
$scope.state.mailList = {
|
||||
remove: $scope.remove,
|
||||
synchronize: $scope.synchronize
|
||||
};
|
||||
|
||||
//
|
||||
// watch tasks
|
||||
//
|
||||
|
||||
/**
|
||||
* List emails from folder when user changes folder
|
||||
*/
|
||||
$scope._stopWatchTask = $scope.$watch('state.nav.currentFolder', function() {
|
||||
if (!getFolder()) {
|
||||
return;
|
||||
@ -177,11 +197,16 @@ define(function(require) {
|
||||
$scope.synchronize();
|
||||
});
|
||||
|
||||
// share local scope functions with root state
|
||||
$scope.state.mailList = {
|
||||
remove: $scope.remove,
|
||||
synchronize: $scope.synchronize
|
||||
};
|
||||
/**
|
||||
* Sync current folder when client comes back online
|
||||
*/
|
||||
$scope.$watch('account.online', function(isOnline) {
|
||||
if (isOnline) {
|
||||
$scope.synchronize();
|
||||
} else {
|
||||
updateStatus('Offline mode');
|
||||
}
|
||||
}, true);
|
||||
|
||||
//
|
||||
// helper functions
|
||||
|
@ -181,7 +181,15 @@ define(function(require) {
|
||||
$scope.replyTo.answered = true;
|
||||
emailDao.sync({
|
||||
folder: $scope.state.nav.currentFolder.path
|
||||
}, $scope.onError);
|
||||
}, function(err) {
|
||||
if (err && err.code === 42) {
|
||||
// offline
|
||||
$scope.onError();
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.onError(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -6,21 +6,12 @@ define(function(require) {
|
||||
str = require('js/app-config').string,
|
||||
config = require('js/app-config').config;
|
||||
|
||||
var EmailDAO = function(keychain, imapClient, smtpClient, crypto, devicestorage) {
|
||||
var EmailDAO = function(keychain, crypto, devicestorage) {
|
||||
var self = this;
|
||||
|
||||
self._keychain = keychain;
|
||||
self._imapClient = imapClient;
|
||||
self._smtpClient = smtpClient;
|
||||
self._crypto = crypto;
|
||||
self._devicestorage = devicestorage;
|
||||
|
||||
// delegation-esque pattern to mitigate between node-style events and plain js
|
||||
self._imapClient.onIncomingMessage = function(message) {
|
||||
if (typeof self.onIncomingMessage === 'function') {
|
||||
self.onIncomingMessage(message);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
//
|
||||
@ -33,6 +24,7 @@ define(function(require) {
|
||||
|
||||
self._account = options.account;
|
||||
self._account.busy = false;
|
||||
self._account.online = false;
|
||||
|
||||
// validate email address
|
||||
var emailAddress = self._account.emailAddress;
|
||||
@ -63,31 +55,70 @@ define(function(require) {
|
||||
}
|
||||
|
||||
function initFolders() {
|
||||
self._imapLogin(function(err) {
|
||||
// try init folders from memory, since imap client not initiated yet
|
||||
self._imapListFolders(function(err, folders) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
self._imapListFolders(function(err, folders) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// every folder is initially created with an unread count of 0.
|
||||
// the unread count will be updated after every sync
|
||||
folders.forEach(function(folder){
|
||||
folder.count = 0;
|
||||
});
|
||||
|
||||
self._account.folders = folders;
|
||||
callback(null, keypair);
|
||||
});
|
||||
self._account.folders = folders;
|
||||
callback(null, keypair);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
EmailDAO.prototype.onConnect = function(options, callback) {
|
||||
var self = this;
|
||||
|
||||
self._imapClient = options.imapClient;
|
||||
self._smtpClient = options.smtpClient;
|
||||
|
||||
// delegation-esque pattern to mitigate between node-style events and plain js
|
||||
self._imapClient.onIncomingMessage = function(message) {
|
||||
if (typeof self.onIncomingMessage === 'function') {
|
||||
self.onIncomingMessage(message);
|
||||
}
|
||||
};
|
||||
|
||||
// connect to newly created imap client
|
||||
self._imapLogin(function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// set status to online
|
||||
self._account.online = true;
|
||||
|
||||
// check memory
|
||||
if (self._account.folders) {
|
||||
// no need to init folder again on connect... already in memory
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// init folders
|
||||
self._imapListFolders(function(err, folders) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
self._account.folders = folders;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
EmailDAO.prototype.onDisconnect = function(options, callback) {
|
||||
// set status to online
|
||||
this._account.online = false;
|
||||
this._imapClient = undefined;
|
||||
this._smtpClient = undefined;
|
||||
|
||||
callback();
|
||||
};
|
||||
|
||||
EmailDAO.prototype.unlock = function(options, callback) {
|
||||
var self = this;
|
||||
@ -180,6 +211,7 @@ define(function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check busy status
|
||||
if (self._account.busy) {
|
||||
callback({
|
||||
errMsg: 'Sync aborted: Previous sync still in progress',
|
||||
@ -188,6 +220,7 @@ define(function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
// not busy -> set busy
|
||||
self._account.busy = true;
|
||||
|
||||
folder = _.findWhere(self._account.folders, {
|
||||
@ -547,7 +580,6 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@ -705,6 +737,14 @@ define(function(require) {
|
||||
};
|
||||
|
||||
EmailDAO.prototype._imapMark = function(options, callback) {
|
||||
if (!this._imapClient) {
|
||||
callback({
|
||||
errMsg: 'Client is currently offline!',
|
||||
code: 42
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._imapClient.updateFlags({
|
||||
path: options.folder,
|
||||
uid: options.uid,
|
||||
@ -714,6 +754,14 @@ define(function(require) {
|
||||
};
|
||||
|
||||
EmailDAO.prototype.move = function(options, callback) {
|
||||
if (!this._imapClient) {
|
||||
callback({
|
||||
errMsg: 'Client is currently offline!',
|
||||
code: 42
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._imapClient.moveMessage({
|
||||
path: options.folder,
|
||||
uid: options.uid,
|
||||
@ -725,6 +773,14 @@ define(function(require) {
|
||||
var self = this,
|
||||
email = options.email;
|
||||
|
||||
if (!self._smtpClient) {
|
||||
callback({
|
||||
errMsg: 'Client is currently offline!',
|
||||
code: 42
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// validate the email input
|
||||
if (!email.to || !email.from || !email.to[0].address || !email.from[0].address) {
|
||||
callback({
|
||||
@ -782,6 +838,14 @@ define(function(require) {
|
||||
};
|
||||
|
||||
EmailDAO.prototype.sendPlaintext = function(options, callback) {
|
||||
if (!this._smtpClient) {
|
||||
callback({
|
||||
errMsg: 'Client is currently offline!',
|
||||
code: 42
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._smtpClient.send(options.email, callback);
|
||||
};
|
||||
|
||||
@ -866,6 +930,14 @@ define(function(require) {
|
||||
* Login the imap client
|
||||
*/
|
||||
EmailDAO.prototype._imapLogin = function(callback) {
|
||||
if (!this._imapClient) {
|
||||
callback({
|
||||
errMsg: 'Client is currently offline!',
|
||||
code: 42
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// login IMAP client if existent
|
||||
this._imapClient.login(callback);
|
||||
};
|
||||
@ -874,6 +946,14 @@ define(function(require) {
|
||||
* Cleanup by logging the user off.
|
||||
*/
|
||||
EmailDAO.prototype._imapLogout = function(callback) {
|
||||
if (!this._imapClient) {
|
||||
callback({
|
||||
errMsg: 'Client is currently offline!',
|
||||
code: 42
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._imapClient.logout(callback);
|
||||
};
|
||||
|
||||
@ -882,6 +962,14 @@ define(function(require) {
|
||||
* @param {String} options.folderName The name of the imap folder.
|
||||
*/
|
||||
EmailDAO.prototype._imapListMessages = function(options, callback) {
|
||||
if (!this._imapClient) {
|
||||
callback({
|
||||
errMsg: 'Client is currently offline!',
|
||||
code: 42
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._imapClient.listMessages({
|
||||
path: options.folder,
|
||||
offset: 0,
|
||||
@ -890,6 +978,14 @@ define(function(require) {
|
||||
};
|
||||
|
||||
EmailDAO.prototype._imapDeleteMessage = function(options, callback) {
|
||||
if (!this._imapClient) {
|
||||
callback({
|
||||
errMsg: 'Client is currently offline!',
|
||||
code: 42
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._imapClient.deleteMessage({
|
||||
path: options.folder,
|
||||
uid: options.uid
|
||||
@ -901,6 +997,14 @@ define(function(require) {
|
||||
* @param {String} options.messageId The
|
||||
*/
|
||||
EmailDAO.prototype._imapGetMessage = function(options, callback) {
|
||||
if (!this._imapClient) {
|
||||
callback({
|
||||
errMsg: 'Client is currently offline!',
|
||||
code: 42
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._imapClient.getMessagePreview({
|
||||
path: options.folder,
|
||||
uid: options.uid
|
||||
@ -933,6 +1037,14 @@ define(function(require) {
|
||||
function fetchFromServer() {
|
||||
var folders;
|
||||
|
||||
if (!self._imapClient) {
|
||||
callback({
|
||||
errMsg: 'Client is currently offline!',
|
||||
code: 42
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// fetch list from imap server
|
||||
self._imapClient.listWellKnownFolders(function(err, wellKnownFolders) {
|
||||
if (err) {
|
||||
|
@ -63,6 +63,12 @@
|
||||
color: $color-grey-dark;
|
||||
line-height: em(28,12);
|
||||
|
||||
.offline {
|
||||
&[data-icon]:before {
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&.syncing {
|
||||
.spinner {
|
||||
top: 6.5px;
|
||||
|
@ -23,6 +23,11 @@
|
||||
|
||||
<footer ng-class="{syncing: account.busy}" ng-click="synchronize()">
|
||||
<span class="spinner"></span>
|
||||
<span class="text">{{lastUpdateLbl}} {{lastUpdate | date:'shortTime'}}</span>
|
||||
<span class="text" ng-switch="account.online">
|
||||
<span ng-switch-when="false">
|
||||
<span class="offline" data-icon=""></span>
|
||||
</span>
|
||||
{{lastUpdateLbl}} {{lastUpdate | date:'shortTime'}}
|
||||
</span>
|
||||
</footer>
|
||||
</div><!--/.view-mail-list-->
|
@ -4,18 +4,19 @@
|
||||
</header>
|
||||
|
||||
<ul class="nav-primary">
|
||||
<li ng-repeat="folder in account.folders" ng-switch="folder.count !== undefined">
|
||||
<li ng-repeat="folder in account.folders" ng-switch="folder.count !== undefined && folder.count > 0">
|
||||
<a href="#" ng-click="openFolder(folder); $event.preventDefault()">
|
||||
{{folder.type}}
|
||||
<span class="label-wrapper" ng-switch-when="true"><span class="label label-light">{{folder.count}}</span></span>
|
||||
<span class="label-wrapper" ng-switch-when="true">
|
||||
<span class="label label-light">{{folder.count}}</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav-secondary">
|
||||
<li><a href="#" ng-click="state.account.toggle(true); $event.preventDefault()">Account</a></li>
|
||||
<li><a href="#" ng-click="$event.preventDefault()">About whiteout.io</a></li>
|
||||
<li><a href="#" ng-click="$event.preventDefault()">Help</a></li>
|
||||
<li><a href="http://whiteout.io" target="_blank">About whiteout.io</a></li>
|
||||
</ul>
|
||||
|
||||
<footer>
|
||||
|
@ -3,6 +3,7 @@ define(function(require) {
|
||||
|
||||
var controller = require('js/app-controller'),
|
||||
EmailDAO = require('js/dao/email-dao'),
|
||||
OutboxBO = require('js/bo/outbox'),
|
||||
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
||||
$ = require('jquery'),
|
||||
expect = chai.expect;
|
||||
@ -13,12 +14,18 @@ define(function(require) {
|
||||
};
|
||||
|
||||
describe('App Controller unit tests', function() {
|
||||
var emailDaoStub, outboxStub, appConfigStoreStub, isOnlineStub,
|
||||
identityStub;
|
||||
|
||||
beforeEach(function() {
|
||||
sinon.stub(controller, 'init', function(userId, password, token, callback) {
|
||||
controller._emailDao = sinon.createStubInstance(EmailDAO);
|
||||
callback();
|
||||
});
|
||||
emailDaoStub = sinon.createStubInstance(EmailDAO);
|
||||
controller._emailDao = emailDaoStub;
|
||||
outboxStub = sinon.createStubInstance(OutboxBO);
|
||||
controller._outboxBo = outboxStub;
|
||||
appConfigStoreStub = sinon.createStubInstance(DeviceStorageDAO);
|
||||
controller._appConfigStore = appConfigStoreStub;
|
||||
|
||||
isOnlineStub = sinon.stub(controller, 'isOnline');
|
||||
|
||||
sinon.stub($, 'get');
|
||||
sinon.stub($, 'ajax').yieldsTo('success', {
|
||||
@ -30,58 +37,285 @@ define(function(require) {
|
||||
if (typeof window.chrome.identity.getAuthToken !== 'function') {
|
||||
window.chrome.identity.getAuthToken = function() {};
|
||||
}
|
||||
sinon.stub(window.chrome.identity, 'getAuthToken');
|
||||
window.chrome.identity.getAuthToken.yields('token42');
|
||||
identityStub = sinon.stub(window.chrome.identity, 'getAuthToken');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
controller.init.restore();
|
||||
$.get.restore();
|
||||
$.ajax.restore();
|
||||
window.chrome.identity.getAuthToken.restore();
|
||||
identityStub.restore();
|
||||
isOnlineStub.restore();
|
||||
});
|
||||
|
||||
describe('start', function() {
|
||||
it('should not explode', function(done) {
|
||||
controller.start(function(err) {
|
||||
controller.start({
|
||||
onError: function() {}
|
||||
}, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('onDisconnect', function() {
|
||||
it('should work', function(done) {
|
||||
emailDaoStub.onDisconnect.yields();
|
||||
|
||||
controller.onDisconnect(function(err) {
|
||||
expect(err).to.not.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('onConnect', function() {
|
||||
var fetchOAuthTokenStub, getCertficateStub;
|
||||
|
||||
beforeEach(function() {
|
||||
// buildModules
|
||||
fetchOAuthTokenStub = sinon.stub(controller, 'fetchOAuthToken');
|
||||
getCertficateStub = sinon.stub(controller, 'getCertficate');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
fetchOAuthTokenStub.restore();
|
||||
getCertficateStub.restore();
|
||||
});
|
||||
|
||||
it('should not connect if offline', function(done) {
|
||||
isOnlineStub.returns(false);
|
||||
|
||||
controller.onConnect(function(err) {
|
||||
expect(err).to.not.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail due to error in certificate', function(done) {
|
||||
isOnlineStub.returns(true);
|
||||
getCertficateStub.yields({});
|
||||
|
||||
controller.onConnect(function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(getCertficateStub.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail due to error in fetch oauth', function(done) {
|
||||
isOnlineStub.returns(true);
|
||||
getCertficateStub.yields(null, 'PEM');
|
||||
fetchOAuthTokenStub.yields({});
|
||||
|
||||
controller.onConnect(function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(fetchOAuthTokenStub.calledOnce).to.be.true;
|
||||
expect(getCertficateStub.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
isOnlineStub.returns(true);
|
||||
fetchOAuthTokenStub.yields(null, {
|
||||
emailAddress: 'asfd@example.com'
|
||||
});
|
||||
getCertficateStub.yields(null, 'PEM');
|
||||
emailDaoStub.onConnect.yields();
|
||||
|
||||
controller.onConnect(function(err) {
|
||||
expect(err).to.not.exist;
|
||||
expect(fetchOAuthTokenStub.calledOnce).to.be.true;
|
||||
expect(getCertficateStub.calledOnce).to.be.true;
|
||||
expect(emailDaoStub.onConnect.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEmailAddress', function() {
|
||||
var fetchOAuthTokenStub;
|
||||
|
||||
beforeEach(function() {
|
||||
// buildModules
|
||||
fetchOAuthTokenStub = sinon.stub(controller, 'fetchOAuthToken');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
fetchOAuthTokenStub.restore();
|
||||
});
|
||||
|
||||
it('should fail due to error in config list items', function(done) {
|
||||
appConfigStoreStub.listItems.yields({});
|
||||
|
||||
controller.getEmailAddress(function(err, emailAddress) {
|
||||
expect(err).to.exist;
|
||||
expect(emailAddress).to.not.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should work if address is already cached', function(done) {
|
||||
appConfigStoreStub.listItems.yields(null, ['asdf']);
|
||||
|
||||
controller.getEmailAddress(function(err, emailAddress) {
|
||||
expect(err).to.not.exist;
|
||||
expect(emailAddress).to.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail first time if app is offline', function(done) {
|
||||
appConfigStoreStub.listItems.yields(null, []);
|
||||
isOnlineStub.returns(false);
|
||||
|
||||
controller.getEmailAddress(function(err, emailAddress) {
|
||||
expect(err).to.exist;
|
||||
expect(emailAddress).to.not.exist;
|
||||
expect(isOnlineStub.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail due to error in fetchOAuthToken', function(done) {
|
||||
appConfigStoreStub.listItems.yields(null, []);
|
||||
isOnlineStub.returns(true);
|
||||
fetchOAuthTokenStub.yields({});
|
||||
|
||||
controller.getEmailAddress(function(err, emailAddress) {
|
||||
expect(err).to.exist;
|
||||
expect(emailAddress).to.not.exist;
|
||||
expect(fetchOAuthTokenStub.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail work when fetching oauth token', function(done) {
|
||||
appConfigStoreStub.listItems.yields(null, []);
|
||||
isOnlineStub.returns(true);
|
||||
fetchOAuthTokenStub.yields(null, {
|
||||
emailAddress: 'asfd@example.com'
|
||||
});
|
||||
|
||||
controller.getEmailAddress(function(err, emailAddress) {
|
||||
expect(err).to.not.exist;
|
||||
expect(emailAddress).to.equal('asfd@example.com');
|
||||
expect(fetchOAuthTokenStub.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchOAuthToken', function() {
|
||||
beforeEach(function() {
|
||||
controller._appConfigStore = sinon.createStubInstance(DeviceStorageDAO);
|
||||
});
|
||||
|
||||
it('should work the first time', function(done) {
|
||||
controller._appConfigStore.listItems.yields(null, []);
|
||||
controller._appConfigStore.storeList.yields();
|
||||
appConfigStoreStub.listItems.yields(null, []);
|
||||
appConfigStoreStub.storeList.yields();
|
||||
identityStub.yields('token42');
|
||||
|
||||
controller.fetchOAuthToken(function(err) {
|
||||
expect(err).to.not.exist;
|
||||
expect(controller._appConfigStore.listItems.calledOnce).to.be.true;
|
||||
expect(controller._appConfigStore.storeList.calledOnce).to.be.true;
|
||||
expect(window.chrome.identity.getAuthToken.calledOnce).to.be.true;
|
||||
expect(appConfigStoreStub.listItems.calledOnce).to.be.true;
|
||||
expect(appConfigStoreStub.storeList.calledOnce).to.be.true;
|
||||
expect(identityStub.calledOnce).to.be.true;
|
||||
expect($.ajax.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should work when the email address is cached', function(done) {
|
||||
controller._appConfigStore.listItems.yields(null, ['asdf']);
|
||||
appConfigStoreStub.listItems.yields(null, ['asdf']);
|
||||
identityStub.yields('token42');
|
||||
|
||||
controller.fetchOAuthToken(function(err) {
|
||||
expect(err).to.not.exist;
|
||||
expect(controller._appConfigStore.listItems.calledOnce).to.be.true;
|
||||
expect(window.chrome.identity.getAuthToken.calledOnce).to.be.true;
|
||||
expect(appConfigStoreStub.listItems.calledOnce).to.be.true;
|
||||
expect(identityStub.calledOnce).to.be.true;
|
||||
expect($.ajax.called).to.be.false;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildModules', function() {
|
||||
it('should work', function() {
|
||||
controller.buildModules();
|
||||
expect(controller._keychain).to.exist;
|
||||
expect(controller._crypto).to.exist;
|
||||
expect(controller._emailDao).to.exist;
|
||||
expect(controller._outboxBo).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('init', function() {
|
||||
var buildModulesStub, onConnectStub;
|
||||
|
||||
beforeEach(function() {
|
||||
// buildModules
|
||||
buildModulesStub = sinon.stub(controller, 'buildModules');
|
||||
buildModulesStub.returns();
|
||||
// onConnect
|
||||
onConnectStub = sinon.stub(controller, 'onConnect');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
buildModulesStub.restore();
|
||||
onConnectStub.restore();
|
||||
});
|
||||
|
||||
it('should fail due to error in emailDao.init', function(done) {
|
||||
emailDaoStub.init.yields({});
|
||||
|
||||
controller.init({}, function(err) {
|
||||
expect(err).to.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail due to error in onConnect', function(done) {
|
||||
emailDaoStub.init.yields();
|
||||
|
||||
onConnectStub.yields({});
|
||||
|
||||
controller.init({}, function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(onConnectStub.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass email dao init when offline', function(done) {
|
||||
emailDaoStub.init.yields({
|
||||
code: 42
|
||||
});
|
||||
|
||||
onConnectStub.yields();
|
||||
outboxStub.init.returns();
|
||||
|
||||
controller.init({}, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
expect(onConnectStub.calledOnce).to.be.true;
|
||||
expect(outboxStub.init.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should work and return a keypair', function(done) {
|
||||
emailDaoStub.init.yields(null, {});
|
||||
|
||||
onConnectStub.yields();
|
||||
outboxStub.init.returns();
|
||||
|
||||
controller.init({}, function(err, keypair) {
|
||||
expect(err).to.not.exist;
|
||||
expect(keypair).to.exist;
|
||||
expect(onConnectStub.calledOnce).to.be.true;
|
||||
expect(outboxStub.init.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -17,7 +17,7 @@ define(function(require) {
|
||||
dummyDecryptedMail, mockKeyPair, account, publicKey, verificationMail, verificationUuid,
|
||||
nonWhitelistedMail;
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(function(done) {
|
||||
emailAddress = 'asdf@asdf.com';
|
||||
passphrase = 'asdf';
|
||||
asymKeySize = 2048;
|
||||
@ -98,17 +98,41 @@ define(function(require) {
|
||||
pgpStub = sinon.createStubInstance(PGP);
|
||||
devicestorageStub = sinon.createStubInstance(DeviceStorageDAO);
|
||||
|
||||
dao = new EmailDAO(keychainStub, imapClientStub, smtpClientStub, pgpStub, devicestorageStub);
|
||||
dao = new EmailDAO(keychainStub, pgpStub, devicestorageStub);
|
||||
dao._account = account;
|
||||
|
||||
expect(dao._keychain).to.equal(keychainStub);
|
||||
expect(dao._imapClient).to.equal(imapClientStub);
|
||||
expect(dao._smtpClient).to.equal(smtpClientStub);
|
||||
expect(dao._crypto).to.equal(pgpStub);
|
||||
expect(dao._devicestorage).to.equal(devicestorageStub);
|
||||
|
||||
// connect
|
||||
expect(dao._imapClient).to.not.exist;
|
||||
expect(dao._smtpClient).to.not.exist;
|
||||
expect(dao._account.online).to.be.undefined;
|
||||
dao._account.folders = [];
|
||||
imapClientStub.login.yields();
|
||||
|
||||
dao.onConnect({
|
||||
imapClient: imapClientStub,
|
||||
smtpClient: smtpClientStub
|
||||
}, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
expect(dao._account.online).to.be.true;
|
||||
expect(dao._imapClient).to.equal(dao._imapClient);
|
||||
expect(dao._smtpClient).to.equal(dao._smtpClient);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {});
|
||||
afterEach(function(done) {
|
||||
dao.onDisconnect(null, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
expect(dao._account.online).to.be.false;
|
||||
expect(dao._imapClient).to.not.exist;
|
||||
expect(dao._smtpClient).to.not.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('push', function() {
|
||||
it('should work', function(done) {
|
||||
@ -129,7 +153,7 @@ define(function(require) {
|
||||
});
|
||||
|
||||
it('should init', function(done) {
|
||||
var loginStub, listFolderStub, folders;
|
||||
var listFolderStub, folders;
|
||||
|
||||
folders = [{}, {}];
|
||||
|
||||
@ -138,24 +162,22 @@ define(function(require) {
|
||||
keychainStub.getUserKeyPair.yields(null, mockKeyPair);
|
||||
|
||||
// initFolders
|
||||
loginStub = sinon.stub(dao, '_imapLogin');
|
||||
listFolderStub = sinon.stub(dao, '_imapListFolders');
|
||||
loginStub.yields();
|
||||
listFolderStub.yields(null, folders);
|
||||
|
||||
dao.init({
|
||||
account: account
|
||||
}, function(err, keyPair) {
|
||||
expect(err).to.not.exist;
|
||||
expect(dao._account.busy).to.be.false;
|
||||
expect(dao._account.online).to.be.false;
|
||||
expect(keyPair).to.equal(mockKeyPair);
|
||||
|
||||
expect(dao._account).to.equal(account);
|
||||
expect(dao._account.folders).to.equal(folders);
|
||||
expect(dao._account.folders[0].count).to.equal(0);
|
||||
expect(devicestorageStub.init.calledOnce).to.be.true;
|
||||
expect(keychainStub.getUserKeyPair.calledOnce).to.be.true;
|
||||
|
||||
expect(loginStub.calledOnce).to.be.true;
|
||||
expect(listFolderStub.calledOnce).to.be.true;
|
||||
|
||||
done();
|
||||
@ -163,16 +185,14 @@ define(function(require) {
|
||||
});
|
||||
|
||||
it('should fail due to error while listing folders', function(done) {
|
||||
var loginStub, listFolderStub;
|
||||
var listFolderStub;
|
||||
|
||||
// initKeychain
|
||||
devicestorageStub.init.withArgs(emailAddress).yields();
|
||||
keychainStub.getUserKeyPair.yields(null, mockKeyPair);
|
||||
|
||||
// initFolders
|
||||
loginStub = sinon.stub(dao, '_imapLogin');
|
||||
listFolderStub = sinon.stub(dao, '_imapListFolders');
|
||||
loginStub.yields();
|
||||
listFolderStub.yields({});
|
||||
|
||||
dao.init({
|
||||
@ -184,40 +204,12 @@ define(function(require) {
|
||||
expect(dao._account).to.equal(account);
|
||||
expect(devicestorageStub.init.calledOnce).to.be.true;
|
||||
expect(keychainStub.getUserKeyPair.calledOnce).to.be.true;
|
||||
|
||||
expect(loginStub.calledOnce).to.be.true;
|
||||
expect(listFolderStub.calledOnce).to.be.true;
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail due to error during imap login', function(done) {
|
||||
var loginStub = sinon.stub(dao, '_imapLogin');
|
||||
|
||||
// initKeychain
|
||||
devicestorageStub.init.withArgs(emailAddress).yields();
|
||||
keychainStub.getUserKeyPair.yields(null, mockKeyPair);
|
||||
|
||||
// initFolders
|
||||
loginStub.yields({});
|
||||
|
||||
dao.init({
|
||||
account: account
|
||||
}, function(err, keyPair) {
|
||||
expect(err).to.exist;
|
||||
expect(keyPair).to.not.exist;
|
||||
|
||||
expect(dao._account).to.equal(account);
|
||||
expect(devicestorageStub.init.calledOnce).to.be.true;
|
||||
expect(keychainStub.getUserKeyPair.calledOnce).to.be.true;
|
||||
|
||||
expect(loginStub.calledOnce).to.be.true;
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail due to error in getUserKeyPair', function(done) {
|
||||
devicestorageStub.init.yields();
|
||||
keychainStub.getUserKeyPair.yields({});
|
||||
@ -235,6 +227,77 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
|
||||
describe('onConnect', function() {
|
||||
var imapLoginStub, imapListFoldersStub;
|
||||
|
||||
beforeEach(function(done) {
|
||||
// imap login
|
||||
imapLoginStub = sinon.stub(dao, '_imapLogin');
|
||||
imapListFoldersStub = sinon.stub(dao, '_imapListFolders');
|
||||
|
||||
dao.onDisconnect(null, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
expect(dao._imapClient).to.not.exist;
|
||||
expect(dao._smtpClient).to.not.exist;
|
||||
expect(dao._account.online).to.be.false;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
imapLoginStub.restore();
|
||||
imapListFoldersStub.restore();
|
||||
});
|
||||
|
||||
it('should fail due to error in imap login', function(done) {
|
||||
imapLoginStub.yields({});
|
||||
|
||||
dao.onConnect({
|
||||
imapClient: imapClientStub,
|
||||
smtpClient: smtpClientStub
|
||||
}, function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(imapLoginStub.calledOnce).to.be.true;
|
||||
expect(dao._account.online).to.be.false;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should work when folder already initiated', function(done) {
|
||||
dao._account.folders = [];
|
||||
imapLoginStub.yields();
|
||||
|
||||
dao.onConnect({
|
||||
imapClient: imapClientStub,
|
||||
smtpClient: smtpClientStub
|
||||
}, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
expect(dao._account.online).to.be.true;
|
||||
expect(dao._imapClient).to.equal(dao._imapClient);
|
||||
expect(dao._smtpClient).to.equal(dao._smtpClient);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should work when folder not yet initiated', function(done) {
|
||||
var folders = [];
|
||||
imapLoginStub.yields();
|
||||
imapListFoldersStub.yields(null, folders);
|
||||
|
||||
dao.onConnect({
|
||||
imapClient: imapClientStub,
|
||||
smtpClient: smtpClientStub
|
||||
}, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
expect(dao._account.online).to.be.true;
|
||||
expect(dao._imapClient).to.equal(dao._imapClient);
|
||||
expect(dao._smtpClient).to.equal(dao._smtpClient);
|
||||
expect(dao._account.folders).to.deep.equal(folders);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unlock', function() {
|
||||
it('should unlock', function(done) {
|
||||
var importMatcher = sinon.match(function(o) {
|
||||
@ -362,6 +425,17 @@ define(function(require) {
|
||||
});
|
||||
|
||||
describe('_imapLogin', function() {
|
||||
it('should fail when disconnected', function(done) {
|
||||
dao.onDisconnect(null, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
|
||||
dao._imapLogin(function(err) {
|
||||
expect(err.code).to.equal(42);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
imapClientStub.login.yields();
|
||||
|
||||
@ -382,6 +456,17 @@ define(function(require) {
|
||||
});
|
||||
|
||||
describe('_imapLogout', function() {
|
||||
it('should fail when disconnected', function(done) {
|
||||
dao.onDisconnect(null, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
|
||||
dao._imapLogout(function(err) {
|
||||
expect(err.code).to.equal(42);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
imapClientStub.logout.yields();
|
||||
|
||||
@ -433,6 +518,19 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when disconnected', function(done) {
|
||||
devicestorageStub.listItems.yields(null, []);
|
||||
|
||||
dao.onDisconnect(null, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
|
||||
dao._imapListFolders(function(err) {
|
||||
expect(err.code).to.equal(42);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should list from imap', function(done) {
|
||||
devicestorageStub.listItems.yields(null, []);
|
||||
imapClientStub.listWellKnownFolders.yields(null, {
|
||||
@ -483,6 +581,17 @@ define(function(require) {
|
||||
});
|
||||
|
||||
describe('_imapListMessages', function() {
|
||||
it('should fail when disconnected', function(done) {
|
||||
dao.onDisconnect(null, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
|
||||
dao._imapListMessages({}, function(err) {
|
||||
expect(err.code).to.equal(42);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
var path = 'FOLDAAAA';
|
||||
|
||||
@ -499,6 +608,17 @@ define(function(require) {
|
||||
});
|
||||
|
||||
describe('_imapDeleteMessage', function() {
|
||||
it('should fail when disconnected', function(done) {
|
||||
dao.onDisconnect(null, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
|
||||
dao._imapDeleteMessage({}, function(err) {
|
||||
expect(err.code).to.equal(42);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
var path = 'FOLDAAAA',
|
||||
uid = 1337;
|
||||
@ -516,6 +636,17 @@ define(function(require) {
|
||||
});
|
||||
|
||||
describe('_imapGetMessage', function() {
|
||||
it('should fail when disconnected', function(done) {
|
||||
dao.onDisconnect(null, function(err) {
|
||||
expect(err).to.not.exist;
|
||||
|
||||
dao._imapGetMessage({}, function(err) {
|
||||
expect(err.code).to.equal(42);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
var path = 'FOLDAAAA',
|
||||
uid = 1337;
|
||||
|
@ -11,10 +11,9 @@ define(function(require) {
|
||||
describe('Login Controller unit test', function() {
|
||||
var scope, location, ctrl, origEmailDao, emailDaoMock,
|
||||
emailAddress = 'fred@foo.com',
|
||||
oauthToken = 'foobarfoobar',
|
||||
startAppStub,
|
||||
checkForUpdateStub,
|
||||
fetchOAuthStub,
|
||||
getEmailAddressStub,
|
||||
initStub;
|
||||
|
||||
describe('initialization', function() {
|
||||
@ -30,6 +29,11 @@ define(function(require) {
|
||||
origEmailDao = appController._emailDao;
|
||||
emailDaoMock = sinon.createStubInstance(EmailDAO);
|
||||
appController._emailDao = emailDaoMock;
|
||||
|
||||
startAppStub = sinon.stub(appController, 'start');
|
||||
checkForUpdateStub = sinon.stub(appController, 'checkForUpdate');
|
||||
getEmailAddressStub = sinon.stub(appController, 'getEmailAddress');
|
||||
initStub = sinon.stub(appController, 'init');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@ -49,18 +53,16 @@ define(function(require) {
|
||||
appController.fetchOAuthToken.restore && appController.fetchOAuthToken.restore();
|
||||
appController.init.restore && appController.init.restore();
|
||||
location.path.restore && location.path.restore();
|
||||
|
||||
startAppStub.restore();
|
||||
checkForUpdateStub.restore();
|
||||
getEmailAddressStub.restore();
|
||||
initStub.restore();
|
||||
});
|
||||
|
||||
it('should forward to existing user login', function(done) {
|
||||
startAppStub = sinon.stub(appController, 'start');
|
||||
startAppStub.yields();
|
||||
checkForUpdateStub = sinon.stub(appController, 'checkForUpdate');
|
||||
fetchOAuthStub = sinon.stub(appController, 'fetchOAuthToken');
|
||||
fetchOAuthStub.yields(null, {
|
||||
emailAddress: emailAddress,
|
||||
token: oauthToken
|
||||
});
|
||||
initStub = sinon.stub(appController, 'init');
|
||||
getEmailAddressStub.yields(null, emailAddress);
|
||||
initStub.yields(null, {
|
||||
privateKey: 'a',
|
||||
publicKey: 'b'
|
||||
@ -74,7 +76,7 @@ define(function(require) {
|
||||
expect(path).to.equal('/login-existing');
|
||||
expect(startAppStub.calledOnce).to.be.true;
|
||||
expect(checkForUpdateStub.calledOnce).to.be.true;
|
||||
expect(fetchOAuthStub.calledOnce).to.be.true;
|
||||
expect(getEmailAddressStub.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
scope = $rootScope.$new();
|
||||
@ -87,15 +89,8 @@ define(function(require) {
|
||||
});
|
||||
|
||||
it('should forward to new device login', function(done) {
|
||||
startAppStub = sinon.stub(appController, 'start');
|
||||
startAppStub.yields();
|
||||
checkForUpdateStub = sinon.stub(appController, 'checkForUpdate');
|
||||
fetchOAuthStub = sinon.stub(appController, 'fetchOAuthToken');
|
||||
fetchOAuthStub.yields(null, {
|
||||
emailAddress: emailAddress,
|
||||
token: oauthToken
|
||||
});
|
||||
initStub = sinon.stub(appController, 'init');
|
||||
getEmailAddressStub.yields(null, emailAddress);
|
||||
initStub.yields(null, {
|
||||
publicKey: 'b'
|
||||
});
|
||||
@ -108,7 +103,7 @@ define(function(require) {
|
||||
expect(path).to.equal('/login-new-device');
|
||||
expect(startAppStub.calledOnce).to.be.true;
|
||||
expect(checkForUpdateStub.calledOnce).to.be.true;
|
||||
expect(fetchOAuthStub.calledOnce).to.be.true;
|
||||
expect(getEmailAddressStub.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
scope = $rootScope.$new();
|
||||
@ -121,15 +116,8 @@ define(function(require) {
|
||||
});
|
||||
|
||||
it('should forward to initial login', function(done) {
|
||||
startAppStub = sinon.stub(appController, 'start');
|
||||
startAppStub.yields();
|
||||
checkForUpdateStub = sinon.stub(appController, 'checkForUpdate');
|
||||
fetchOAuthStub = sinon.stub(appController, 'fetchOAuthToken');
|
||||
fetchOAuthStub.yields(null, {
|
||||
emailAddress: emailAddress,
|
||||
token: oauthToken
|
||||
});
|
||||
initStub = sinon.stub(appController, 'init');
|
||||
getEmailAddressStub.yields(null, emailAddress);
|
||||
initStub.yields();
|
||||
|
||||
angular.module('logintest', []);
|
||||
@ -140,34 +128,7 @@ define(function(require) {
|
||||
expect(path).to.equal('/login-initial');
|
||||
expect(startAppStub.calledOnce).to.be.true;
|
||||
expect(checkForUpdateStub.calledOnce).to.be.true;
|
||||
expect(fetchOAuthStub.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
scope = $rootScope.$new();
|
||||
scope.state = {};
|
||||
ctrl = $controller(LoginCtrl, {
|
||||
$location: location,
|
||||
$scope: scope
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should fall back to dev mode', function(done) {
|
||||
var chromeIdentity;
|
||||
|
||||
chromeIdentity = window.chrome.identity;
|
||||
delete window.chrome.identity;
|
||||
|
||||
startAppStub = sinon.stub(appController, 'start');
|
||||
startAppStub.yields();
|
||||
|
||||
angular.module('logintest', []);
|
||||
mocks.module('logintest');
|
||||
mocks.inject(function($controller, $rootScope, $location) {
|
||||
location = $location;
|
||||
sinon.stub(location, 'path', function(path) {
|
||||
expect(path).to.equal('/desktop');
|
||||
window.chrome.identity = chromeIdentity;
|
||||
expect(getEmailAddressStub.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
scope = $rootScope.$new();
|
||||
|
@ -141,6 +141,42 @@ define(function(require) {
|
||||
});
|
||||
|
||||
describe('send to outbox', function() {
|
||||
it('should work when offline', function(done) {
|
||||
var verifyToSpy = sinon.spy(scope, 'verifyTo'),
|
||||
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(verifyToSpy.calledOnce).to.be.true;
|
||||
expect(emailDaoMock.store.calledOnce).to.be.true;
|
||||
expect(emailDaoMock.sync.calledOnce).to.be.true;
|
||||
|
||||
scope.verifyTo.restore();
|
||||
done();
|
||||
};
|
||||
|
||||
emailDaoMock.store.yields();
|
||||
emailDaoMock.sync.yields({
|
||||
code: 42
|
||||
});
|
||||
|
||||
scope.state.writer.write(re);
|
||||
scope.sendToOutbox();
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
var verifyToSpy = sinon.spy(scope, 'verifyTo'),
|
||||
re = {
|
||||
@ -173,7 +209,40 @@ define(function(require) {
|
||||
|
||||
scope.state.writer.write(re);
|
||||
scope.sendToOutbox();
|
||||
});
|
||||
|
||||
it('should fail', function(done) {
|
||||
var verifyToSpy = sinon.spy(scope, 'verifyTo'),
|
||||
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.exist;
|
||||
expect(scope.state.writer.open).to.be.false;
|
||||
expect(verifyToSpy.calledOnce).to.be.true;
|
||||
expect(emailDaoMock.store.calledOnce).to.be.true;
|
||||
expect(emailDaoMock.sync.calledOnce).to.be.true;
|
||||
|
||||
scope.verifyTo.restore();
|
||||
done();
|
||||
};
|
||||
|
||||
emailDaoMock.store.yields();
|
||||
emailDaoMock.sync.yields({});
|
||||
|
||||
scope.state.writer.write(re);
|
||||
scope.sendToOutbox();
|
||||
});
|
||||
|
||||
it('should not work and not close the write view', function(done) {
|
||||
|
Loading…
Reference in New Issue
Block a user