2013-06-06 07:34:22 -04:00
|
|
|
/**
|
|
|
|
* The main application controller
|
|
|
|
*/
|
2013-08-20 09:19:13 -04:00
|
|
|
define(function(require) {
|
2013-08-20 13:48:49 -04:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var $ = require('jquery'),
|
2013-09-19 12:35:12 -04:00
|
|
|
ImapClient = require('imap-client'),
|
2014-02-03 16:07:39 -05:00
|
|
|
PgpMailer = require('pgpmailer'),
|
2013-08-20 13:48:49 -04:00
|
|
|
EmailDAO = require('js/dao/email-dao'),
|
2013-10-29 07:19:27 -04:00
|
|
|
RestDAO = require('js/dao/rest-dao'),
|
|
|
|
PublicKeyDAO = require('js/dao/publickey-dao'),
|
|
|
|
LawnchairDAO = require('js/dao/lawnchair-dao'),
|
2013-08-20 13:48:49 -04:00
|
|
|
KeychainDAO = require('js/dao/keychain-dao'),
|
2013-09-26 07:26:57 -04:00
|
|
|
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
|
2013-11-19 10:14:48 -05:00
|
|
|
InvitationDAO = require('js/dao/invitation-dao'),
|
|
|
|
OutboxBO = require('js/bo/outbox'),
|
2013-10-11 21:19:01 -04:00
|
|
|
PGP = require('js/crypto/pgp'),
|
2013-09-15 09:13:19 -04:00
|
|
|
config = require('js/app-config').config;
|
2013-08-20 13:48:49 -04:00
|
|
|
|
|
|
|
var self = {};
|
|
|
|
|
|
|
|
/**
|
2013-10-09 10:40:36 -04:00
|
|
|
* Start the application
|
2013-08-20 13:48:49 -04:00
|
|
|
*/
|
2013-12-09 13:21:52 -05:00
|
|
|
self.start = function(options, callback) {
|
2014-02-01 08:43:15 -05:00
|
|
|
// are we running in a cordova app or in a browser environment?
|
|
|
|
if (window.cordova) {
|
|
|
|
// wait for 'deviceready' event to make sure plugins are loaded
|
2013-08-20 13:48:49 -04:00
|
|
|
console.log('Assuming Cordova environment...');
|
|
|
|
document.addEventListener("deviceready", onDeviceReady, false);
|
2014-02-01 08:43:15 -05:00
|
|
|
} else {
|
|
|
|
// No need to wait on events... just start the app
|
|
|
|
console.log('Assuming Browser environment...');
|
|
|
|
onDeviceReady();
|
2013-08-20 13:48:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function onDeviceReady() {
|
|
|
|
console.log('Starting app.');
|
2013-12-09 13:21:52 -05:00
|
|
|
|
|
|
|
// Handle offline and online gracefully
|
|
|
|
window.addEventListener('online', self.onConnect.bind(self, options.onError));
|
|
|
|
window.addEventListener('offline', self.onDisconnect.bind(self, options.onError));
|
|
|
|
|
2013-10-09 10:40:36 -04:00
|
|
|
// init app config storage
|
2013-10-29 07:19:27 -04:00
|
|
|
self._appConfigStore = new DeviceStorageDAO(new LawnchairDAO());
|
2013-10-09 10:40:36 -04:00
|
|
|
self._appConfigStore.init('app-config', callback);
|
2013-08-20 13:48:49 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
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) {
|
2014-02-03 16:07:39 -05:00
|
|
|
var auth, imapOptions, imapClient, smtpOptions, pgpMailer;
|
2013-12-09 13:21:52 -05:00
|
|
|
|
|
|
|
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 = {
|
2014-02-03 16:07:39 -05:00
|
|
|
secureConnection: config.gmail.smtp.secure,
|
2013-12-09 13:21:52 -05:00
|
|
|
port: config.gmail.smtp.port,
|
|
|
|
host: config.gmail.smtp.host,
|
|
|
|
auth: auth,
|
2014-02-03 16:07:39 -05:00
|
|
|
tls: {
|
|
|
|
ca: [certificate]
|
|
|
|
},
|
|
|
|
onError: console.error
|
2013-12-09 13:21:52 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
imapClient = new ImapClient(imapOptions);
|
2014-02-03 16:07:39 -05:00
|
|
|
pgpMailer = new PgpMailer(smtpOptions);
|
2013-12-09 13:21:52 -05:00
|
|
|
|
|
|
|
imapClient.onError = function(err) {
|
2014-01-27 15:27:00 -05:00
|
|
|
console.log('IMAP error.', err);
|
|
|
|
console.log('IMAP reconnecting...');
|
2013-12-09 13:21:52 -05:00
|
|
|
// re-init client modules on error
|
2014-02-05 09:47:27 -05:00
|
|
|
self.onConnect(function(err) {
|
|
|
|
if (!self._initialized) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
console.error('IMAP reconnect failed!', err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('IMAP reconnect attempt complete.');
|
|
|
|
});
|
2013-12-09 13:21:52 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
// connect to clients
|
|
|
|
self._emailDao.onConnect({
|
|
|
|
imapClient: imapClient,
|
2014-02-03 16:07:39 -05:00
|
|
|
pgpMailer: pgpMailer
|
2013-12-09 13:21:52 -05:00
|
|
|
}, 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;
|
|
|
|
};
|
|
|
|
|
2013-11-08 18:30:45 -05:00
|
|
|
self.checkForUpdate = function() {
|
2013-11-08 15:35:30 -05:00
|
|
|
if (!chrome || !chrome.runtime || !chrome.runtime.onUpdateAvailable) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-11-08 18:30:45 -05:00
|
|
|
// check for update and restart
|
2013-11-08 13:08:34 -05:00
|
|
|
chrome.runtime.onUpdateAvailable.addListener(function(details) {
|
2013-11-08 18:30:45 -05:00
|
|
|
console.log("Updating to version " + details.version);
|
|
|
|
chrome.runtime.reload();
|
2013-11-08 13:08:34 -05:00
|
|
|
});
|
|
|
|
chrome.runtime.requestUpdateCheck(function(status) {
|
|
|
|
if (status === "update_found") {
|
|
|
|
console.log("Update pending...");
|
|
|
|
} else if (status === "no_update") {
|
|
|
|
console.log("No update found.");
|
|
|
|
} else if (status === "throttled") {
|
|
|
|
console.log("Checking updates too frequently.");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2013-09-26 09:48:32 -04:00
|
|
|
/**
|
2013-12-09 13:21:52 -05:00
|
|
|
* Gracefully try to fetch the user's email address from local storage.
|
|
|
|
* If not yet stored, handle online/offline cases on first use.
|
2013-09-26 09:48:32 -04:00
|
|
|
*/
|
2013-12-09 13:21:52 -05:00
|
|
|
self.getEmailAddress = function(callback) {
|
|
|
|
// try to fetch email address from local storage
|
|
|
|
self.getEmailAddressFromConfig(function(err, cachedEmailAddress) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!cachedEmailAddress && !self.isOnline()) {
|
|
|
|
// first time login... must be online
|
|
|
|
callback({
|
|
|
|
errMsg: 'The app must be online on first use!'
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-27 12:50:13 -05:00
|
|
|
callback(null, cachedEmailAddress);
|
2013-12-09 13:21:52 -05:00
|
|
|
});
|
|
|
|
};
|
2013-08-20 13:48:49 -04:00
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
/**
|
|
|
|
* 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;
|
2013-08-20 13:48:49 -04:00
|
|
|
}
|
2013-12-09 13:21:52 -05:00
|
|
|
|
|
|
|
// no email address is cached yet
|
|
|
|
if (!cachedItems || cachedItems.length < 1) {
|
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null, cachedItems[0]);
|
|
|
|
});
|
2013-09-04 15:01:32 -04:00
|
|
|
};
|
2013-08-20 13:48:49 -04:00
|
|
|
|
2013-09-26 09:48:32 -04:00
|
|
|
/**
|
2013-12-09 13:21:52 -05:00
|
|
|
* 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
|
2013-09-26 09:48:32 -04:00
|
|
|
*/
|
|
|
|
self.queryEmailAddress = function(token, callback) {
|
2013-10-09 10:40:36 -04:00
|
|
|
var itemKey = 'emailaddress';
|
2013-09-26 11:17:47 -04:00
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
self.getEmailAddressFromConfig(function(err, cachedEmailAddress) {
|
2013-10-09 10:40:36 -04:00
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
2013-09-26 11:17:47 -04:00
|
|
|
|
2013-10-09 10:40:36 -04:00
|
|
|
// do roundtrip to google api if no email address is cached yet
|
2013-12-09 13:21:52 -05:00
|
|
|
if (!cachedEmailAddress) {
|
2013-10-09 10:40:36 -04:00
|
|
|
queryGoogleApi();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
callback(null, cachedEmailAddress);
|
2013-09-26 09:48:32 -04:00
|
|
|
});
|
2013-09-26 11:17:47 -04:00
|
|
|
|
|
|
|
function queryGoogleApi() {
|
2013-12-09 13:21:52 -05:00
|
|
|
if (!token) {
|
|
|
|
callback({
|
|
|
|
errMsg: 'Invalid OAuth token!'
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-09-26 11:17:47 -04:00
|
|
|
// 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
|
2013-10-09 10:40:36 -04:00
|
|
|
self._appConfigStore.storeList([info.email], itemKey, function(err) {
|
2013-09-26 11:17:47 -04:00
|
|
|
callback(err, info.email);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
error: function(xhr, textStatus, err) {
|
|
|
|
callback({
|
|
|
|
errMsg: xhr.status + ': ' + xhr.statusText,
|
|
|
|
err: err
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2013-09-26 09:48:32 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2013-12-09 13:21:52 -05:00
|
|
|
* Request an OAuth token from chrome for gmail users
|
2013-09-26 09:48:32 -04:00
|
|
|
*/
|
2013-12-09 13:21:52 -05:00
|
|
|
self.fetchOAuthToken = function(callback) {
|
|
|
|
// get OAuth Token from chrome
|
|
|
|
chrome.identity.getAuthToken({
|
|
|
|
'interactive': true
|
|
|
|
},
|
|
|
|
function(token) {
|
|
|
|
if ((chrome && chrome.runtime && chrome.runtime.lastError) || !token) {
|
|
|
|
callback({
|
|
|
|
errMsg: 'Error fetching an OAuth token for the user!',
|
|
|
|
err: chrome.runtime.lastError
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2013-11-14 11:41:31 -05:00
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
// get email address for the token
|
|
|
|
self.queryEmailAddress(token, function(err, emailAddress) {
|
|
|
|
if (err || !emailAddress) {
|
|
|
|
callback({
|
|
|
|
errMsg: 'Error looking up email address on login!',
|
|
|
|
err: err
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// init the email dao
|
|
|
|
callback(null, {
|
|
|
|
emailAddress: emailAddress,
|
|
|
|
token: token
|
|
|
|
});
|
2013-11-14 11:41:31 -05:00
|
|
|
});
|
2013-08-20 13:48:49 -04:00
|
|
|
}
|
2013-12-09 13:21:52 -05:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
self.buildModules = function() {
|
|
|
|
var lawnchairDao, restDao, pubkeyDao, invitationDao,
|
|
|
|
emailDao, keychain, pgp, userStorage;
|
|
|
|
|
|
|
|
// init objects and inject dependencies
|
|
|
|
restDao = new RestDAO();
|
|
|
|
pubkeyDao = new PublicKeyDAO(restDao);
|
|
|
|
invitationDao = new InvitationDAO(restDao);
|
|
|
|
lawnchairDao = new LawnchairDAO();
|
|
|
|
userStorage = new DeviceStorageDAO(lawnchairDao);
|
|
|
|
|
|
|
|
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(options, callback) {
|
|
|
|
self.buildModules();
|
|
|
|
|
|
|
|
// init email dao
|
|
|
|
var account = {
|
|
|
|
emailAddress: options.emailAddress,
|
|
|
|
asymKeySize: config.asymKeySize
|
2013-08-20 13:48:49 -04:00
|
|
|
};
|
2013-11-14 11:41:31 -05:00
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
self._emailDao.init({
|
|
|
|
account: account
|
|
|
|
}, function(err, keypair) {
|
2013-12-10 17:05:17 -05:00
|
|
|
if (err) {
|
2013-12-09 13:21:52 -05:00
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
2013-11-14 11:41:31 -05:00
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
// connect tcp clients on first startup
|
|
|
|
self.onConnect(function(err) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
2013-11-14 11:41:31 -05:00
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
// init outbox
|
2013-12-04 11:13:45 -05:00
|
|
|
self._outboxBo.init();
|
|
|
|
|
2013-12-09 13:21:52 -05:00
|
|
|
callback(null, keypair);
|
|
|
|
});
|
|
|
|
});
|
2013-08-20 13:48:49 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
return self;
|
2013-06-10 11:57:33 -04:00
|
|
|
});
|