mail/src/js/app-controller.js

402 lines
13 KiB
JavaScript
Raw Normal View History

/**
* The main application controller
*/
2013-08-20 09:19:13 -04:00
define(function(require) {
'use strict';
2014-02-28 14:14:32 -05:00
var ImapClient = require('imap-client'),
2014-02-25 13:18:37 -05:00
mailreader = require('mailreader'),
PgpMailer = require('pgpmailer'),
EmailDAO = require('js/dao/email-dao'),
EmailSync = require('js/dao/email-sync'),
RestDAO = require('js/dao/rest-dao'),
PublicKeyDAO = require('js/dao/publickey-dao'),
LawnchairDAO = require('js/dao/lawnchair-dao'),
KeychainDAO = require('js/dao/keychain-dao'),
2013-09-26 07:26:57 -04:00
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
InvitationDAO = require('js/dao/invitation-dao'),
OutboxBO = require('js/bo/outbox'),
2013-10-11 21:19:01 -04:00
PGP = require('js/crypto/pgp'),
2014-02-25 11:29:12 -05:00
PgpBuilder = require('pgpbuilder'),
2014-03-11 12:49:47 -04:00
UpdateHandler = require('js/util/update/update-handler'),
config = require('js/app-config').config;
var self = {};
/**
* Start the application
*/
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
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();
}
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, pgpMailer;
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 = {
secureConnection: config.gmail.smtp.secure,
port: config.gmail.smtp.port,
host: config.gmail.smtp.host,
auth: auth,
tls: {
ca: [certificate]
},
onError: console.error
};
2014-02-25 13:18:37 -05:00
imapClient = new ImapClient(imapOptions, mailreader);
2014-02-25 11:29:12 -05:00
pgpMailer = new PgpMailer(smtpOptions, self._pgpbuilder);
imapClient.onError = function(err) {
console.log('IMAP error.', err);
console.log('IMAP reconnecting...');
// re-init client modules on error
self.onConnect(function(err) {
if (err) {
console.error('IMAP reconnect failed!', err);
return;
}
console.log('IMAP reconnect attempt complete.');
});
};
// connect to clients
self._emailDao.onConnect({
imapClient: imapClient,
pgpMailer: pgpMailer
}, callback);
}
};
self.getCertficate = function(localCallback) {
if (self.certificate) {
localCallback(null, self.certificate);
return;
}
// fetch pinned local ssl certificate
2014-02-28 14:14:32 -05:00
var ca = new RestDAO({
baseUri: '/ca'
});
ca.get({
uri: '/Google_Internet_Authority_G2.pem',
type: 'text'
}, function(err, cert) {
if (err || !cert) {
localCallback({
errMsg: 'Could not fetch pinned certificate!'
});
2014-02-28 14:14:32 -05:00
return;
}
2014-02-28 14:14:32 -05:00
self.certificate = cert;
localCallback(null, self.certificate);
return;
});
};
self.isOnline = function() {
return navigator.onLine;
};
2013-11-08 18:30:45 -05:00
self.checkForUpdate = function() {
if (!chrome || !chrome.runtime || !chrome.runtime.onUpdateAvailable) {
return;
}
2013-11-08 18:30:45 -05:00
// check for update and restart
chrome.runtime.onUpdateAvailable.addListener(function(details) {
2013-11-08 18:30:45 -05:00
console.log("Updating to version " + details.version);
chrome.runtime.reload();
});
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
/**
* 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
*/
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);
});
};
/**
* 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]);
});
};
2013-09-26 09:48:32 -04: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) {
var itemKey = 'emailaddress';
2013-09-26 11:17:47 -04:00
self.getEmailAddressFromConfig(function(err, cachedEmailAddress) {
if (err) {
callback(err);
return;
}
2013-09-26 11:17:47 -04:00
// do roundtrip to google api if no email address is cached yet
if (!cachedEmailAddress) {
queryGoogleApi();
return;
}
callback(null, cachedEmailAddress);
2013-09-26 09:48:32 -04:00
});
2013-09-26 11:17:47 -04:00
function queryGoogleApi() {
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
2014-02-28 14:14:32 -05:00
var googleEndpoint = new RestDAO({
baseUri: 'https://www.googleapis.com'
});
2013-09-26 11:17:47 -04:00
2014-02-28 14:14:32 -05:00
googleEndpoint.get({
uri: '/oauth2/v1/tokeninfo?access_token=' + token
}, function(err, info) {
if (err || !info || !info.email) {
2013-09-26 11:17:47 -04:00
callback({
2014-02-28 14:14:32 -05:00
errMsg: 'Error looking up email address on google api!'
2013-09-26 11:17:47 -04:00
});
2014-02-28 14:14:32 -05:00
return;
2013-09-26 11:17:47 -04:00
}
2014-02-28 14:14:32 -05:00
// cache the email address on the device
self._appConfigStore.storeList([info.email], itemKey, function(err) {
callback(err, info.email);
});
2013-09-26 11:17:47 -04:00
});
}
2013-09-26 09:48:32 -04:00
};
/**
* Request an OAuth token from chrome for gmail users
2013-09-26 09:48:32 -04:00
*/
self.fetchOAuthToken = function(callback) {
// get OAuth Token from chrome
chrome.identity.getAuthToken({
2014-02-28 14:14:32 -05:00
'interactive': true
}, onToken);
function onToken(token) {
if ((chrome && chrome.runtime && chrome.runtime.lastError) || !token) {
callback({
errMsg: 'Error fetching an OAuth token for the user!'
});
return;
}
// get email address for the token
self.queryEmailAddress(token, function(err, emailAddress) {
if (err || !emailAddress) {
callback({
2014-02-28 14:14:32 -05:00
errMsg: 'Error looking up email address on login!',
err: err
});
return;
}
2013-11-14 11:41:31 -05:00
2014-02-28 14:14:32 -05:00
// init the email dao
callback(null, {
emailAddress: emailAddress,
token: token
2013-11-14 11:41:31 -05:00
});
2014-02-28 14:14:32 -05:00
});
}
};
self.buildModules = function() {
var lawnchairDao, restDao, pubkeyDao, emailDao, emailSync, keychain, pgp, userStorage, pgpbuilder;
2014-03-11 12:49:47 -04:00
// start the mailreader's worker thread
2014-03-03 15:02:14 -05:00
mailreader.startWorker(config.workerPath + '/../lib/mailreader-parser-worker.js');
// init objects and inject dependencies
restDao = new RestDAO();
pubkeyDao = new PublicKeyDAO(restDao);
lawnchairDao = new LawnchairDAO();
2014-03-11 13:15:33 -04:00
self._userStorage = userStorage = new DeviceStorageDAO(lawnchairDao);
2014-03-11 12:49:47 -04:00
self._invitationDao = new InvitationDAO(restDao);
self._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao);
self._crypto = pgp = new PGP();
2014-02-25 14:06:17 -05:00
self._pgpbuilder = pgpbuilder = new PgpBuilder();
emailSync = new EmailSync(keychain, userStorage);
self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader, emailSync);
2014-03-11 13:15:33 -04:00
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage);
self._updateHandler = new UpdateHandler(self._appConfigStore, userStorage);
};
/**
* Instanciate the mail email data access object and its dependencies. Login to imap on init.
*/
self.init = function(options, callback) {
self.buildModules();
2014-03-11 12:49:47 -04:00
// init user's local database
self._userStorage.init(options.emailAddress, function(err) {
if (err) {
callback(err);
return;
}
2013-11-14 11:41:31 -05:00
2014-03-11 12:49:47 -04:00
// Migrate the databases if necessary
self._updateHandler.update(onUpdate);
});
function onUpdate(err) {
if (err) {
2014-03-11 12:49:47 -04:00
callback({
errMsg: 'Update failed, please reinstall the app.',
err: err
});
return;
}
2013-11-14 11:41:31 -05:00
2014-03-11 12:49:47 -04:00
// account information for the email dao
var account = {
emailAddress: options.emailAddress,
asymKeySize: config.asymKeySize
};
// init email dao
self._emailDao.init({
account: account
}, function(err, keypair) {
if (err) {
callback(err);
return;
}
2013-11-14 11:41:31 -05:00
callback(null, keypair);
});
2014-03-11 12:49:47 -04:00
}
};
return self;
2013-06-10 11:57:33 -04:00
});