From 628beb02e8909f28a1e23a40840b54d1b95d99b5 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 17 Nov 2014 18:58:03 +0100 Subject: [PATCH 01/40] WIP: start new service architecture --- src/js/app-controller.js | 15 ++++++++++++- src/js/app.js | 3 +-- src/js/service/account.js | 41 ++++++++++++++++++++++++++++++++++++ src/js/service/index.js | 7 ++++++ src/js/service/newsletter.js | 2 +- 5 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 src/js/service/account.js create mode 100644 src/js/service/index.js diff --git a/src/js/app-controller.js b/src/js/app-controller.js index 0c6059c..f358ffc 100644 --- a/src/js/app-controller.js +++ b/src/js/app-controller.js @@ -41,7 +41,7 @@ ctrl.start = function(options, callback) { } ctrl.started = true; - ctrl.onError = options.onError; + ctrl.onError = options.onError; // TODO: replace by errorService // are we running in a cordova app or in a browser environment? if (window.cordova) { @@ -57,16 +57,21 @@ ctrl.start = function(options, callback) { function onDeviceReady() { axe.debug('Starting app.'); + // TODO: will be replaced by angular dependency management ctrl.buildModules(); + // TODO: move self-contained connection management in emailDao // Handle offline and online gracefully window.addEventListener('online', ctrl.onConnect.bind(ctrl, ctrl.onError)); window.addEventListener('offline', ctrl.onDisconnect.bind(ctrl)); + // TODO: move appConfigService shared singleton ctrl._appConfigStore.init('app-config', callback); } }; +// TODO: will be replaced by angular dependency management + /** * Initialize the dependency tree. */ @@ -121,6 +126,8 @@ ctrl.checkForUpdate = function() { ctrl._updateHandler.checkForUpdate(ctrl.onError); }; +// TODO: move to AccountService + /** * Fire up the database, retrieve the available keys for the user and initialize the email data access object */ @@ -207,6 +214,8 @@ ctrl.isOnline = function() { return navigator.onLine; }; +// TODO: move to AccountService + /** * Event handler that is called when the user agent goes offline. */ @@ -214,6 +223,8 @@ ctrl.onDisconnect = function() { ctrl._emailDao.onDisconnect(); }; +// TODO: move to AccountService + /** * Log the current user out by clear the app config store and deleting instances of imap-client and pgp-mailer. */ @@ -243,6 +254,8 @@ ctrl.logout = function() { }); }; +// TODO: move onConnect to emailDao + /** * Event that is called when the user agent goes online. This create new instances of the imap-client and pgp-mailer and connects to the mail server. */ diff --git a/src/js/app.js b/src/js/app.js index 7480f02..0b52818 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -37,8 +37,7 @@ var DialogCtrl = require('./controller/dialog'), errorUtil = require('./util/error'), backButtonUtil = require('./util/backbutton-handler'); require('./directive/common'), -require('./service/newsletter'), -require('./service/mail-config'); +require('./service'); // init main angular module including dependencies var app = angular.module('mail', [ diff --git a/src/js/service/account.js b/src/js/service/account.js new file mode 100644 index 0000000..fcadb36 --- /dev/null +++ b/src/js/service/account.js @@ -0,0 +1,41 @@ +'use strict'; + +var ngModule = angular.module('woServices'); +ngModule.service('account', Account); + +var EmailDAO = require('../dao/email-dao'); + +function Account() { + this._emailDAOs = []; +} + +/** + * Lists all of the current accounts connected to the app + * @return {Array} The account objects containing folder and message objects + */ +Account.prototype.all = function() { + return this._emailDAOs.map(function(emailDao) { + return emailDao._account; + }); +}; + +/** + * Login to an existing email account. This creates a new email data access object instance for that account and logs in via IMAP. + * @param {String} options.emailAddress The account's email address + */ +Account.prototype.login = function(options) { + var emailDao = new EmailDAO(); + this._emailDAOs.push(emailDao); +}; + +/** + * Create a new whiteout account. This creates a new email data access object instance for that account and logs in via IMAP. + * @param {String} options.emailAddress The account's email address + */ +Account.prototype.create = function(options) {}; + +/** + * Logout of an email account. This creates a new email data access object instance for that account and logs in via IMAP. + * @param {String} options.emailAddress The account's email address + */ +Account.prototype.logout = function(options) {}; \ No newline at end of file diff --git a/src/js/service/index.js b/src/js/service/index.js new file mode 100644 index 0000000..1dea33f --- /dev/null +++ b/src/js/service/index.js @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('woServices', []); + +require('./newsletter'), +require('./mail-config'), +require('./account'); \ No newline at end of file diff --git a/src/js/service/newsletter.js b/src/js/service/newsletter.js index a3c2c4c..ff25f74 100644 --- a/src/js/service/newsletter.js +++ b/src/js/service/newsletter.js @@ -1,6 +1,6 @@ 'use strict'; -var ngModule = angular.module('woServices', []); +var ngModule = angular.module('woServices'); ngModule.service('newsletter', Newsletter); function Newsletter($q) { From f7b7e174b0eda806a29c48fae373781023538e9e Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 18 Nov 2014 18:13:34 +0100 Subject: [PATCH 02/40] Rename DAOs to services --- src/js/{dao/admin-dao.js => service/admin.js} | 0 src/js/{dao/devicestorage-dao.js => service/devicestorage.js} | 0 src/js/{dao/email-dao.js => service/email.js} | 0 src/js/{dao/invitation-dao.js => service/invitation.js} | 0 src/js/{dao/keychain-dao.js => service/keychain.js} | 0 src/js/{dao/lawnchair-dao.js => service/lawnchair.js} | 0 src/js/{dao/privatekey-dao.js => service/privatekey.js} | 0 src/js/{dao/publickey-dao.js => service/publickey.js} | 0 src/js/{dao/rest-dao.js => service/rest.js} | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename src/js/{dao/admin-dao.js => service/admin.js} (100%) rename src/js/{dao/devicestorage-dao.js => service/devicestorage.js} (100%) rename src/js/{dao/email-dao.js => service/email.js} (100%) rename src/js/{dao/invitation-dao.js => service/invitation.js} (100%) rename src/js/{dao/keychain-dao.js => service/keychain.js} (100%) rename src/js/{dao/lawnchair-dao.js => service/lawnchair.js} (100%) rename src/js/{dao/privatekey-dao.js => service/privatekey.js} (100%) rename src/js/{dao/publickey-dao.js => service/publickey.js} (100%) rename src/js/{dao/rest-dao.js => service/rest.js} (100%) diff --git a/src/js/dao/admin-dao.js b/src/js/service/admin.js similarity index 100% rename from src/js/dao/admin-dao.js rename to src/js/service/admin.js diff --git a/src/js/dao/devicestorage-dao.js b/src/js/service/devicestorage.js similarity index 100% rename from src/js/dao/devicestorage-dao.js rename to src/js/service/devicestorage.js diff --git a/src/js/dao/email-dao.js b/src/js/service/email.js similarity index 100% rename from src/js/dao/email-dao.js rename to src/js/service/email.js diff --git a/src/js/dao/invitation-dao.js b/src/js/service/invitation.js similarity index 100% rename from src/js/dao/invitation-dao.js rename to src/js/service/invitation.js diff --git a/src/js/dao/keychain-dao.js b/src/js/service/keychain.js similarity index 100% rename from src/js/dao/keychain-dao.js rename to src/js/service/keychain.js diff --git a/src/js/dao/lawnchair-dao.js b/src/js/service/lawnchair.js similarity index 100% rename from src/js/dao/lawnchair-dao.js rename to src/js/service/lawnchair.js diff --git a/src/js/dao/privatekey-dao.js b/src/js/service/privatekey.js similarity index 100% rename from src/js/dao/privatekey-dao.js rename to src/js/service/privatekey.js diff --git a/src/js/dao/publickey-dao.js b/src/js/service/publickey.js similarity index 100% rename from src/js/dao/publickey-dao.js rename to src/js/service/publickey.js diff --git a/src/js/dao/rest-dao.js b/src/js/service/rest.js similarity index 100% rename from src/js/dao/rest-dao.js rename to src/js/service/rest.js From c8d56b4bd1f696eaa0ccad23918dc00bceed1088 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 18 Nov 2014 18:44:00 +0100 Subject: [PATCH 03/40] Expose previous DAOs as angular services and as common.js modules --- src/js/service/account.js | 14 +++--- src/js/service/admin.js | 16 ++++--- src/js/service/devicestorage.js | 27 +++++------ src/js/service/email.js | 82 +++++++++++++++++---------------- src/js/service/invitation.js | 26 ++++++----- src/js/service/keychain.js | 65 +++++++++++++------------- src/js/service/lawnchair.js | 19 +++++--- src/js/service/privatekey.js | 28 +++++------ src/js/service/publickey.js | 22 +++++---- src/js/service/rest.js | 14 ++++-- 10 files changed, 167 insertions(+), 146 deletions(-) diff --git a/src/js/service/account.js b/src/js/service/account.js index fcadb36..9fbeac0 100644 --- a/src/js/service/account.js +++ b/src/js/service/account.js @@ -3,10 +3,10 @@ var ngModule = angular.module('woServices'); ngModule.service('account', Account); -var EmailDAO = require('../dao/email-dao'); +var Email = require('./email'); function Account() { - this._emailDAOs = []; + this._emailDAOs = []; } /** @@ -14,9 +14,9 @@ function Account() { * @return {Array} The account objects containing folder and message objects */ Account.prototype.all = function() { - return this._emailDAOs.map(function(emailDao) { - return emailDao._account; - }); + return this._emailDAOs.map(function(emailDao) { + return emailDao._account; + }); }; /** @@ -24,8 +24,8 @@ Account.prototype.all = function() { * @param {String} options.emailAddress The account's email address */ Account.prototype.login = function(options) { - var emailDao = new EmailDAO(); - this._emailDAOs.push(emailDao); + var emailDao = new Email(); + this._emailDAOs.push(emailDao); }; /** diff --git a/src/js/service/admin.js b/src/js/service/admin.js index 9209c3b..8c3fc13 100644 --- a/src/js/service/admin.js +++ b/src/js/service/admin.js @@ -1,8 +1,12 @@ 'use strict'; -var AdminDAO = function(restDao) { +var ngModule = angular.module('woServices'); +ngModule.service('admin', Admin); +module.exports = Admin; + +function Admin(restDao) { this._restDao = restDao; -}; +} /** * Create a new email account. @@ -11,7 +15,7 @@ var AdminDAO = function(restDao) { * @param {String} options.phone The user's mobile phone number (required for verification and password reset). * @param {Function} callback(error) */ -AdminDAO.prototype.createUser = function(options, callback) { +Admin.prototype.createUser = function(options, callback) { var uri; if (!options.emailAddress || !options.password || !options.phone) { @@ -39,7 +43,7 @@ AdminDAO.prototype.createUser = function(options, callback) { * @param {String} options.token The validation token. * @param {Function} callback(error) */ -AdminDAO.prototype.validateUser = function(options, callback) { +Admin.prototype.validateUser = function(options, callback) { var uri; if (!options.emailAddress || !options.token) { @@ -56,6 +60,4 @@ AdminDAO.prototype.validateUser = function(options, callback) { callback(new Error('Validation failed!')); } }); -}; - -module.exports = AdminDAO; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/service/devicestorage.js b/src/js/service/devicestorage.js index 8acb14d..4c9f90e 100644 --- a/src/js/service/devicestorage.js +++ b/src/js/service/devicestorage.js @@ -1,14 +1,17 @@ +'use strict'; + +var ngModule = angular.module('woServices'); +ngModule.service('deviceStorage', DeviceStorage); +module.exports = DeviceStorage; + /** * High level storage api that handles all persistence on the device. */ - -'use strict'; - -var DeviceStorageDAO = function(localDbDao) { +function DeviceStorage(localDbDao) { this._localDbDao = localDbDao; -}; +} -DeviceStorageDAO.prototype.init = function(emailAddress, callback) { +DeviceStorage.prototype.init = function(emailAddress, callback) { this._localDbDao.init(emailAddress, callback); }; @@ -17,7 +20,7 @@ DeviceStorageDAO.prototype.init = function(emailAddress, callback) { * @param list [Array] The list of items to be persisted * @param type [String] The type of item to be persisted e.g. 'email' */ -DeviceStorageDAO.prototype.storeList = function(list, type, callback) { +DeviceStorage.prototype.storeList = function(list, type, callback) { var key, items = []; // nothing to store @@ -49,7 +52,7 @@ DeviceStorageDAO.prototype.storeList = function(list, type, callback) { /** * Deletes items of a certain type from storage */ -DeviceStorageDAO.prototype.removeList = function(type, callback) { +DeviceStorage.prototype.removeList = function(type, callback) { this._localDbDao.removeList(type, callback); }; @@ -59,7 +62,7 @@ DeviceStorageDAO.prototype.removeList = function(type, callback) { * @param offset [Number] The offset of items to fetch (0 is the last stored item) * @param num [Number] The number of items to fetch (null means fetch all) */ -DeviceStorageDAO.prototype.listItems = function(type, offset, num, callback) { +DeviceStorage.prototype.listItems = function(type, offset, num, callback) { // fetch all items of a certain type from the data-store this._localDbDao.list(type, offset, num, callback); }; @@ -67,7 +70,7 @@ DeviceStorageDAO.prototype.listItems = function(type, offset, num, callback) { /** * Clear the whole device data-store */ -DeviceStorageDAO.prototype.clear = function(callback) { +DeviceStorage.prototype.clear = function(callback) { this._localDbDao.clear(callback); }; @@ -88,6 +91,4 @@ function createKey(i, type) { } return key; -} - -module.exports = DeviceStorageDAO; \ No newline at end of file +} \ No newline at end of file diff --git a/src/js/service/email.js b/src/js/service/email.js index 6ba54a2..85b07d8 100644 --- a/src/js/service/email.js +++ b/src/js/service/email.js @@ -1,5 +1,9 @@ 'use strict'; +var ngModule = angular.module('woServices'); +ngModule.service('email', Email); +module.exports = Email; + var config = require('../app-config').config, str = require('../app-config').string; @@ -32,7 +36,7 @@ var MSG_PART_TYPE_HTML = 'html'; // // -// Email Dao +// Email Service // // @@ -46,13 +50,13 @@ var MSG_PART_TYPE_HTML = 'html'; * @param {Object} pgpbuilder Generates and encrypts MIME and SMTP messages * @param {Object} mailreader Parses MIME messages received from IMAP */ -var EmailDAO = function(keychain, pgp, devicestorage, pgpbuilder, mailreader) { +function Email(keychain, pgp, devicestorage, pgpbuilder, mailreader) { this._keychain = keychain; this._pgp = pgp; this._devicestorage = devicestorage; this._pgpbuilder = pgpbuilder; this._mailreader = mailreader; -}; +} // @@ -71,7 +75,7 @@ var EmailDAO = function(keychain, pgp, devicestorage, pgpbuilder, mailreader) { * @param {String} options.account.realname The user's id * @param {Function} callback(error, keypair) Invoked with the keypair or error information when the email dao is initialized */ -EmailDAO.prototype.init = function(options, callback) { +Email.prototype.init = function(options, callback) { this._account = options.account; this._account.busy = 0; // > 0 triggers the spinner this._account.online = false; @@ -86,7 +90,7 @@ EmailDAO.prototype.init = function(options, callback) { * @param {String} options.passphrase The passphrase to decrypt the private key * @param {Function} callback(error) Invoked when the the keychain is unlocked or when an error occurred buring unlocking */ -EmailDAO.prototype.unlock = function(options, callback) { +Email.prototype.unlock = function(options, callback) { var self = this; if (options.keypair) { @@ -202,7 +206,7 @@ EmailDAO.prototype.unlock = function(options, callback) { * @param {Object} options.folder The folder to be opened * @param {Function} callback(error) Invoked when the folder has been opened */ -EmailDAO.prototype.openFolder = function(options, callback) { +Email.prototype.openFolder = function(options, callback) { var self = this, err; @@ -229,7 +233,7 @@ EmailDAO.prototype.openFolder = function(options, callback) { * @param {Object} options.folder The folder to synchronize * @param {Function} callback [description] */ -EmailDAO.prototype.refreshFolder = function(options, callback) { +Email.prototype.refreshFolder = function(options, callback) { var self = this, folder = options.folder; @@ -289,7 +293,7 @@ EmailDAO.prototype.refreshFolder = function(options, callback) { * @param {Object} options.folder The folder for which to fetch the message * @param {Function} callback(error) Invoked when the message is persisted and added to folder.messages */ -EmailDAO.prototype.fetchMessages = function(options, callback) { +Email.prototype.fetchMessages = function(options, callback) { var self = this, folder = options.folder; @@ -440,7 +444,7 @@ EmailDAO.prototype.fetchMessages = function(options, callback) { * @param {Boolean} options.localOnly Indicated if the message should not be removed from IMAP * @param {Function} callback(error) Invoked when the message was delete, or an error occurred */ -EmailDAO.prototype.deleteMessage = function(options, callback) { +Email.prototype.deleteMessage = function(options, callback) { var self = this, folder = options.folder, message = options.message; @@ -508,7 +512,7 @@ EmailDAO.prototype.deleteMessage = function(options, callback) { * @param {[type]} options [description] * @param {Function} callback [description] */ -EmailDAO.prototype.setFlags = function(options, callback) { +Email.prototype.setFlags = function(options, callback) { var self = this, folder = options.folder, message = options.message; @@ -600,7 +604,7 @@ EmailDAO.prototype.setFlags = function(options, callback) { * @param {Object} options.message The message that should be moved * @param {Function} callback(error) Invoked when the message was moved, or an error occurred */ -EmailDAO.prototype.moveMessage = function(options, callback) { +Email.prototype.moveMessage = function(options, callback) { var self = this, folder = options.folder, destination = options.destination, @@ -652,7 +656,7 @@ EmailDAO.prototype.moveMessage = function(options, callback) { * @param {Object} options.folder The IMAP folder * @param {Function} callback(error, message) Invoked when the message is streamed, or provides information if an error occurred */ -EmailDAO.prototype.getBody = function(options, callback) { +Email.prototype.getBody = function(options, callback) { var self = this, message = options.message, folder = options.folder; @@ -837,7 +841,7 @@ EmailDAO.prototype.getBody = function(options, callback) { } }; -EmailDAO.prototype._checkSignatures = function(message, callback) { +Email.prototype._checkSignatures = function(message, callback) { var self = this; self._keychain.getReceiverPublicKey(message.from[0].address, function(err, senderPublicKey) { @@ -866,7 +870,7 @@ EmailDAO.prototype._checkSignatures = function(message, callback) { * @param {Object} options.attachment The attachment body part to fetch and parse from IMAP * @param {Function} callback(error, attachment) Invoked when the attachment body part was retrieved and parsed, or an error occurred */ -EmailDAO.prototype.getAttachment = function(options, callback) { +Email.prototype.getAttachment = function(options, callback) { var self = this, attachment = options.attachment; @@ -896,7 +900,7 @@ EmailDAO.prototype.getAttachment = function(options, callback) { * @param {Object} options.message The message * @param {Function} callback(error, message) */ -EmailDAO.prototype.decryptBody = function(options, callback) { +Email.prototype.decryptBody = function(options, callback) { var self = this, message = options.message; @@ -1016,7 +1020,7 @@ EmailDAO.prototype.decryptBody = function(options, callback) { * @param {Object} options.email The message to be sent * @param {Function} callback(error) Invoked when the message was sent, or an error occurred */ -EmailDAO.prototype.sendEncrypted = function(options, callback) { +Email.prototype.sendEncrypted = function(options, callback) { var self = this; if (!self._account.online) { @@ -1056,7 +1060,7 @@ EmailDAO.prototype.sendEncrypted = function(options, callback) { * @param {Object} options.email The message to be sent * @param {Function} callback(error) Invoked when the message was sent, or an error occurred */ -EmailDAO.prototype.sendPlaintext = function(options, callback) { +Email.prototype.sendPlaintext = function(options, callback) { var self = this; if (!self._account.online) { @@ -1097,7 +1101,7 @@ EmailDAO.prototype.sendPlaintext = function(options, callback) { * @param {Object} options.email The message to be encrypted * @param {Function} callback(error, message) Invoked when the message was encrypted, or an error occurred */ -EmailDAO.prototype.encrypt = function(options, callback) { +Email.prototype.encrypt = function(options, callback) { var self = this; self.busy(); @@ -1124,7 +1128,7 @@ EmailDAO.prototype.encrypt = function(options, callback) { * @param {Object} options.pgpMailer The SMTP client used to send messages * @param {Function} callback [description] */ -EmailDAO.prototype.onConnect = function(options, callback) { +Email.prototype.onConnect = function(options, callback) { var self = this; self._account.loggingIn = true; @@ -1212,7 +1216,7 @@ EmailDAO.prototype.onConnect = function(options, callback) { * This handler should be invoked when navigator.onLine === false. * It will discard the imap client and pgp mailer */ -EmailDAO.prototype.onDisconnect = function(callback) { +Email.prototype.onDisconnect = function(callback) { var self = this; // logout of imap-client @@ -1239,7 +1243,7 @@ EmailDAO.prototype.onDisconnect = function(callback) { * @param {String} options.path The mailbox for which updates are available * @param {Array} options.list Array containing update information. Number (uid) or mail with Object (uid and flags), respectively */ -EmailDAO.prototype._onSyncUpdate = function(options) { +Email.prototype._onSyncUpdate = function(options) { var self = this; var folder = _.findWhere(self._account.folders, { @@ -1319,7 +1323,7 @@ EmailDAO.prototype._onSyncUpdate = function(options) { * * @param {Function} callback Invoked when the folders are up to date */ -EmailDAO.prototype._initFoldersFromDisk = function(callback) { +Email.prototype._initFoldersFromDisk = function(callback) { var self = this; self.busy(); // start the spinner @@ -1347,7 +1351,7 @@ EmailDAO.prototype._initFoldersFromDisk = function(callback) { * * @param {Function} callback Invoked when the folders are up to date */ -EmailDAO.prototype._initFoldersFromImap = function(callback) { +Email.prototype._initFoldersFromImap = function(callback) { var self = this; self.busy(); // start the spinner @@ -1498,7 +1502,7 @@ EmailDAO.prototype._initFoldersFromImap = function(callback) { * * @param {Function} callback Invoked when the folders are filled with messages */ -EmailDAO.prototype._initMessagesFromDisk = function(callback) { +Email.prototype._initMessagesFromDisk = function(callback) { var self = this; if (!self._account.folders || self._account.folders.length === 0) { @@ -1526,11 +1530,11 @@ EmailDAO.prototype._initMessagesFromDisk = function(callback) { }); }; -EmailDAO.prototype.busy = function() { +Email.prototype.busy = function() { this._account.busy++; }; -EmailDAO.prototype.done = function() { +Email.prototype.done = function() { if (this._account.busy > 0) { this._account.busy--; } @@ -1552,7 +1556,7 @@ EmailDAO.prototype.done = function() { * @param {Number} options.unread Un-/Read flag * @param {Number} options.answered Un-/Answered flag */ -EmailDAO.prototype._imapMark = function(options, callback) { +Email.prototype._imapMark = function(options, callback) { if (!this._account.online) { callback({ errMsg: 'Client is currently offline!', @@ -1573,7 +1577,7 @@ EmailDAO.prototype._imapMark = function(options, callback) { * @param {Number} options.uid The uid of the message * @param {Function} callback(error) Callback with an error object in case something went wrong. */ -EmailDAO.prototype._imapDeleteMessage = function(options, callback) { +Email.prototype._imapDeleteMessage = function(options, callback) { if (!this._account.online) { callback({ errMsg: 'Client is currently offline!', @@ -1611,7 +1615,7 @@ EmailDAO.prototype._imapDeleteMessage = function(options, callback) { * @param {String} options.uid the message's uid * @param {Function} callback (error) The callback when the message is moved */ -EmailDAO.prototype._imapMoveMessage = function(options, callback) { +Email.prototype._imapMoveMessage = function(options, callback) { this._imapClient.moveMessage({ path: options.folder.path, destination: options.destination.path, @@ -1628,7 +1632,7 @@ EmailDAO.prototype._imapMoveMessage = function(options, callback) { * @param {Number} options.lastUid The upper bound of the uid range (inclusive) * @param {Function} callback (error, messages) The callback when the imap client is done fetching message metadata */ -EmailDAO.prototype._imapListMessages = function(options, callback) { +Email.prototype._imapListMessages = function(options, callback) { var self = this; if (!this._account.online) { @@ -1650,7 +1654,7 @@ EmailDAO.prototype._imapListMessages = function(options, callback) { * @param {String} options.message The rfc2822 compatible raw ASCII e-mail source * @param {Function} callback (error) The callback when the imap client is done uploading */ -EmailDAO.prototype._imapUploadMessage = function(options, callback) { +Email.prototype._imapUploadMessage = function(options, callback) { this._imapClient.uploadMessage({ path: options.folder.path, message: options.message @@ -1664,7 +1668,7 @@ EmailDAO.prototype._imapUploadMessage = function(options, callback) { * @param {Object} options.bodyParts The message, as retrieved by _imapListMessages * @param {Function} callback (error, message) The callback when the imap client is done streaming message text content */ -EmailDAO.prototype._getBodyParts = function(options, callback) { +Email.prototype._getBodyParts = function(options, callback) { var self = this; if (!self._account.online) { @@ -1702,7 +1706,7 @@ EmailDAO.prototype._getBodyParts = function(options, callback) { * @param {Object} options.uid A specific uid to look up locally in the folder * @param {Function} callback(error, list) Invoked with the results of the query, or further information, if an error occurred */ -EmailDAO.prototype._localListMessages = function(options, callback) { +Email.prototype._localListMessages = function(options, callback) { var dbType = 'email_' + options.folder.path + (options.uid ? '_' + options.uid : ''); this._devicestorage.listItems(dbType, 0, null, callback); }; @@ -1714,7 +1718,7 @@ EmailDAO.prototype._localListMessages = function(options, callback) { * @param {Array} options.messages The messages to store * @param {Function} callback(error, list) Invoked with the results of the query, or further information, if an error occurred */ -EmailDAO.prototype._localStoreMessages = function(options, callback) { +Email.prototype._localStoreMessages = function(options, callback) { var dbType = 'email_' + options.folder.path; this._devicestorage.storeList(options.emails, dbType, callback); }; @@ -1726,7 +1730,7 @@ EmailDAO.prototype._localStoreMessages = function(options, callback) { * @param {Array} options.messages The messages to store * @param {Function} callback(error, list) Invoked with the results of the query, or further information, if an error occurred */ -EmailDAO.prototype._localDeleteMessage = function(options, callback) { +Email.prototype._localDeleteMessage = function(options, callback) { var path = options.folder.path, uid = options.uid, id = options.id; @@ -1756,7 +1760,7 @@ EmailDAO.prototype._localDeleteMessage = function(options, callback) { * @param {String} options.message The rfc2822 compatible raw ASCII e-mail source * @param {Function} callback (error) The callback when the imap client is done uploading */ -EmailDAO.prototype._uploadToSent = function(options, callback) { +Email.prototype._uploadToSent = function(options, callback) { var self = this; self.busy(); @@ -1796,7 +1800,7 @@ EmailDAO.prototype._uploadToSent = function(options, callback) { * @param {String} hostname The hostname to check * @return {Boolean} true if upload can be ignored, otherwise false */ -EmailDAO.prototype.checkIgnoreUploadOnSent = function(hostname) { +Email.prototype.checkIgnoreUploadOnSent = function(hostname) { for (var i = 0; i < config.ignoreUploadOnSentDomains.length; i++) { if (config.ignoreUploadOnSentDomains[i].test(hostname)) { return true; @@ -1876,6 +1880,4 @@ function inlineExternalImages(message) { return prefix + localSource + suffix; }); -} - -module.exports = EmailDAO; \ No newline at end of file +} \ No newline at end of file diff --git a/src/js/service/invitation.js b/src/js/service/invitation.js index 41e16ab..d5a0afc 100644 --- a/src/js/service/invitation.js +++ b/src/js/service/invitation.js @@ -1,20 +1,24 @@ 'use strict'; +var ngModule = angular.module('woServices'); +ngModule.service('invitation', Invitation); +module.exports = Invitation; + /** - * The InvitationDAO is a high level Data Access Object that access the invitation service REST endpoint. + * The Invitation is a high level Data Access Object that access the invitation service REST endpoint. * @param {Object} restDao The REST Data Access Object abstraction */ -var InvitationDAO = function(restDao) { +function Invitation(restDao) { this._restDao = restDao; -}; +} // // Constants // -InvitationDAO.INVITE_MISSING = 1; -InvitationDAO.INVITE_PENDING = 2; -InvitationDAO.INVITE_SUCCESS = 4; +Invitation.INVITE_MISSING = 1; +Invitation.INVITE_PENDING = 2; +Invitation.INVITE_SUCCESS = 4; // // API @@ -26,7 +30,7 @@ InvitationDAO.INVITE_SUCCESS = 4; * @param {String} options.sender User ID of the sender * @param {Function} callback(error, status) Returns information if the invitation worked (INVITE_SUCCESS), if an invitation is already pendin (INVITE_PENDING), or information if an error occurred. */ -InvitationDAO.prototype.invite = function(options, callback) { +Invitation.prototype.invite = function(options, callback) { if (typeof options !== 'object' || typeof options.recipient !== 'string' || typeof options.recipient !== 'string') { callback({ errMsg: 'erroneous usage of api: incorrect parameters!' @@ -44,10 +48,10 @@ InvitationDAO.prototype.invite = function(options, callback) { } if (status === 201) { - callback(null, InvitationDAO.INVITE_SUCCESS); + callback(null, Invitation.INVITE_SUCCESS); return; } else if (status === 304) { - callback(null, InvitationDAO.INVITE_PENDING); + callback(null, Invitation.INVITE_PENDING); return; } @@ -55,6 +59,4 @@ InvitationDAO.prototype.invite = function(options, callback) { errMsg: 'unexpected invitation state' }); } -}; - -module.exports = InvitationDAO; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/service/keychain.js b/src/js/service/keychain.js index f71040b..e284893 100644 --- a/src/js/service/keychain.js +++ b/src/js/service/keychain.js @@ -1,10 +1,9 @@ -/** - * A high-level Data-Access Api for handling Keypair synchronization - * between the cloud service and the device's local storage - */ - 'use strict'; +var ngModule = angular.module('woServices'); +ngModule.service('keychain', Keychain); +module.exports = Keychain; + var util = require('crypto-lib').util, config = require('../app-config').config; @@ -13,13 +12,17 @@ var DB_PUBLICKEY = 'publickey', DB_DEVICENAME = 'devicename', DB_DEVICE_SECRET = 'devicesecret'; -var KeychainDAO = function(localDbDao, publicKeyDao, privateKeyDao, crypto, pgp) { +/** + * A high-level Data-Access Api for handling Keypair synchronization + * between the cloud service and the device's local storage + */ +function Keychain(localDbDao, publicKeyDao, privateKeyDao, crypto, pgp) { this._localDbDao = localDbDao; this._publicKeyDao = publicKeyDao; this._privateKeyDao = privateKeyDao; this._crypto = crypto; this._pgp = pgp; -}; +} // // Public key functions @@ -30,7 +33,7 @@ var KeychainDAO = function(localDbDao, publicKeyDao, privateKeyDao, crypto, pgp) * @param {String} uuid The uuid to verify the key * @param {Function} callback(error) Callback with an optional error object when the verification is done. If the was an error, the error object contains the information for it. */ -KeychainDAO.prototype.verifyPublicKey = function(uuid, callback) { +Keychain.prototype.verifyPublicKey = function(uuid, callback) { this._publicKeyDao.verify(uuid, callback); }; @@ -40,7 +43,7 @@ KeychainDAO.prototype.verifyPublicKey = function(uuid, callback) { * @param ids [Array] the key ids as [{_id, userId}] * @return [PublicKeyCollection] The requiested public keys */ -KeychainDAO.prototype.getPublicKeys = function(ids, callback) { +Keychain.prototype.getPublicKeys = function(ids, callback) { var self = this, after, already, pubkeys = []; @@ -85,7 +88,7 @@ KeychainDAO.prototype.getPublicKeys = function(ids, callback) { * @param {String} options.overridePermission (optional) Indicates if the update should happen automatically (true) or with the user being queried (false). Defaults to false * @param {Function} callback(error, key) Invoked when the key has been updated or an error occurred */ -KeychainDAO.prototype.refreshKeyForUserId = function(options, callback) { +Keychain.prototype.refreshKeyForUserId = function(options, callback) { var self = this, userId = options.userId, overridePermission = options.overridePermission; @@ -189,7 +192,7 @@ KeychainDAO.prototype.refreshKeyForUserId = function(options, callback) { * Look up a reveiver's public key by user id * @param userId [String] the receiver's email address */ -KeychainDAO.prototype.getReceiverPublicKey = function(userId, callback) { +Keychain.prototype.getReceiverPublicKey = function(userId, callback) { var self = this; // search local keyring for public key @@ -266,7 +269,7 @@ KeychainDAO.prototype.getReceiverPublicKey = function(userId, callback) { * @param {String} deviceName The device name * @param {Function} callback(error) */ -KeychainDAO.prototype.setDeviceName = function(deviceName, callback) { +Keychain.prototype.setDeviceName = function(deviceName, callback) { if (!deviceName) { callback(new Error('Please set a device name!')); return; @@ -280,7 +283,7 @@ KeychainDAO.prototype.setDeviceName = function(deviceName, callback) { * @param {Function} callback(error, deviceName) * @return {String} The device name */ -KeychainDAO.prototype.getDeviceName = function(callback) { +Keychain.prototype.getDeviceName = function(callback) { // check if deviceName is already persisted in storage this._localDbDao.read(DB_DEVICENAME, function(err, deviceName) { if (err) { @@ -301,7 +304,7 @@ KeychainDAO.prototype.getDeviceName = function(callback) { * Geneate a device specific key and secret to authenticate to the private key service. * @param {Function} callback(error, deviceSecret:[base64 encoded string]) */ -KeychainDAO.prototype.getDeviceSecret = function(callback) { +Keychain.prototype.getDeviceSecret = function(callback) { var self = this; // generate random deviceSecret or get from storage @@ -336,7 +339,7 @@ KeychainDAO.prototype.getDeviceSecret = function(callback) { * @param {String} options.userId The user's email address * @param {Function} callback(error) */ -KeychainDAO.prototype.registerDevice = function(options, callback) { +Keychain.prototype.registerDevice = function(options, callback) { var self = this, devName; @@ -435,7 +438,7 @@ KeychainDAO.prototype.registerDevice = function(options, callback) { * @param {Function} callback(error, authSessionKey) * @return {Object} {sessionId:String, sessionKey:[base64 encoded]} */ -KeychainDAO.prototype._authenticateToPrivateKeyServer = function(userId, callback) { +Keychain.prototype._authenticateToPrivateKeyServer = function(userId, callback) { var self = this, sessionId; @@ -552,7 +555,7 @@ KeychainDAO.prototype._authenticateToPrivateKeyServer = function(userId, callbac * @param {String} options.code The randomly generated or self selected code used to derive the key for the encryption of the private PGP key * @param {Function} callback(error) */ -KeychainDAO.prototype.uploadPrivateKey = function(options, callback) { +Keychain.prototype.uploadPrivateKey = function(options, callback) { var self = this, keySize = config.symKeySize, salt; @@ -646,7 +649,7 @@ KeychainDAO.prototype.uploadPrivateKey = function(options, callback) { * @param {String} options.keyId The private PGP key id * @param {Function} callback(error) */ -KeychainDAO.prototype.requestPrivateKeyDownload = function(options, callback) { +Keychain.prototype.requestPrivateKeyDownload = function(options, callback) { this._privateKeyDao.requestDownload(options, callback); }; @@ -656,7 +659,7 @@ KeychainDAO.prototype.requestPrivateKeyDownload = function(options, callback) { * @param {String} options.keyId The private PGP key id * @param {Function} callback(error) */ -KeychainDAO.prototype.hasPrivateKey = function(options, callback) { +Keychain.prototype.hasPrivateKey = function(options, callback) { this._privateKeyDao.hasPrivateKey(options, callback); }; @@ -667,7 +670,7 @@ KeychainDAO.prototype.hasPrivateKey = function(options, callback) { * @param {String} options.recoveryToken The recovery token acquired via email/sms from the key server * @param {Function} callback(error, encryptedPrivateKey) */ -KeychainDAO.prototype.downloadPrivateKey = function(options, callback) { +Keychain.prototype.downloadPrivateKey = function(options, callback) { this._privateKeyDao.download(options, callback); }; @@ -681,7 +684,7 @@ KeychainDAO.prototype.downloadPrivateKey = function(options, callback) { * @param {String} options.iv The iv used to encrypt the private PGP key * @param {Function} callback(error, keyObject) */ -KeychainDAO.prototype.decryptAndStorePrivateKeyLocally = function(options, callback) { +Keychain.prototype.decryptAndStorePrivateKeyLocally = function(options, callback) { var self = this, code = options.code, salt = options.salt, @@ -756,7 +759,7 @@ KeychainDAO.prototype.decryptAndStorePrivateKeyLocally = function(options, callb * If no key pair exists, null is returned. * return [Object] The user's key pair {publicKey, privateKey} */ -KeychainDAO.prototype.getUserKeyPair = function(userId, callback) { +Keychain.prototype.getUserKeyPair = function(userId, callback) { var self = this; // search for user's public key locally @@ -833,7 +836,7 @@ KeychainDAO.prototype.getUserKeyPair = function(userId, callback) { * locally and in the cloud and persist arccordingly * @param [Object] The user's key pair {publicKey, privateKey} */ -KeychainDAO.prototype.putUserKeyPair = function(keypair, callback) { +Keychain.prototype.putUserKeyPair = function(keypair, callback) { var self = this; // validate input @@ -872,7 +875,7 @@ KeychainDAO.prototype.putUserKeyPair = function(keypair, callback) { // Helper functions // -KeychainDAO.prototype.lookupPublicKey = function(id, callback) { +Keychain.prototype.lookupPublicKey = function(id, callback) { var self = this; if (!id) { @@ -917,30 +920,28 @@ KeychainDAO.prototype.lookupPublicKey = function(id, callback) { /** * List all the locally stored public keys */ -KeychainDAO.prototype.listLocalPublicKeys = function(callback) { +Keychain.prototype.listLocalPublicKeys = function(callback) { // search local keyring for public key this._localDbDao.list(DB_PUBLICKEY, 0, null, callback); }; -KeychainDAO.prototype.removeLocalPublicKey = function(id, callback) { +Keychain.prototype.removeLocalPublicKey = function(id, callback) { this._localDbDao.remove(DB_PUBLICKEY + '_' + id, callback); }; -KeychainDAO.prototype.lookupPrivateKey = function(id, callback) { +Keychain.prototype.lookupPrivateKey = function(id, callback) { // lookup in local storage this._localDbDao.read(DB_PRIVATEKEY + '_' + id, callback); }; -KeychainDAO.prototype.saveLocalPublicKey = function(pubkey, callback) { +Keychain.prototype.saveLocalPublicKey = function(pubkey, callback) { // persist public key (email, _id) var pkLookupKey = DB_PUBLICKEY + '_' + pubkey._id; this._localDbDao.persist(pkLookupKey, pubkey, callback); }; -KeychainDAO.prototype.saveLocalPrivateKey = function(privkey, callback) { +Keychain.prototype.saveLocalPrivateKey = function(privkey, callback) { // persist private key (email, _id) var prkLookupKey = DB_PRIVATEKEY + '_' + privkey._id; this._localDbDao.persist(prkLookupKey, privkey, callback); -}; - -module.exports = KeychainDAO; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/service/lawnchair.js b/src/js/service/lawnchair.js index 685c7f2..afdfcd3 100644 --- a/src/js/service/lawnchair.js +++ b/src/js/service/lawnchair.js @@ -1,11 +1,18 @@ +'use strict'; + +var ngModule = angular.module('woServices'); +ngModule.service('lawnchairDAO', LawnchairDAO); +module.exports = LawnchairDAO; + /** * Handles generic caching of JSON objects in a lawnchair adapter */ +function LawnchairDAO() {} -'use strict'; - -var LawnchairDAO = function() {}; - +/** + * Initialize the lawnchair database + * @param {String} dbName The name of the database + */ LawnchairDAO.prototype.init = function(dbName, callback) { if (!dbName) { callback({ @@ -211,6 +218,4 @@ LawnchairDAO.prototype.removeList = function(type, callback) { */ LawnchairDAO.prototype.clear = function(callback) { this._db.nuke(callback); -}; - -module.exports = LawnchairDAO; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/service/privatekey.js b/src/js/service/privatekey.js index 8657790..8677bed 100644 --- a/src/js/service/privatekey.js +++ b/src/js/service/privatekey.js @@ -1,8 +1,12 @@ 'use strict'; -var PrivateKeyDAO = function(restDao) { +var ngModule = angular.module('woServices'); +ngModule.service('privateKey', PrivateKey); +module.exports = PrivateKey; + +function PrivateKey(restDao) { this._restDao = restDao; -}; +} // // Device registration functions @@ -15,7 +19,7 @@ var PrivateKeyDAO = function(restDao) { * @param {Function} callback(error, regSessionKey) * @return {Object} {encryptedRegSessionKey:[base64]} */ -PrivateKeyDAO.prototype.requestDeviceRegistration = function(options, callback) { +PrivateKey.prototype.requestDeviceRegistration = function(options, callback) { var uri; if (!options.userId || !options.deviceName) { @@ -35,7 +39,7 @@ PrivateKeyDAO.prototype.requestDeviceRegistration = function(options, callback) * @param {String} options.iv The iv used for encryption * @param {Function} callback(error) */ -PrivateKeyDAO.prototype.uploadDeviceSecret = function(options, callback) { +PrivateKey.prototype.uploadDeviceSecret = function(options, callback) { var uri; if (!options.userId || !options.deviceName || !options.encryptedDeviceSecret || !options.iv) { @@ -57,7 +61,7 @@ PrivateKeyDAO.prototype.uploadDeviceSecret = function(options, callback) { * @param {Function} callback(error, authSessionKey) * @return {Object} {sessionId, encryptedAuthSessionKey:[base64 encoded], encryptedChallenge:[base64 encoded]} */ -PrivateKeyDAO.prototype.requestAuthSessionKey = function(options, callback) { +PrivateKey.prototype.requestAuthSessionKey = function(options, callback) { var uri; if (!options.userId) { @@ -77,7 +81,7 @@ PrivateKeyDAO.prototype.requestAuthSessionKey = function(options, callback) { * @param {String} options.iv The iv used for encryption * @param {Function} callback(error) */ -PrivateKeyDAO.prototype.verifyAuthentication = function(options, callback) { +PrivateKey.prototype.verifyAuthentication = function(options, callback) { var uri; if (!options.userId || !options.sessionId || !options.encryptedChallenge || !options.encryptedDeviceSecret || !options.iv) { @@ -97,7 +101,7 @@ PrivateKeyDAO.prototype.verifyAuthentication = function(options, callback) { * @param {String} options.sessionId The session id * @param {Function} callback(error) */ -PrivateKeyDAO.prototype.upload = function(options, callback) { +PrivateKey.prototype.upload = function(options, callback) { var uri; if (!options._id || !options.userId || !options.encryptedPrivateKey || !options.sessionId || !options.salt || !options.iv) { @@ -116,7 +120,7 @@ PrivateKeyDAO.prototype.upload = function(options, callback) { * @param {Function} callback(error, found) * @return {Boolean} whether the key was found on the server or not. */ -PrivateKeyDAO.prototype.hasPrivateKey = function(options, callback) { +PrivateKey.prototype.hasPrivateKey = function(options, callback) { if (!options.userId || !options.keyId) { callback(new Error('Incomplete arguments!')); return; @@ -147,7 +151,7 @@ PrivateKeyDAO.prototype.hasPrivateKey = function(options, callback) { * @param {Function} callback(error, found) * @return {Boolean} whether the key was found on the server or not. */ -PrivateKeyDAO.prototype.requestDownload = function(options, callback) { +PrivateKey.prototype.requestDownload = function(options, callback) { if (!options.userId || !options.keyId) { callback(new Error('Incomplete arguments!')); return; @@ -179,7 +183,7 @@ PrivateKeyDAO.prototype.requestDownload = function(options, callback) { * @param {Function} callback(error, encryptedPrivateKey) * @return {Object} {_id:[hex encoded capital 16 char key id], encryptedPrivateKey:[base64 encoded], encryptedUserId: [base64 encoded]} */ -PrivateKeyDAO.prototype.download = function(options, callback) { +PrivateKey.prototype.download = function(options, callback) { var uri; if (!options.userId || !options.keyId || !options.recoveryToken) { @@ -191,6 +195,4 @@ PrivateKeyDAO.prototype.download = function(options, callback) { this._restDao.get({ uri: uri }, callback); -}; - -module.exports = PrivateKeyDAO; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/service/publickey.js b/src/js/service/publickey.js index 0472871..7f074fd 100644 --- a/src/js/service/publickey.js +++ b/src/js/service/publickey.js @@ -1,13 +1,17 @@ 'use strict'; -var PublicKeyDAO = function(restDao) { +var ngModule = angular.module('woServices'); +ngModule.service('publicKey', PublicKey); +module.exports = PublicKey; + +function PublicKey(restDao) { this._restDao = restDao; -}; +} /** * Verify the public key behind the given uuid */ -PublicKeyDAO.prototype.verify = function(uuid, callback) { +PublicKey.prototype.verify = function(uuid, callback) { var uri = '/verify/' + uuid; this._restDao.get({ @@ -27,7 +31,7 @@ PublicKeyDAO.prototype.verify = function(uuid, callback) { /** * Find the user's corresponding public key */ -PublicKeyDAO.prototype.get = function(keyId, callback) { +PublicKey.prototype.get = function(keyId, callback) { var uri = '/publickey/key/' + keyId; this._restDao.get({ @@ -50,7 +54,7 @@ PublicKeyDAO.prototype.get = function(keyId, callback) { /** * Find the user's corresponding public key by email */ -PublicKeyDAO.prototype.getByUserId = function(userId, callback) { +PublicKey.prototype.getByUserId = function(userId, callback) { var uri = '/publickey/user/' + userId; this._restDao.get({ @@ -87,7 +91,7 @@ PublicKeyDAO.prototype.getByUserId = function(userId, callback) { /** * Persist the user's publc key */ -PublicKeyDAO.prototype.put = function(pubkey, callback) { +PublicKey.prototype.put = function(pubkey, callback) { var uri = '/publickey/user/' + pubkey.userId + '/key/' + pubkey._id; this._restDao.put(pubkey, uri, callback); }; @@ -95,9 +99,7 @@ PublicKeyDAO.prototype.put = function(pubkey, callback) { /** * Delete the public key from the cloud storage service */ -PublicKeyDAO.prototype.remove = function(keyId, callback) { +PublicKey.prototype.remove = function(keyId, callback) { var uri = '/publickey/key/' + keyId; this._restDao.remove(uri, callback); -}; - -module.exports = PublicKeyDAO; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/service/rest.js b/src/js/service/rest.js index 26da383..844639c 100644 --- a/src/js/service/rest.js +++ b/src/js/service/rest.js @@ -1,14 +1,20 @@ 'use strict'; +var ngModule = angular.module('woServices'); +ngModule.factory('restDao', function() { + return new RestDAO(); +}); +module.exports = RestDAO; + var config = require('../app-config').config; -var RestDAO = function(baseUri) { +function RestDAO(baseUri) { if (baseUri) { this._baseUri = baseUri; } else { this._baseUri = config.cloudUrl; } -}; +} /** * GET (read) request @@ -116,6 +122,4 @@ RestDAO.prototype._processRequest = function(options, callback) { }; xhr.send(options.payload ? JSON.stringify(options.payload) : undefined); -}; - -module.exports = RestDAO; \ No newline at end of file +}; \ No newline at end of file From ac09c7d91f1497ee1d3466cdc75c963b7953be36 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 18 Nov 2014 19:18:57 +0100 Subject: [PATCH 04/40] Move auth, oauth, connection-doctor to service dir --- src/js/{bo => service}/auth.js | 0 src/js/{util => service}/connection-doctor.js | 0 src/js/{util => service}/oauth.js | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/js/{bo => service}/auth.js (100%) rename src/js/{util => service}/connection-doctor.js (100%) rename src/js/{util => service}/oauth.js (100%) diff --git a/src/js/bo/auth.js b/src/js/service/auth.js similarity index 100% rename from src/js/bo/auth.js rename to src/js/service/auth.js diff --git a/src/js/util/connection-doctor.js b/src/js/service/connection-doctor.js similarity index 100% rename from src/js/util/connection-doctor.js rename to src/js/service/connection-doctor.js diff --git a/src/js/util/oauth.js b/src/js/service/oauth.js similarity index 100% rename from src/js/util/oauth.js rename to src/js/service/oauth.js From 38d908995a7eb1b45be1f6dd9e2b9873bf80b1a5 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 18 Nov 2014 20:19:29 +0100 Subject: [PATCH 05/40] Refactor BOs and utils to services --- src/js/crypto/crypto.js | 19 ++++++++++--------- src/js/crypto/index.js | 6 ++++++ src/js/crypto/pgp.js | 19 ++++++++++--------- src/js/service/account.js | 2 +- src/js/service/auth.js | 12 +++++++----- src/js/service/devicestorage.js | 8 +++++--- src/js/service/index.js | 4 ++-- src/js/service/invitation.js | 3 +++ src/js/service/lawnchair.js | 4 +++- src/js/service/oauth.js | 15 +++++++++------ src/js/service/privatekey.js | 3 +++ src/js/service/publickey.js | 3 +++ src/js/service/rest.js | 16 ++++++++-------- 13 files changed, 70 insertions(+), 44 deletions(-) create mode 100644 src/js/crypto/index.js diff --git a/src/js/crypto/crypto.js b/src/js/crypto/crypto.js index f19093a..04c13d8 100644 --- a/src/js/crypto/crypto.js +++ b/src/js/crypto/crypto.js @@ -1,16 +1,19 @@ -/** - * High level crypto api that invokes native crypto (if available) and - * gracefully degrades to JS crypto (if unavailable) - */ - 'use strict'; +var ngModule = angular.module('woCrypto'); +ngModule.service('crypto', Crypto); +module.exports = Crypto; + var aes = require('crypto-lib').aes, pbkdf2 = require('./pbkdf2'), config = require('../app-config').config, axe = require('axe-logger'); -var Crypto = function() {}; +/** + * High level crypto api that invokes native crypto (if available) and + * gracefully degrades to JS crypto (if unavailable) + */ +function Crypto() {} /** * Encrypt plaintext using AES-GCM. @@ -115,6 +118,4 @@ function startWorker(options) { return; } options.callback(null, result); -} - -module.exports = Crypto; \ No newline at end of file +} \ No newline at end of file diff --git a/src/js/crypto/index.js b/src/js/crypto/index.js new file mode 100644 index 0000000..f471282 --- /dev/null +++ b/src/js/crypto/index.js @@ -0,0 +1,6 @@ +'use strict'; + +angular.module('woCrypto', []); + +require('./pgp'); +require('./crypto'); \ No newline at end of file diff --git a/src/js/crypto/pgp.js b/src/js/crypto/pgp.js index 429085e..847532e 100644 --- a/src/js/crypto/pgp.js +++ b/src/js/crypto/pgp.js @@ -1,16 +1,19 @@ -/** - * High level crypto api that handles all calls to OpenPGP.js - */ - 'use strict'; +var ngModule = angular.module('woCrypto'); +ngModule.service('pgp', PGP); +module.exports = PGP; + var util = openpgp.util, config = require('../app-config').config; -var PGP = function() { +/** + * High level crypto api that handles all calls to OpenPGP.js + */ +function PGP() { openpgp.config.prefer_hash_algorithm = openpgp.enums.hash.sha256; openpgp.initWorker(config.workerPath + '/openpgp.worker.min.js'); -}; +} /** * Generate a key pair for the user @@ -426,6 +429,4 @@ function checkSignatureValidity(signatures) { // everything is in order return true; -} - -module.exports = PGP; \ No newline at end of file +} \ No newline at end of file diff --git a/src/js/service/account.js b/src/js/service/account.js index 9fbeac0..6ee7fba 100644 --- a/src/js/service/account.js +++ b/src/js/service/account.js @@ -13,7 +13,7 @@ function Account() { * Lists all of the current accounts connected to the app * @return {Array} The account objects containing folder and message objects */ -Account.prototype.all = function() { +Account.prototype.list = function() { return this._emailDAOs.map(function(emailDao) { return emailDao._account; }); diff --git a/src/js/service/auth.js b/src/js/service/auth.js index 7acdc7e..f5e1b23 100644 --- a/src/js/service/auth.js +++ b/src/js/service/auth.js @@ -1,5 +1,9 @@ 'use strict'; +var ngModule = angular.module('woServices'); +ngModule.service('auth', Auth); +module.exports = Auth; + var axe = require('axe-logger'), cfg = require('../app-config').config, str = require('../app-config').string; @@ -21,11 +25,11 @@ var SMTP_DB_KEY = 'smtp'; * auth.getCredentials(...); // called to gather all the information to connect to IMAP/SMTP, * username, password / oauth token, IMAP/SMTP server host names, ... */ -var Auth = function(appConfigStore, oauth, pgp) { +function Auth(appConfigStore, oauth, pgp) { this._appConfigStore = appConfigStore; this._oauth = oauth; this._pgp = pgp; -}; +} /** * Retrieves credentials and IMAP/SMTP settings: @@ -439,6 +443,4 @@ Auth.prototype.logout = function(callback) { callback(); }); -}; - -module.exports = Auth; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/service/devicestorage.js b/src/js/service/devicestorage.js index 4c9f90e..262cf24 100644 --- a/src/js/service/devicestorage.js +++ b/src/js/service/devicestorage.js @@ -1,14 +1,16 @@ 'use strict'; var ngModule = angular.module('woServices'); -ngModule.service('deviceStorage', DeviceStorage); +ngModule.factory('deviceStorage', ['lawnchairDAO', function(lawnchairDAO) { + return new DeviceStorage(lawnchairDAO); +}]); module.exports = DeviceStorage; /** * High level storage api that handles all persistence on the device. */ -function DeviceStorage(localDbDao) { - this._localDbDao = localDbDao; +function DeviceStorage(lawnchairDAO) { + this._localDbDao = lawnchairDAO; } DeviceStorage.prototype.init = function(emailAddress, callback) { diff --git a/src/js/service/index.js b/src/js/service/index.js index 1dea33f..eaf6a3c 100644 --- a/src/js/service/index.js +++ b/src/js/service/index.js @@ -2,6 +2,6 @@ angular.module('woServices', []); -require('./newsletter'), -require('./mail-config'), +require('./newsletter'); +require('./mail-config'); require('./account'); \ No newline at end of file diff --git a/src/js/service/invitation.js b/src/js/service/invitation.js index d5a0afc..9a34211 100644 --- a/src/js/service/invitation.js +++ b/src/js/service/invitation.js @@ -4,12 +4,15 @@ var ngModule = angular.module('woServices'); ngModule.service('invitation', Invitation); module.exports = Invitation; +var config = require('../app-config').config; + /** * The Invitation is a high level Data Access Object that access the invitation service REST endpoint. * @param {Object} restDao The REST Data Access Object abstraction */ function Invitation(restDao) { this._restDao = restDao; + this._restDao.setBaseUri(config.cloudUrl); } // diff --git a/src/js/service/lawnchair.js b/src/js/service/lawnchair.js index afdfcd3..fcdcee3 100644 --- a/src/js/service/lawnchair.js +++ b/src/js/service/lawnchair.js @@ -1,7 +1,9 @@ 'use strict'; var ngModule = angular.module('woServices'); -ngModule.service('lawnchairDAO', LawnchairDAO); +ngModule.factory('lawnchairDAO', function() { + return new LawnchairDAO(); +}); module.exports = LawnchairDAO; /** diff --git a/src/js/service/oauth.js b/src/js/service/oauth.js index f67be7c..a13913d 100644 --- a/src/js/service/oauth.js +++ b/src/js/service/oauth.js @@ -1,8 +1,13 @@ 'use strict'; -var OAuth = function(googleApi) { - this._googleApi = googleApi; -}; +var ngModule = angular.module('woServices'); +ngModule.service('oauth', OAuth); +module.exports = OAuth; + +function OAuth(restDao) { + this._googleApi = restDao; + this._googleApi.setBaseUri('https://www.googleapis.com'); +} /** * Check if chrome.identity api is supported @@ -94,6 +99,4 @@ OAuth.prototype.queryEmailAddress = function(token, callback) { callback(null, info.email); }); -}; - -module.exports = OAuth; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/service/privatekey.js b/src/js/service/privatekey.js index 8677bed..0511506 100644 --- a/src/js/service/privatekey.js +++ b/src/js/service/privatekey.js @@ -4,8 +4,11 @@ var ngModule = angular.module('woServices'); ngModule.service('privateKey', PrivateKey); module.exports = PrivateKey; +var config = require('../app-config').config; + function PrivateKey(restDao) { this._restDao = restDao; + this._restDao.setBaseUri(config.privkeyServerUrl); } // diff --git a/src/js/service/publickey.js b/src/js/service/publickey.js index 7f074fd..f2b9736 100644 --- a/src/js/service/publickey.js +++ b/src/js/service/publickey.js @@ -4,8 +4,11 @@ var ngModule = angular.module('woServices'); ngModule.service('publicKey', PublicKey); module.exports = PublicKey; +var config = require('../app-config').config; + function PublicKey(restDao) { this._restDao = restDao; + this._restDao.setBaseUri(config.cloudUrl); } /** diff --git a/src/js/service/rest.js b/src/js/service/rest.js index 844639c..f4a765f 100644 --- a/src/js/service/rest.js +++ b/src/js/service/rest.js @@ -6,15 +6,15 @@ ngModule.factory('restDao', function() { }); module.exports = RestDAO; -var config = require('../app-config').config; +function RestDAO() {} -function RestDAO(baseUri) { - if (baseUri) { - this._baseUri = baseUri; - } else { - this._baseUri = config.cloudUrl; - } -} +/** + * Set the REST DAO's base url + * @param {String} baseUri The base url e.g. https://api.example.com + */ +RestDAO.prototype.setBaseUri = function(baseUri) { + this._baseUri = baseUri; +}; /** * GET (read) request From a5da52c8a64769e707224e14e26b8b7cf2d1ff53 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 18 Nov 2014 20:22:51 +0100 Subject: [PATCH 06/40] Move outbox to services --- src/js/{bo => service}/outbox.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/js/{bo => service}/outbox.js (100%) diff --git a/src/js/bo/outbox.js b/src/js/service/outbox.js similarity index 100% rename from src/js/bo/outbox.js rename to src/js/service/outbox.js From 41879bf5b32da88f0527558da569ec2465d1eedc Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 18 Nov 2014 20:28:10 +0100 Subject: [PATCH 07/40] Refactor outbox to be an angular service --- src/js/service/outbox.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/js/service/outbox.js b/src/js/service/outbox.js index 12ccc50..a81d06a 100644 --- a/src/js/service/outbox.js +++ b/src/js/service/outbox.js @@ -1,5 +1,9 @@ 'use strict'; +var ngModule = angular.module('woServices'); +ngModule.service('outbox', Outbox); +module.exports = Outbox; + var util = require('crypto-lib').util, config = require('../app-config').config, outboxDb = 'email_OUTBOX'; @@ -9,7 +13,7 @@ var util = require('crypto-lib').util, * The local outbox takes care of the emails before they are being sent. * It also checks periodically if there are any mails in the local device storage to be sent. */ -var OutboxBO = function(emailDao, keychain, devicestorage) { +function Outbox(emailDao, keychain, devicestorage) { /** @private */ this._emailDao = emailDao; @@ -23,13 +27,13 @@ var OutboxBO = function(emailDao, keychain, devicestorage) { * Semaphore-esque flag to avoid 'concurrent' calls to _processOutbox when the timeout fires, but a call is still in process. * @private */ this._outboxBusy = false; -}; +} /** * This function activates the periodic checking of the local device storage for pending mails. * @param {Function} callback(error, pendingMailsCount) Callback that informs you about the count of pending mails. */ -OutboxBO.prototype.startChecking = function(callback) { +Outbox.prototype.startChecking = function(callback) { // remember global callback this._onUpdate = callback; // start periodic checking of outbox @@ -39,7 +43,7 @@ OutboxBO.prototype.startChecking = function(callback) { /** * Outbox stops the periodic checking of the local device storage for pending mails. */ -OutboxBO.prototype.stopChecking = function() { +Outbox.prototype.stopChecking = function() { if (!this._intervalId) { return; } @@ -53,7 +57,7 @@ OutboxBO.prototype.stopChecking = function() { * @param {Object} mail The Email DTO * @param {Function} callback Invoked when the object was encrypted and persisted to disk */ -OutboxBO.prototype.put = function(mail, callback) { +Outbox.prototype.put = function(mail, callback) { var self = this, allReaders = mail.from.concat(mail.to.concat(mail.cc.concat(mail.bcc))); // all the users that should be able to read the mail @@ -133,7 +137,7 @@ OutboxBO.prototype.put = function(mail, callback) { * Checks the local device storage for pending mails. * @param {Function} callback(error, pendingMailsCount) Callback that informs you about the count of pending mails. */ -OutboxBO.prototype._processOutbox = function(callback) { +Outbox.prototype._processOutbox = function(callback) { var self = this, unsentMails = 0; @@ -224,6 +228,4 @@ OutboxBO.prototype._processOutbox = function(callback) { done(); }); } -}; - -module.exports = OutboxBO; \ No newline at end of file +}; \ No newline at end of file From 95ad15b0dddd7c28f5f2541d113ae1391fccacec Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Wed, 19 Nov 2014 15:15:57 +0100 Subject: [PATCH 08/40] Export account service as common.js module --- src/js/service/account.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/js/service/account.js b/src/js/service/account.js index 6ee7fba..6b87b47 100644 --- a/src/js/service/account.js +++ b/src/js/service/account.js @@ -2,11 +2,12 @@ var ngModule = angular.module('woServices'); ngModule.service('account', Account); +module.exports = Account; -var Email = require('./email'); - -function Account() { - this._emailDAOs = []; +function Account(email, outbox) { + this._emailDAOs = [email]; + this._outboxes = [outbox]; + this._accounts = undefined; } /** @@ -14,9 +15,11 @@ function Account() { * @return {Array} The account objects containing folder and message objects */ Account.prototype.list = function() { - return this._emailDAOs.map(function(emailDao) { + this._accounts = this._emailDAOs.map(function(emailDao) { return emailDao._account; }); + + return this._accounts; }; /** From c221372ed4b0f0f652c35f792bd8e3cbe9aa3eaa Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Wed, 19 Nov 2014 15:23:10 +0100 Subject: [PATCH 09/40] Restructure architecture into angular modules and services --- src/js/app-controller.js | 5 ++--- src/js/{service => email}/account.js | 0 src/js/{service => email}/email.js | 0 src/js/email/index.js | 6 ++++++ src/js/email/mailreader.js | 10 ++++++++++ src/js/{service => email}/outbox.js | 0 src/js/email/pgpbuilder.js | 4 ++++ src/js/service/auth.js | 4 ++-- src/js/service/devicestorage.js | 12 +++++++----- src/js/service/index.js | 3 ++- src/js/{service => util}/connection-doctor.js | 0 src/js/util/dialog.js | 12 ++++++++++++ src/js/util/index.js | 6 ++++++ src/js/util/update/update-handler.js | 13 ++++++++----- 14 files changed, 59 insertions(+), 16 deletions(-) rename src/js/{service => email}/account.js (100%) rename src/js/{service => email}/email.js (100%) create mode 100644 src/js/email/index.js create mode 100644 src/js/email/mailreader.js rename src/js/{service => email}/outbox.js (100%) create mode 100644 src/js/email/pgpbuilder.js rename src/js/{service => util}/connection-doctor.js (100%) create mode 100644 src/js/util/dialog.js create mode 100644 src/js/util/index.js diff --git a/src/js/app-controller.js b/src/js/app-controller.js index f358ffc..77b3763 100644 --- a/src/js/app-controller.js +++ b/src/js/app-controller.js @@ -78,9 +78,6 @@ ctrl.start = function(options, callback) { ctrl.buildModules = function() { var lawnchairDao, restDao, pubkeyDao, privkeyDao, crypto, emailDao, keychain, pgp, userStorage, pgpbuilder, oauth, appConfigStore, auth; - // start the mailreader's worker thread - mailreader.startWorker(config.workerPath + '/mailreader-parser-worker.min.js'); - // init objects and inject dependencies restDao = new RestDAO(); lawnchairDao = new LawnchairDAO(); @@ -91,6 +88,8 @@ ctrl.buildModules = function() { crypto = new Crypto(); ctrl._pgp = pgp = new PGP(); ctrl._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao, privkeyDao, crypto, pgp); + + // TODO: inject dialog service directly into keychain service keychain.requestPermissionForKeyUpdate = function(params, callback) { var message = params.newKey ? str.updatePublicKeyMsgNewKey : str.updatePublicKeyMsgRemovedKey; message = message.replace('{0}', params.userId); diff --git a/src/js/service/account.js b/src/js/email/account.js similarity index 100% rename from src/js/service/account.js rename to src/js/email/account.js diff --git a/src/js/service/email.js b/src/js/email/email.js similarity index 100% rename from src/js/service/email.js rename to src/js/email/email.js diff --git a/src/js/email/index.js b/src/js/email/index.js new file mode 100644 index 0000000..e8c6d5a --- /dev/null +++ b/src/js/email/index.js @@ -0,0 +1,6 @@ +'use strict'; + +angular.module('woEmail', []); + +require('./email'); +require('./account'); \ No newline at end of file diff --git a/src/js/email/mailreader.js b/src/js/email/mailreader.js new file mode 100644 index 0000000..c37a537 --- /dev/null +++ b/src/js/email/mailreader.js @@ -0,0 +1,10 @@ +'use strict'; + +var mailreader = require('mailreader'); +var config = require('../app-config').config; +mailreader.startWorker(config.workerPath + '/mailreader-parser-worker.min.js'); + +var ngModule = angular.module('woServices'); +ngModule.factory('mailreader', function() { + return mailreader; +}); \ No newline at end of file diff --git a/src/js/service/outbox.js b/src/js/email/outbox.js similarity index 100% rename from src/js/service/outbox.js rename to src/js/email/outbox.js diff --git a/src/js/email/pgpbuilder.js b/src/js/email/pgpbuilder.js new file mode 100644 index 0000000..3245e32 --- /dev/null +++ b/src/js/email/pgpbuilder.js @@ -0,0 +1,4 @@ +'use strict'; + +var ngModule = angular.module('woServices'); +ngModule.service('pgpbuilder', require('pgpbuilder')); \ No newline at end of file diff --git a/src/js/service/auth.js b/src/js/service/auth.js index f5e1b23..6cb1863 100644 --- a/src/js/service/auth.js +++ b/src/js/service/auth.js @@ -25,8 +25,8 @@ var SMTP_DB_KEY = 'smtp'; * auth.getCredentials(...); // called to gather all the information to connect to IMAP/SMTP, * username, password / oauth token, IMAP/SMTP server host names, ... */ -function Auth(appConfigStore, oauth, pgp) { - this._appConfigStore = appConfigStore; +function Auth(deviceStorage, oauth, pgp) { + this._appConfigStore = deviceStorage; this._oauth = oauth; this._pgp = pgp; } diff --git a/src/js/service/devicestorage.js b/src/js/service/devicestorage.js index 262cf24..b457e15 100644 --- a/src/js/service/devicestorage.js +++ b/src/js/service/devicestorage.js @@ -1,9 +1,11 @@ 'use strict'; var ngModule = angular.module('woServices'); -ngModule.factory('deviceStorage', ['lawnchairDAO', function(lawnchairDAO) { - return new DeviceStorage(lawnchairDAO); -}]); +ngModule.factory('deviceStorage', ['lawnchairDAO', + function(lawnchairDAO) { + return new DeviceStorage(lawnchairDAO); + } +]); module.exports = DeviceStorage; /** @@ -13,8 +15,8 @@ function DeviceStorage(lawnchairDAO) { this._localDbDao = lawnchairDAO; } -DeviceStorage.prototype.init = function(emailAddress, callback) { - this._localDbDao.init(emailAddress, callback); +DeviceStorage.prototype.init = function(dbName, callback) { + this._localDbDao.init(dbName, callback); }; /** diff --git a/src/js/service/index.js b/src/js/service/index.js index eaf6a3c..bbe35ab 100644 --- a/src/js/service/index.js +++ b/src/js/service/index.js @@ -4,4 +4,5 @@ angular.module('woServices', []); require('./newsletter'); require('./mail-config'); -require('./account'); \ No newline at end of file +require('./account'); +require('.pgpbuilder'); \ No newline at end of file diff --git a/src/js/service/connection-doctor.js b/src/js/util/connection-doctor.js similarity index 100% rename from src/js/service/connection-doctor.js rename to src/js/util/connection-doctor.js diff --git a/src/js/util/dialog.js b/src/js/util/dialog.js new file mode 100644 index 0000000..41a7692 --- /dev/null +++ b/src/js/util/dialog.js @@ -0,0 +1,12 @@ +'use strict'; + +var ngModule = angular.module('woUtil'); +ngModule.service('dialog', Dialog); + +function Dialog() {} + +Dialog.prototype.error = function() {}; + +Dialog.prototype.info = function() {}; + +Dialog.prototype.confirm = function() {}; \ No newline at end of file diff --git a/src/js/util/index.js b/src/js/util/index.js new file mode 100644 index 0000000..8685385 --- /dev/null +++ b/src/js/util/index.js @@ -0,0 +1,6 @@ +'use strict'; + +angular.module('woUtil', []); + +require('./dialog'); +require('./update/update-handler'); \ No newline at end of file diff --git a/src/js/util/update/update-handler.js b/src/js/util/update/update-handler.js index 0928cbf..d74e9e0 100644 --- a/src/js/util/update/update-handler.js +++ b/src/js/util/update/update-handler.js @@ -1,5 +1,10 @@ 'use strict'; +var ngModule = angular.module('woUtil'); +ngModule.service('updateHandler', ['deviceStorage', 'deviceStorage', 'auth', UpdateHandler]); +module.exports = UpdateHandler; + + var axe = require('axe-logger'), cfg = require('../../app-config').config, updateV1 = require('./update-v1'), @@ -11,12 +16,12 @@ var axe = require('axe-logger'), /** * Handles database migration */ -var UpdateHandler = function(appConfigStorage, userStorage, auth) { +function UpdateHandler(appConfigStorage, userStorage, auth) { this._appConfigStorage = appConfigStorage; this._userStorage = userStorage; this._updateScripts = [updateV1, updateV2, updateV3, updateV4, updateV5]; this._auth = auth; -}; +} /** * Executes all the necessary updates @@ -125,6 +130,4 @@ UpdateHandler.prototype.checkForUpdate = function(dialog) { } }); } -}; - -module.exports = UpdateHandler; \ No newline at end of file +}; \ No newline at end of file From da416f43297e1a433769dac76ebcf40c473e7b39 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Wed, 19 Nov 2014 15:46:20 +0100 Subject: [PATCH 10/40] Add TODO comments to controllers --- src/js/controller/login.js | 2 ++ src/js/controller/validate-phone.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/js/controller/login.js b/src/js/controller/login.js index 30854ec..7087cbc 100644 --- a/src/js/controller/login.js +++ b/src/js/controller/login.js @@ -19,6 +19,8 @@ var LoginCtrl = function($scope, $location) { initializeUser(); }); + // TODO: move to Account service login function + function initializeUser() { // get OAuth token from chrome appController._auth.getEmailAddress(function(err, info) { diff --git a/src/js/controller/validate-phone.js b/src/js/controller/validate-phone.js index fd261b8..05c1221 100644 --- a/src/js/controller/validate-phone.js +++ b/src/js/controller/validate-phone.js @@ -8,6 +8,8 @@ var ValidatePhoneCtrl = function($scope, $location, $routeParams, mailConfig) { return; } + // TODO: move to Account service create function + $scope.validateUser = function() { if ($scope.form.$invalid) { $scope.errMsg = 'Please fill out all required fields!'; From 1f5fa4ca418777c7fb533f2e37bef9df97da5a07 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Wed, 19 Nov 2014 15:53:05 +0100 Subject: [PATCH 11/40] Move controllers into subfolders --- src/js/controller/{ => app}/about.js | 0 src/js/controller/{ => app}/account.js | 0 src/js/controller/{ => app}/action-bar.js | 0 src/js/controller/{ => app}/contacts.js | 0 src/js/controller/{ => app}/dialog.js | 0 src/js/controller/{ => app}/mail-list.js | 0 src/js/controller/{ => app}/navigation.js | 0 src/js/controller/{ => app}/privatekey-upload.js | 0 src/js/controller/{ => app}/read-sandbox.js | 0 src/js/controller/{ => app}/read.js | 0 src/js/controller/{ => app}/set-passphrase.js | 0 src/js/controller/{ => app}/write.js | 0 src/js/controller/{ => login}/add-account.js | 0 src/js/controller/{ => login}/create-account.js | 0 src/js/controller/{ => login}/login-existing.js | 0 src/js/controller/{ => login}/login-initial.js | 0 src/js/controller/{ => login}/login-new-device.js | 0 src/js/controller/{ => login}/login-privatekey-download.js | 0 src/js/controller/{ => login}/login-set-credentials.js | 0 src/js/controller/{ => login}/login.js | 0 src/js/controller/{ => login}/validate-phone.js | 0 21 files changed, 0 insertions(+), 0 deletions(-) rename src/js/controller/{ => app}/about.js (100%) rename src/js/controller/{ => app}/account.js (100%) rename src/js/controller/{ => app}/action-bar.js (100%) rename src/js/controller/{ => app}/contacts.js (100%) rename src/js/controller/{ => app}/dialog.js (100%) rename src/js/controller/{ => app}/mail-list.js (100%) rename src/js/controller/{ => app}/navigation.js (100%) rename src/js/controller/{ => app}/privatekey-upload.js (100%) rename src/js/controller/{ => app}/read-sandbox.js (100%) rename src/js/controller/{ => app}/read.js (100%) rename src/js/controller/{ => app}/set-passphrase.js (100%) rename src/js/controller/{ => app}/write.js (100%) rename src/js/controller/{ => login}/add-account.js (100%) rename src/js/controller/{ => login}/create-account.js (100%) rename src/js/controller/{ => login}/login-existing.js (100%) rename src/js/controller/{ => login}/login-initial.js (100%) rename src/js/controller/{ => login}/login-new-device.js (100%) rename src/js/controller/{ => login}/login-privatekey-download.js (100%) rename src/js/controller/{ => login}/login-set-credentials.js (100%) rename src/js/controller/{ => login}/login.js (100%) rename src/js/controller/{ => login}/validate-phone.js (100%) diff --git a/src/js/controller/about.js b/src/js/controller/app/about.js similarity index 100% rename from src/js/controller/about.js rename to src/js/controller/app/about.js diff --git a/src/js/controller/account.js b/src/js/controller/app/account.js similarity index 100% rename from src/js/controller/account.js rename to src/js/controller/app/account.js diff --git a/src/js/controller/action-bar.js b/src/js/controller/app/action-bar.js similarity index 100% rename from src/js/controller/action-bar.js rename to src/js/controller/app/action-bar.js diff --git a/src/js/controller/contacts.js b/src/js/controller/app/contacts.js similarity index 100% rename from src/js/controller/contacts.js rename to src/js/controller/app/contacts.js diff --git a/src/js/controller/dialog.js b/src/js/controller/app/dialog.js similarity index 100% rename from src/js/controller/dialog.js rename to src/js/controller/app/dialog.js diff --git a/src/js/controller/mail-list.js b/src/js/controller/app/mail-list.js similarity index 100% rename from src/js/controller/mail-list.js rename to src/js/controller/app/mail-list.js diff --git a/src/js/controller/navigation.js b/src/js/controller/app/navigation.js similarity index 100% rename from src/js/controller/navigation.js rename to src/js/controller/app/navigation.js diff --git a/src/js/controller/privatekey-upload.js b/src/js/controller/app/privatekey-upload.js similarity index 100% rename from src/js/controller/privatekey-upload.js rename to src/js/controller/app/privatekey-upload.js diff --git a/src/js/controller/read-sandbox.js b/src/js/controller/app/read-sandbox.js similarity index 100% rename from src/js/controller/read-sandbox.js rename to src/js/controller/app/read-sandbox.js diff --git a/src/js/controller/read.js b/src/js/controller/app/read.js similarity index 100% rename from src/js/controller/read.js rename to src/js/controller/app/read.js diff --git a/src/js/controller/set-passphrase.js b/src/js/controller/app/set-passphrase.js similarity index 100% rename from src/js/controller/set-passphrase.js rename to src/js/controller/app/set-passphrase.js diff --git a/src/js/controller/write.js b/src/js/controller/app/write.js similarity index 100% rename from src/js/controller/write.js rename to src/js/controller/app/write.js diff --git a/src/js/controller/add-account.js b/src/js/controller/login/add-account.js similarity index 100% rename from src/js/controller/add-account.js rename to src/js/controller/login/add-account.js diff --git a/src/js/controller/create-account.js b/src/js/controller/login/create-account.js similarity index 100% rename from src/js/controller/create-account.js rename to src/js/controller/login/create-account.js diff --git a/src/js/controller/login-existing.js b/src/js/controller/login/login-existing.js similarity index 100% rename from src/js/controller/login-existing.js rename to src/js/controller/login/login-existing.js diff --git a/src/js/controller/login-initial.js b/src/js/controller/login/login-initial.js similarity index 100% rename from src/js/controller/login-initial.js rename to src/js/controller/login/login-initial.js diff --git a/src/js/controller/login-new-device.js b/src/js/controller/login/login-new-device.js similarity index 100% rename from src/js/controller/login-new-device.js rename to src/js/controller/login/login-new-device.js diff --git a/src/js/controller/login-privatekey-download.js b/src/js/controller/login/login-privatekey-download.js similarity index 100% rename from src/js/controller/login-privatekey-download.js rename to src/js/controller/login/login-privatekey-download.js diff --git a/src/js/controller/login-set-credentials.js b/src/js/controller/login/login-set-credentials.js similarity index 100% rename from src/js/controller/login-set-credentials.js rename to src/js/controller/login/login-set-credentials.js diff --git a/src/js/controller/login.js b/src/js/controller/login/login.js similarity index 100% rename from src/js/controller/login.js rename to src/js/controller/login/login.js diff --git a/src/js/controller/validate-phone.js b/src/js/controller/login/validate-phone.js similarity index 100% rename from src/js/controller/validate-phone.js rename to src/js/controller/login/validate-phone.js From e6b22bd0a0557e0809286cdd9dbe6d796b179d64 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Wed, 19 Nov 2014 17:19:55 +0100 Subject: [PATCH 12/40] Expose angular/common.js modules for util, app-config --- src/js/app-config.js | 14 +++++++++++--- src/js/app-controller.js | 2 +- src/js/service/mail-config.js | 8 ++++---- src/js/service/newsletter.js | 1 + src/js/util/connection-doctor.js | 10 ++++++---- src/js/util/dialog.js | 1 + src/js/util/index.js | 1 + src/js/util/update/update-handler.js | 1 - 8 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/js/app-config.js b/src/js/app-config.js index 460b117..4f06416 100644 --- a/src/js/app-config.js +++ b/src/js/app-config.js @@ -1,9 +1,17 @@ 'use strict'; +var appCfg = {}; + +var ngModule = angular.module('mail'); +ngModule.factory('appConfig', function() { + return appCfg; +}); +exports = appCfg; + /** * Global app configurations */ -exports.config = { +appCfg.config = { cloudUrl: 'https://keys.whiteout.io', privkeyServerUrl: 'https://keychain.whiteout.io', adminUrl: 'https://admin-node.whiteout.io', @@ -38,7 +46,7 @@ if (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.getManifes } function setConfigParams(manifest) { - var cfg = exports.config; + var cfg = appCfg.config; function getUrl(beginsWith) { return _.find(manifest.permissions, function(permission) { @@ -57,7 +65,7 @@ function setConfigParams(manifest) { /** * Strings are maintained here */ -exports.string = { +appCfg.string = { fallbackSubject: '(no subject)', invitationSubject: 'Invitation to a private conversation', invitationMessage: 'Hi,\n\nI use Whiteout Mail to send and receive encrypted email. I would like to exchange encrypted messages with you as well.\n\nPlease install the Whiteout Mail application. This application makes it easy to read and write messages securely with PGP encryption applied.\n\nGo to the Whiteout Networks homepage to learn more and to download the application: https://whiteout.io\n\n', diff --git a/src/js/app-controller.js b/src/js/app-controller.js index 77b3763..b28e9f5 100644 --- a/src/js/app-controller.js +++ b/src/js/app-controller.js @@ -253,7 +253,7 @@ ctrl.logout = function() { }); }; -// TODO: move onConnect to emailDao +// TODO: move onConnect to Account service /** * Event that is called when the user agent goes online. This create new instances of the imap-client and pgp-mailer and connects to the mail server. diff --git a/src/js/service/mail-config.js b/src/js/service/mail-config.js index df7eda9..72e4846 100644 --- a/src/js/service/mail-config.js +++ b/src/js/service/mail-config.js @@ -2,12 +2,12 @@ var ngModule = angular.module('woServices'); ngModule.service('mailConfig', MailConfig); +module.exports = MailConfig; -var cfg = require('../app-config').config; - -function MailConfig($http, $q) { +function MailConfig($http, $q, appConfig) { this._http = $http; this._q = $q; + this._appConfig = appConfig; } /** @@ -20,7 +20,7 @@ MailConfig.prototype.get = function(emailAddress) { }); } - var url = cfg.settingsUrl + emailAddress.split('@')[1]; + var url = this._appConfig.settingsUrl + emailAddress.split('@')[1]; return this._http.get(url).then(function(res) { return res.data; }); diff --git a/src/js/service/newsletter.js b/src/js/service/newsletter.js index ff25f74..1686650 100644 --- a/src/js/service/newsletter.js +++ b/src/js/service/newsletter.js @@ -2,6 +2,7 @@ var ngModule = angular.module('woServices'); ngModule.service('newsletter', Newsletter); +module.exports = Newsletter; function Newsletter($q) { this._q = $q; diff --git a/src/js/util/connection-doctor.js b/src/js/util/connection-doctor.js index eeeccb5..b78f462 100644 --- a/src/js/util/connection-doctor.js +++ b/src/js/util/connection-doctor.js @@ -1,5 +1,9 @@ 'use strict'; +var ngModule = angular.module('woUtil'); +ngModule.service('connectionDoctor', ConnectionDoctor); +module.exports = ConnectionDoctor; + var TCPSocket = require('tcp-socket'), appConfig = require('../app-config'), cfg = appConfig.config, @@ -14,7 +18,7 @@ var TCPSocket = require('tcp-socket'), * * @constructor */ -var ConnectionDoctor = function() {}; +function ConnectionDoctor() {} // @@ -293,6 +297,4 @@ function createError(code, message, underlyingError) { error.underlyingError = underlyingError; return error; -} - -module.exports = ConnectionDoctor; +} \ No newline at end of file diff --git a/src/js/util/dialog.js b/src/js/util/dialog.js index 41a7692..e1852a5 100644 --- a/src/js/util/dialog.js +++ b/src/js/util/dialog.js @@ -2,6 +2,7 @@ var ngModule = angular.module('woUtil'); ngModule.service('dialog', Dialog); +module.exports = Dialog; function Dialog() {} diff --git a/src/js/util/index.js b/src/js/util/index.js index 8685385..1e50e5f 100644 --- a/src/js/util/index.js +++ b/src/js/util/index.js @@ -3,4 +3,5 @@ angular.module('woUtil', []); require('./dialog'); +require('./connection-doctor'); require('./update/update-handler'); \ No newline at end of file diff --git a/src/js/util/update/update-handler.js b/src/js/util/update/update-handler.js index d74e9e0..e72fccb 100644 --- a/src/js/util/update/update-handler.js +++ b/src/js/util/update/update-handler.js @@ -4,7 +4,6 @@ var ngModule = angular.module('woUtil'); ngModule.service('updateHandler', ['deviceStorage', 'deviceStorage', 'auth', UpdateHandler]); module.exports = UpdateHandler; - var axe = require('axe-logger'), cfg = require('../../app-config').config, updateV1 = require('./update-v1'), From 4c04ba4e74295499635a92e6aec8f11252acb054 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Wed, 19 Nov 2014 20:54:59 +0100 Subject: [PATCH 13/40] Refactor login controllers and delete AppController --- src/index.html | 2 +- src/js/app-config.js | 2 +- src/js/app-controller.js | 319 ------------------ src/js/app.js | 46 ++- src/js/controller/app/navigation.js | 32 +- src/js/controller/login/add-account.js | 13 +- src/js/controller/login/create-account.js | 20 +- src/js/controller/login/login-existing.js | 19 +- src/js/controller/login/login-initial.js | 18 +- src/js/controller/login/login-new-device.js | 22 +- .../login/login-privatekey-download.js | 20 +- .../controller/login/login-set-credentials.js | 16 +- src/js/controller/login/login.js | 46 +-- src/js/controller/login/validate-phone.js | 25 +- src/js/crypto/index.js | 2 +- src/js/email/account.js | 239 +++++++++++-- src/js/email/email.js | 6 +- src/js/email/index.js | 5 +- src/js/email/outbox.js | 6 +- src/js/service/admin.js | 3 +- src/js/service/app-config-store.js | 15 + src/js/service/auth.js | 18 +- src/js/service/devicestorage.js | 12 +- src/js/service/index.js | 17 +- src/js/service/invitation.js | 6 +- src/js/service/keychain.js | 54 ++- src/js/service/lawnchair.js | 16 +- src/js/service/privatekey.js | 6 +- src/js/service/publickey.js | 6 +- src/js/util/update/update-handler.js | 15 +- 30 files changed, 455 insertions(+), 571 deletions(-) delete mode 100644 src/js/app-controller.js create mode 100644 src/js/service/app-config-store.js diff --git a/src/index.html b/src/index.html index 8f12cd6..dc456e1 100644 --- a/src/index.html +++ b/src/index.html @@ -1,5 +1,5 @@ - + Whiteout Mail diff --git a/src/js/app-config.js b/src/js/app-config.js index 4f06416..2678a45 100644 --- a/src/js/app-config.js +++ b/src/js/app-config.js @@ -2,7 +2,7 @@ var appCfg = {}; -var ngModule = angular.module('mail'); +var ngModule = angular.module('woAppConfig'); ngModule.factory('appConfig', function() { return appCfg; }); diff --git a/src/js/app-controller.js b/src/js/app-controller.js deleted file mode 100644 index b28e9f5..0000000 --- a/src/js/app-controller.js +++ /dev/null @@ -1,319 +0,0 @@ -/** - * The main application controller - */ - -'use strict'; - -var axe = require('axe-logger'), - Auth = require('./bo/auth'), - PGP = require('./crypto/pgp'), - OAuth = require('./util/oauth'), - PgpMailer = require('pgpmailer'), - util = require('crypto-lib').util, - PgpBuilder = require('pgpbuilder'), - OutboxBO = require('./bo/outbox'), - mailreader = require('mailreader'), - ImapClient = require('imap-client'), - Crypto = require('./crypto/crypto'), - RestDAO = require('./dao/rest-dao'), - appConfig = require('./app-config'), - EmailDAO = require('./dao/email-dao'), - AdminDao = require('./dao/admin-dao'), - KeychainDAO = require('./dao/keychain-dao'), - PublicKeyDAO = require('./dao/publickey-dao'), - LawnchairDAO = require('./dao/lawnchair-dao'), - PrivateKeyDAO = require('./dao/privatekey-dao'), - InvitationDAO = require('./dao/invitation-dao'), - DeviceStorageDAO = require('./dao/devicestorage-dao'), - ConnectionDoctor = require('./util/connection-doctor'), - UpdateHandler = require('./util/update/update-handler'), - config = appConfig.config, - str = appConfig.string; - -var ctrl = {}; - -/** - * Start the application. - */ -ctrl.start = function(options, callback) { - if (ctrl.started) { - return callback(); - } - - ctrl.started = true; - ctrl.onError = options.onError; // TODO: replace by errorService - - // 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 - axe.debug('Assuming Cordova environment...'); - document.addEventListener("deviceready", onDeviceReady, false); - } else { - // No need to wait on events... just start the app - axe.debug('Assuming Browser environment...'); - onDeviceReady(); - } - - function onDeviceReady() { - axe.debug('Starting app.'); - - // TODO: will be replaced by angular dependency management - ctrl.buildModules(); - - // TODO: move self-contained connection management in emailDao - // Handle offline and online gracefully - window.addEventListener('online', ctrl.onConnect.bind(ctrl, ctrl.onError)); - window.addEventListener('offline', ctrl.onDisconnect.bind(ctrl)); - - // TODO: move appConfigService shared singleton - ctrl._appConfigStore.init('app-config', callback); - } -}; - -// TODO: will be replaced by angular dependency management - -/** - * Initialize the dependency tree. - */ -ctrl.buildModules = function() { - var lawnchairDao, restDao, pubkeyDao, privkeyDao, crypto, emailDao, keychain, pgp, userStorage, pgpbuilder, oauth, appConfigStore, auth; - - // init objects and inject dependencies - restDao = new RestDAO(); - lawnchairDao = new LawnchairDAO(); - pubkeyDao = new PublicKeyDAO(restDao); - privkeyDao = new PrivateKeyDAO(new RestDAO(config.privkeyServerUrl)); - oauth = new OAuth(new RestDAO('https://www.googleapis.com')); - - crypto = new Crypto(); - ctrl._pgp = pgp = new PGP(); - ctrl._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao, privkeyDao, crypto, pgp); - - // TODO: inject dialog service directly into keychain service - keychain.requestPermissionForKeyUpdate = function(params, callback) { - var message = params.newKey ? str.updatePublicKeyMsgNewKey : str.updatePublicKeyMsgRemovedKey; - message = message.replace('{0}', params.userId); - - ctrl.onError({ - title: str.updatePublicKeyTitle, - message: message, - positiveBtnStr: str.updatePublicKeyPosBtn, - negativeBtnStr: str.updatePublicKeyNegBtn, - showNegativeBtn: true, - callback: callback - }); - }; - - ctrl._appConfigStore = appConfigStore = new DeviceStorageDAO(new LawnchairDAO()); - ctrl._auth = auth = new Auth(appConfigStore, oauth, pgp); - ctrl._userStorage = userStorage = new DeviceStorageDAO(lawnchairDao); - ctrl._invitationDao = new InvitationDAO(restDao); - ctrl._pgpbuilder = pgpbuilder = new PgpBuilder(); - ctrl._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader); - ctrl._outboxBo = new OutboxBO(emailDao, keychain, userStorage); - ctrl._updateHandler = new UpdateHandler(appConfigStore, userStorage, auth); - ctrl._adminDao = new AdminDao(new RestDAO(config.adminUrl)); - ctrl._doctor = new ConnectionDoctor(); - - emailDao.onError = ctrl.onError; -}; - -/** - * Calls runtime hooks to check if an app update is available. - */ -ctrl.checkForUpdate = function() { - ctrl._updateHandler.checkForUpdate(ctrl.onError); -}; - -// TODO: move to AccountService - -/** - * Fire up the database, retrieve the available keys for the user and initialize the email data access object - */ -ctrl.init = function(options, callback) { - // account information for the email dao - var account = { - realname: options.realname, - emailAddress: options.emailAddress, - asymKeySize: config.asymKeySize - }; - - // Pre-Flight check: don't even start to initialize stuff if the email address is not valid - if (!util.validateEmailAddress(options.emailAddress)) { - return callback(new Error('The user email address is invalid!')); - } - - prepareDatabase(); - - // Pre-Flight check: initialize and prepare user's local database - function prepareDatabase() { - ctrl._userStorage.init(options.emailAddress, function(err) { - if (err) { - return callback(err); - } - - // Migrate the databases if necessary - ctrl._updateHandler.update(function(err) { - if (err) { - return callback(new Error('Updating the internal database failed. Please reinstall the app! Reason: ' + err.message)); - } - - prepareKeys(); - }); - }); - } - - // retrieve keypair fom devicestorage/cloud, refresh public key if signup was incomplete before - function prepareKeys() { - ctrl._keychain.getUserKeyPair(options.emailAddress, function(err, keys) { - if (err) { - return callback(err); - } - - // this is either a first start on a new device, OR a subsequent start without completing the signup, - // since we can't differenciate those cases here, do a public key refresh because it might be outdated - if (keys && keys.publicKey && !keys.privateKey) { - ctrl._keychain.refreshKeyForUserId({ - userId: options.emailAddress, - overridePermission: true - }, function(err, publicKey) { - if (err) { - return callback(err); - } - - initEmailDao({ - publicKey: publicKey - }); - }); - return; - } - - // either signup was complete or no pubkey is available, so we're good here. - initEmailDao(keys); - }); - } - - function initEmailDao(keys) { - ctrl._emailDao.init({ - account: account - }, function(err) { - if (err) { - return callback(err); - } - - callback(null, keys); - }); - } -}; - -/** - * Check if the user agent is online. - */ -ctrl.isOnline = function() { - return navigator.onLine; -}; - -// TODO: move to AccountService - -/** - * Event handler that is called when the user agent goes offline. - */ -ctrl.onDisconnect = function() { - ctrl._emailDao.onDisconnect(); -}; - -// TODO: move to AccountService - -/** - * Log the current user out by clear the app config store and deleting instances of imap-client and pgp-mailer. - */ -ctrl.logout = function() { - // clear app config store - ctrl._auth.logout(function(err) { - if (err) { - ctrl.onError(err); - return; - } - - // delete instance of imap-client and pgp-mailer - ctrl._emailDao.onDisconnect(function(err) { - if (err) { - ctrl.onError(err); - return; - } - - if (typeof window.chrome !== 'undefined' && chrome.runtime && chrome.runtime.reload) { - // reload chrome app - chrome.runtime.reload(); - } else { - // navigate to login - window.location.href = '/'; - } - }); - }); -}; - -// TODO: move onConnect to Account service - -/** - * Event that is called when the user agent goes online. This create new instances of the imap-client and pgp-mailer and connects to the mail server. - */ -ctrl.onConnect = function(callback) { - if (!ctrl.isOnline() || !ctrl._emailDao || !ctrl._emailDao._account) { - // prevent connection infinite loop - callback(); - return; - } - - ctrl._auth.getCredentials(function(err, credentials) { - if (err) { - callback(err); - return; - } - - initClients(credentials); - }); - - function initClients(credentials) { - // add the maximum update batch size for imap folders to the imap configuration - credentials.imap.maxUpdateSize = config.imapUpdateBatchSize; - - // tls socket worker path for multithreaded tls in non-native tls environments - credentials.imap.tlsWorkerPath = credentials.smtp.tlsWorkerPath = config.workerPath + '/tcp-socket-tls-worker.min.js'; - - var pgpMailer = new PgpMailer(credentials.smtp, ctrl._pgpbuilder); - var imapClient = new ImapClient(credentials.imap); - imapClient.onError = onConnectionError; - pgpMailer.onError = onConnectionError; - - // certificate update handling - imapClient.onCert = ctrl._auth.handleCertificateUpdate.bind(ctrl._auth, 'imap', ctrl.onConnect, ctrl.onError); - pgpMailer.onCert = ctrl._auth.handleCertificateUpdate.bind(ctrl._auth, 'smtp', ctrl.onConnect, ctrl.onError); - - // connect to clients - ctrl._emailDao.onConnect({ - imapClient: imapClient, - pgpMailer: pgpMailer, - ignoreUploadOnSent: ctrl._emailDao.checkIgnoreUploadOnSent(credentials.imap.host) - }, callback); - } - - function onConnectionError(error) { - axe.debug('Connection error. Attempting reconnect in ' + config.reconnectInterval + ' ms. Error: ' + (error.errMsg || error.message) + (error.stack ? ('\n' + error.stack) : '')); - - setTimeout(function() { - axe.debug('Reconnecting...'); - // re-init client modules on error - ctrl.onConnect(function(err) { - if (err) { - axe.error('Reconnect attempt failed! ' + (err.errMsg || err.message) + (err.stack ? ('\n' + err.stack) : '')); - return; - } - - axe.debug('Reconnect attempt complete.'); - }); - }, config.reconnectInterval); - } -}; - -module.exports = ctrl; \ No newline at end of file diff --git a/src/js/app.js b/src/js/app.js index 0b52818..738cefe 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -14,7 +14,8 @@ if (typeof window.applicationCache !== 'undefined') { }; } -var DialogCtrl = require('./controller/dialog'), +var axe = require('axe-logger'), + DialogCtrl = require('./controller/dialog'), AddAccountCtrl = require('./controller/add-account'), CreateAccountCtrl = require('./controller/create-account'), ValidatePhoneCtrl = require('./controller/validate-phone'), @@ -36,13 +37,26 @@ var DialogCtrl = require('./controller/dialog'), ActionBarCtrl = require('./controller/action-bar'), errorUtil = require('./util/error'), backButtonUtil = require('./util/backbutton-handler'); -require('./directive/common'), + +// include angular modules +require('./app-config'); +require('./directive/common'); +require('./util'); +require('./crypto'); require('./service'); +require('./email'); // init main angular module including dependencies var app = angular.module('mail', [ 'ngRoute', 'ngAnimate', + 'ngTagsInput', + 'woAppConfig', + 'woDirectives', + 'woUtil', + 'woCrypto,', + 'woServices', + 'woEmail', 'navigation', 'mail-list', 'write', @@ -50,10 +64,7 @@ var app = angular.module('mail', [ 'contacts', 'login-new-device', 'privatekey-upload', - 'infinite-scroll', - 'ngTagsInput', - 'woDirectives', - 'woServices' + 'infinite-scroll' ]); // set router paths @@ -130,4 +141,25 @@ app.controller('PrivateKeyUploadCtrl', PrivateKeyUploadCtrl); app.controller('ContactsCtrl', ContactsCtrl); app.controller('AboutCtrl', AboutCtrl); app.controller('DialogCtrl', DialogCtrl); -app.controller('ActionBarCtrl', ActionBarCtrl); \ No newline at end of file +app.controller('ActionBarCtrl', ActionBarCtrl); + +// +// Manual angular bootstraping +// + +// 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 + axe.debug('Assuming Cordova environment...'); + document.addEventListener('deviceready', bootstrap, false); +} else { + // No need to wait on events... just start the app + axe.debug('Assuming Browser environment...'); + bootstrap(); +} + +function bootstrap() { + angular.element(document).ready(function() { + angular.bootstrap(document, ['mail']); + }); +} \ No newline at end of file diff --git a/src/js/controller/app/navigation.js b/src/js/controller/app/navigation.js index 1ec4080..5fcc458 100644 --- a/src/js/controller/app/navigation.js +++ b/src/js/controller/app/navigation.js @@ -5,8 +5,7 @@ var appController = require('../app-controller'), backBtnHandler = require('../util/backbutton-handler'), appCfg = require('../app-config'), config = appCfg.config, - str = appCfg.string, - emailDao, outboxBo; + str = appCfg.string; // // Constants @@ -19,14 +18,8 @@ var NOTIFICATION_SENT_TIMEOUT = 2000; // Controller // -var NavigationCtrl = function($scope, $routeParams, $location) { - if (!appController._emailDao && !$routeParams.dev) { - $location.path('/'); // init app - return; - } - - emailDao = appController._emailDao; - outboxBo = appController._outboxBo; +var NavigationCtrl = function($scope, $routeParams, $location, account, email, outbox) { + !$routeParams.dev && !account.isLoggedIn() && $location.path('/'); // init app // // scope functions @@ -51,14 +44,14 @@ var NavigationCtrl = function($scope, $routeParams, $location) { } // update the outbox mail count - var outbox = _.findWhere($scope.account.folders, { + var ob = _.findWhere($scope.account.folders, { type: config.outboxMailboxType }); - outbox.count = count; + ob.count = count; $scope.$apply(); - emailDao.refreshFolder({ - folder: outbox + email.refreshFolder({ + folder: ob }, $scope.onError); }; @@ -114,18 +107,19 @@ var NavigationCtrl = function($scope, $routeParams, $location) { } // get pointer to account/folder/message tree on root scope - $scope.$root.account = emailDao._account; + $scope.$root.account = email._account; + // TODO: $scope.accounts = account.list(); // set notificatio handler for sent messages - outboxBo.onSent = sentNotification; + outbox.onSent = sentNotification; // start checking outbox periodically - outboxBo.startChecking($scope.onOutboxUpdate); + outbox.startChecking($scope.onOutboxUpdate); } - function sentNotification(email) { + function sentNotification(message) { notification.create({ title: 'Message sent', - message: email.subject, + message: message.subject, timeout: NOTIFICATION_SENT_TIMEOUT }, function() {}); } diff --git a/src/js/controller/login/add-account.js b/src/js/controller/login/add-account.js index 28276da..82e2184 100644 --- a/src/js/controller/login/add-account.js +++ b/src/js/controller/login/add-account.js @@ -1,12 +1,7 @@ 'use strict'; -var appCtrl = require('../app-controller'); - -var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig) { - if (!appCtrl._auth && !$routeParams.dev) { - $location.path('/'); // init app - return; - } +var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth) { + !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app $scope.getAccountSettings = function() { if ($scope.form.$invalid) { @@ -25,7 +20,7 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig) { }; var hostname = config.imap.hostname; - if (appCtrl._auth.useOAuth(hostname)) { + if (auth.useOAuth(hostname)) { // check for oauth support $scope.oauthPossible(); } else { @@ -62,7 +57,7 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig) { function getOAuthToken() { // fetches the email address from the chrome identity api - appCtrl._auth.getOAuthToken(function(err) { + auth.getOAuthToken(function(err) { if (err) { return $scope.onError(err); } diff --git a/src/js/controller/login/create-account.js b/src/js/controller/login/create-account.js index 7222d3d..a9b0c8e 100644 --- a/src/js/controller/login/create-account.js +++ b/src/js/controller/login/create-account.js @@ -1,13 +1,7 @@ 'use strict'; -var appCtrl = require('../app-controller'), - cfg = require('../app-config').config; - -var CreateAccountCtrl = function($scope, $location, $routeParams) { - if (!appCtrl._auth && !$routeParams.dev) { - $location.path('/'); // init app - return; - } +var CreateAccountCtrl = function($scope, $location, $routeParams, auth, admin, appConfig) { + !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app $scope.createWhiteoutAccount = function() { if ($scope.form.$invalid) { @@ -17,17 +11,17 @@ var CreateAccountCtrl = function($scope, $location, $routeParams) { $scope.busy = true; $scope.errMsg = undefined; // reset error msg - var emailAddress = $scope.user + '@' + cfg.wmailDomain; + var emailAddress = $scope.user + '@' + appConfig.config.wmailDomain; // set to state for next view - $scope.state.createAccount = { + auth.setCredentials({ emailAddress: emailAddress, - pass: $scope.pass, + password: $scope.pass, realname: $scope.realname - }; + }); // call REST api - appCtrl._adminDao.createUser({ + admin.createUser({ emailAddress: emailAddress, password: $scope.pass, phone: $scope.phone.replace(/\s+/g, ''), // remove spaces from the phone number diff --git a/src/js/controller/login/login-existing.js b/src/js/controller/login/login-existing.js index 24cf496..505f1d2 100644 --- a/src/js/controller/login/login-existing.js +++ b/src/js/controller/login/login-existing.js @@ -1,14 +1,7 @@ 'use strict'; -var appController = require('../app-controller'); - -var LoginExistingCtrl = function($scope, $location, $routeParams) { - if (!appController._emailDao && !$routeParams.dev) { - $location.path('/'); // init app - return; - } - - var emailDao = appController._emailDao; +var LoginExistingCtrl = function($scope, $location, $routeParams, email, auth, keychain) { + !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app $scope.confirmPassphrase = function() { if ($scope.form.$invalid) { @@ -24,14 +17,14 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) { }; function unlockCrypto() { - var userId = emailDao._account.emailAddress; - emailDao._keychain.getUserKeyPair(userId, function(err, keypair) { + var userId = auth.emailAddress; + keychain.getUserKeyPair(userId, function(err, keypair) { if (err) { displayError(err); return; } - emailDao.unlock({ + email.unlock({ keypair: keypair, passphrase: $scope.passphrase }, onUnlock); @@ -44,7 +37,7 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) { return; } - appController._auth.storeCredentials(function(err) { + auth.storeCredentials(function(err) { if (err) { displayError(err); return; diff --git a/src/js/controller/login/login-initial.js b/src/js/controller/login/login-initial.js index ced1bd1..08408d6 100644 --- a/src/js/controller/login/login-initial.js +++ b/src/js/controller/login/login-initial.js @@ -1,17 +1,9 @@ 'use strict'; -var appController = require('../app-controller'); +var LoginInitialCtrl = function($scope, $location, $routeParams, newsletter, email, auth) { + !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app -var LoginInitialCtrl = function($scope, $location, $routeParams, newsletter) { - if (!appController._emailDao && !$routeParams.dev) { - $location.path('/'); // init app - return; - } - - if (appController._emailDao) { - var emailDao = appController._emailDao, - emailAddress = emailDao._account.emailAddress; - } + var emailAddress = auth.emailAddress; var termsMsg = 'You must accept the Terms of Service to continue.', states = { @@ -59,7 +51,7 @@ var LoginInitialCtrl = function($scope, $location, $routeParams, newsletter) { // go to set keygen screen $scope.setState(states.PROCESSING); - emailDao.unlock({ + email.unlock({ passphrase: undefined // generate key without passphrase }, function(err) { if (err) { @@ -67,7 +59,7 @@ var LoginInitialCtrl = function($scope, $location, $routeParams, newsletter) { return; } - appController._auth.storeCredentials(function(err) { + auth.storeCredentials(function(err) { if (err) { displayError(err); return; diff --git a/src/js/controller/login/login-new-device.js b/src/js/controller/login/login-new-device.js index fc24b17..44a3292 100644 --- a/src/js/controller/login/login-new-device.js +++ b/src/js/controller/login/login-new-device.js @@ -1,15 +1,7 @@ 'use strict'; -var appController = require('../app-controller'); - -var LoginExistingCtrl = function($scope, $location, $routeParams) { - if (!appController._emailDao && !$routeParams.dev) { - $location.path('/'); // init app - return; - } - - var emailDao = appController._emailDao, - pgp = appController._pgp; +var LoginExistingCtrl = function($scope, $location, $routeParams, email, auth, pgp, keychain) { + !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app $scope.incorrect = false; @@ -27,9 +19,9 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) { }; function unlockCrypto() { - var userId = emailDao._account.emailAddress; + var userId = auth.emailAddress; // check if user already has a public key on the key server - emailDao._keychain.getUserKeyPair(userId, function(err, keypair) { + keychain.getUserKeyPair(userId, function(err, keypair) { if (err) { $scope.displayError(err); return; @@ -76,7 +68,7 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) { } // import and validate keypair - emailDao.unlock({ + email.unlock({ keypair: keypair, passphrase: $scope.passphrase }, function(err) { @@ -86,7 +78,7 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) { return; } - emailDao._keychain.putUserKeyPair(keypair, onUnlock); + keychain.putUserKeyPair(keypair, onUnlock); }); }); } @@ -97,7 +89,7 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) { return; } - appController._auth.storeCredentials(function(err) { + auth.storeCredentials(function(err) { if (err) { $scope.displayError(err); return; diff --git a/src/js/controller/login/login-privatekey-download.js b/src/js/controller/login/login-privatekey-download.js index e45106c..2d9715b 100644 --- a/src/js/controller/login/login-privatekey-download.js +++ b/src/js/controller/login/login-privatekey-download.js @@ -1,18 +1,7 @@ 'use strict'; -var appController = require('../app-controller'); - -var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams) { - if (!appController._emailDao && !$routeParams.dev) { - $location.path('/'); // init app - return; - } - - if (appController._emailDao) { - var keychain = appController._keychain, - emailDao = appController._emailDao, - userId = emailDao._account.emailAddress; - } +var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams, auth, email, keychain) { + !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app $scope.step = 1; @@ -38,6 +27,7 @@ var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams) { }; $scope.verifyRecoveryToken = function(callback) { + var userId = auth.emailAddress; keychain.getUserKeyPair(userId, function(err, keypair) { if (err) { displayError(err); @@ -95,7 +85,7 @@ var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams) { $scope.cachedKeypair.privateKey = privateKey; // try empty passphrase - emailDao.unlock({ + email.unlock({ keypair: $scope.cachedKeypair, passphrase: undefined }, function(err) { @@ -106,7 +96,7 @@ var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams) { } // passphrase is corrent ... go to main app - appController._auth.storeCredentials(function(err) { + auth.storeCredentials(function(err) { if (err) { displayError(err); return; diff --git a/src/js/controller/login/login-set-credentials.js b/src/js/controller/login/login-set-credentials.js index 795ec37..c5bfbd2 100644 --- a/src/js/controller/login/login-set-credentials.js +++ b/src/js/controller/login/login-set-credentials.js @@ -4,16 +4,8 @@ var ENCRYPTION_METHOD_NONE = 0; var ENCRYPTION_METHOD_STARTTLS = 1; var ENCRYPTION_METHOD_TLS = 2; -var appCtrl = require('../app-controller'); - -var SetCredentialsCtrl = function($scope, $location, $routeParams) { - if (!appCtrl._auth && !$routeParams.dev) { - $location.path('/'); // init app - return; - } - - var auth = appCtrl._auth; - var doctor = appCtrl._doctor; +var SetCredentialsCtrl = function($scope, $location, $routeParams, auth, connectionDoctor) { + !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app // // Presets and Settings @@ -87,11 +79,11 @@ var SetCredentialsCtrl = function($scope, $location, $routeParams) { }; // use the credentials in the connection doctor - doctor.configure(credentials); + connectionDoctor.configure(credentials); // run connection doctor test suite $scope.busy = true; - doctor.check(function(err) { + connectionDoctor.check(function(err) { if (err) { // display the error in the settings UI $scope.connectionError = err; diff --git a/src/js/controller/login/login.js b/src/js/controller/login/login.js index 7087cbc..ed163f9 100644 --- a/src/js/controller/login/login.js +++ b/src/js/controller/login/login.js @@ -1,31 +1,19 @@ 'use strict'; -var appController = require('../app-controller'); +var LoginCtrl = function($scope, $location, updateHandler, account, auth, email, keychain, dialog) { -var LoginCtrl = function($scope, $location) { - - // start main application controller - appController.start({ - onError: $scope.onError - }, function(err) { - if (err) { - $scope.onError(err); - return; - } - - // check for app update - appController.checkForUpdate(); - - initializeUser(); - }); - - // TODO: move to Account service login function + // check for app update + updateHandler.checkForUpdate(); + // initialize the user account + initializeUser(); function initializeUser() { + // init the auth modules + auth.init(); // get OAuth token from chrome - appController._auth.getEmailAddress(function(err, info) { + auth.getEmailAddress(function(err, info) { if (err) { - $scope.onError(err); + dialog.error(err); return; } @@ -35,13 +23,13 @@ var LoginCtrl = function($scope, $location) { return; } - // initiate controller by creating email dao - appController.init({ + // initiate the account by initializing the email dao and user storage + account.init({ emailAddress: info.emailAddress, realname: info.realname }, function(err, availableKeys) { if (err) { - $scope.onError(err); + dialog.error(err); return; } @@ -53,7 +41,7 @@ var LoginCtrl = function($scope, $location) { function redirect(availableKeys) { if (availableKeys && availableKeys.publicKey && availableKeys.privateKey) { // public and private key available, try empty passphrase - appController._emailDao.unlock({ + email.unlock({ keypair: availableKeys, passphrase: undefined }, function(err) { @@ -62,9 +50,9 @@ var LoginCtrl = function($scope, $location) { return; } - appController._auth.storeCredentials(function(err) { + auth.storeCredentials(function(err) { if (err) { - return $scope.onError(err); + return dialog.error(err); } goTo('/desktop'); @@ -72,12 +60,12 @@ var LoginCtrl = function($scope, $location) { }); } else if (availableKeys && availableKeys.publicKey && !availableKeys.privateKey) { // check if private key is synced - appController._keychain.requestPrivateKeyDownload({ + keychain.requestPrivateKeyDownload({ userId: availableKeys.publicKey.userId, keyId: availableKeys.publicKey._id, }, function(err, privateKeySynced) { if (err) { - $scope.onError(err); + dialog.error(err); return; } diff --git a/src/js/controller/login/validate-phone.js b/src/js/controller/login/validate-phone.js index 05c1221..a673416 100644 --- a/src/js/controller/login/validate-phone.js +++ b/src/js/controller/login/validate-phone.js @@ -1,12 +1,7 @@ 'use strict'; -var appCtrl = require('../app-controller'); - -var ValidatePhoneCtrl = function($scope, $location, $routeParams, mailConfig) { - if (!appCtrl._auth && !$routeParams.dev) { - $location.path('/'); // init app - return; - } +var ValidatePhoneCtrl = function($scope, $location, $routeParams, mailConfig, auth, admin) { + !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app // TODO: move to Account service create function @@ -20,8 +15,8 @@ var ValidatePhoneCtrl = function($scope, $location, $routeParams, mailConfig) { $scope.errMsg = undefined; // reset error msg // verify user to REST api - appCtrl._adminDao.validateUser({ - emailAddress: $scope.state.createAccount.emailAddress, + admin.validateUser({ + emailAddress: auth.emailAddress, token: $scope.token.toUpperCase() }, function(err) { if (err) { @@ -37,14 +32,14 @@ var ValidatePhoneCtrl = function($scope, $location, $routeParams, mailConfig) { }; $scope.login = function() { - var address = $scope.state.createAccount.emailAddress; + var address = auth.emailAddress; return mailConfig.get(address).then(function(config) { // store credentials in memory - appCtrl._auth.setCredentials({ - emailAddress: $scope.state.createAccount.emailAddress, - username: $scope.state.createAccount.emailAddress, - realname: $scope.state.createAccount.realname, - password: $scope.state.createAccount.pass, + auth.setCredentials({ + emailAddress: auth.emailAddress, + username: auth.emailAddress, + realname: auth.realname, + password: auth.password, imap: { host: config.imap.hostname, port: parseInt(config.imap.port, 10), diff --git a/src/js/crypto/index.js b/src/js/crypto/index.js index f471282..6a77bcc 100644 --- a/src/js/crypto/index.js +++ b/src/js/crypto/index.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('woCrypto', []); +angular.module('woCrypto', ['woAppConfig', 'woUtil']); require('./pgp'); require('./crypto'); \ No newline at end of file diff --git a/src/js/email/account.js b/src/js/email/account.js index 6b87b47..debca95 100644 --- a/src/js/email/account.js +++ b/src/js/email/account.js @@ -4,41 +4,240 @@ var ngModule = angular.module('woServices'); ngModule.service('account', Account); module.exports = Account; -function Account(email, outbox) { - this._emailDAOs = [email]; - this._outboxes = [outbox]; - this._accounts = undefined; +var axe = require('axe-logger'), + util = require('crypto-lib').util, + PgpMailer = require('pgpmailer'), + ImapClient = require('imap-client'); + +function Account(appConfig, auth, admin, mailConfig, keychain, pgpbuilder, email, outbox, deviceStorage, updateHandler) { + this._appConfig = appConfig; + this._auth = auth; + this._admin = admin; + this._mailConfig = mailConfig; + this._keychain = keychain; + this._emailDao = email; + this._pgpbuilder = pgpbuilder; + this._outbox = outbox; + this._deviceStorage = deviceStorage; + this._updateHandler = updateHandler; + this._accounts = []; // init accounts list } +/** + * Check if the account is already logged in. + * @return {Boolean} if the account is logged in + */ +Account.prototype.isLoggedIn = function() { + return (this._accounts.length > 0); +}; + /** * Lists all of the current accounts connected to the app * @return {Array} The account objects containing folder and message objects */ Account.prototype.list = function() { - this._accounts = this._emailDAOs.map(function(emailDao) { - return emailDao._account; - }); - return this._accounts; }; /** - * Login to an existing email account. This creates a new email data access object instance for that account and logs in via IMAP. - * @param {String} options.emailAddress The account's email address + * Fire up the database, retrieve the available keys for the user and initialize the email data access object */ -Account.prototype.login = function(options) { - var emailDao = new Email(); - this._emailDAOs.push(emailDao); +Account.prototype.init = function(options, callback) { + var self = this; + + // account information for the email dao + var account = { + realname: options.realname, + emailAddress: options.emailAddress, + asymKeySize: this._appConfig.asymKeySize + }; + + // Pre-Flight check: don't even start to initialize stuff if the email address is not valid + if (!util.validateEmailAddress(options.emailAddress)) { + return callback(new Error('The user email address is invalid!')); + } + + prepareDatabase(); + + // Pre-Flight check: initialize and prepare user's local database + function prepareDatabase() { + self._deviceStorage.init(options.emailAddress, function(err) { + if (err) { + return callback(err); + } + + // Migrate the databases if necessary + self._updateHandler.update(function(err) { + if (err) { + return callback(new Error('Updating the internal database failed. Please reinstall the app! Reason: ' + err.message)); + } + + prepareKeys(); + }); + }); + } + + // retrieve keypair fom devicestorage/cloud, refresh public key if signup was incomplete before + function prepareKeys() { + self._keychain.getUserKeyPair(options.emailAddress, function(err, keys) { + if (err) { + return callback(err); + } + + // this is either a first start on a new device, OR a subsequent start without completing the signup, + // since we can't differenciate those cases here, do a public key refresh because it might be outdated + if (keys && keys.publicKey && !keys.privateKey) { + self._keychain.refreshKeyForUserId({ + userId: options.emailAddress, + overridePermission: true + }, function(err, publicKey) { + if (err) { + return callback(err); + } + + initEmailDao({ + publicKey: publicKey + }); + }); + return; + } + + // either signup was complete or no pubkey is available, so we're good here. + initEmailDao(keys); + }); + } + + function initEmailDao(keys) { + self._emailDao.init({ + account: account + }, function(err) { + if (err) { + return callback(err); + } + + // Handle offline and online gracefully ... arm dom event + window.addEventListener('online', self.onConnect.bind(self)); + window.addEventListener('offline', self.onDisconnect.bind(self)); + + // add account object to the accounts array for the ng controllers + self._accounts.push(account); + + callback(null, keys); + }); + } +}; + +/** + * Check if the user agent is online. + */ +Account.prototype.isOnline = function() { + return navigator.onLine; +}; + +/** + * Event that is called when the user agent goes online. This create new instances of the imap-client and pgp-mailer and connects to the mail server. + */ +Account.prototype.onConnect = function(callback) { + var self = this; + var config = self._appConfig.config; + + if (!self.isOnline() || !self._emailDao || !self._emailDao._account) { + // prevent connection infinite loop + callback(); + return; + } + + self._auth.getCredentials(function(err, credentials) { + if (err) { + self._dialog.error(err); + return; + } + + initClients(credentials); + }); + + function initClients(credentials) { + // add the maximum update batch size for imap folders to the imap configuration + credentials.imap.maxUpdateSize = config.imapUpdateBatchSize; + + // tls socket worker path for multithreaded tls in non-native tls environments + credentials.imap.tlsWorkerPath = credentials.smtp.tlsWorkerPath = config.workerPath + '/tcp-socket-tls-worker.min.js'; + + var pgpMailer = new PgpMailer(credentials.smtp, self._pgpbuilder); + var imapClient = new ImapClient(credentials.imap); + imapClient.onError = onConnectionError; + pgpMailer.onError = onConnectionError; + + // certificate update handling + imapClient.onCert = self._auth.handleCertificateUpdate.bind(self._auth, 'imap', self.onConnect, self._dialog.error); + pgpMailer.onCert = self._auth.handleCertificateUpdate.bind(self._auth, 'smtp', self.onConnect, self._dialog.error); + + // connect to clients + self._emailDao.onConnect({ + imapClient: imapClient, + pgpMailer: pgpMailer, + ignoreUploadOnSent: self._emailDao.checkIgnoreUploadOnSent(credentials.imap.host) + }, self._dialog.error); + } + + function onConnectionError(error) { + axe.debug('Connection error. Attempting reconnect in ' + config.reconnectInterval + ' ms. Error: ' + (error.errMsg || error.message) + (error.stack ? ('\n' + error.stack) : '')); + + setTimeout(function() { + axe.debug('Reconnecting...'); + // re-init client modules on error + self.onConnect(function(err) { + if (err) { + axe.error('Reconnect attempt failed! ' + (err.errMsg || err.message) + (err.stack ? ('\n' + err.stack) : '')); + return; + } + + axe.debug('Reconnect attempt complete.'); + }); + }, config.reconnectInterval); + } +}; + +/** + * Event handler that is called when the user agent goes offline. + */ +Account.prototype.onDisconnect = function() { + this._emailDao.onDisconnect(); +}; + +/** + * Logout of an email account. Log the current user out by clear the app config store and deleting instances of imap-client and pgp-mailer. + */ +Account.prototype.logout = function() { + var self = this; + + // clear app config store + self._auth.logout(function(err) { + if (err) { + self._dialog.error(err); + return; + } + + // delete instance of imap-client and pgp-mailer + self._emailDao.onDisconnect(function(err) { + if (err) { + self._dialog.error(err); + return; + } + + if (typeof window.chrome !== 'undefined' && chrome.runtime && chrome.runtime.reload) { + // reload chrome app + chrome.runtime.reload(); + } else { + // navigate to login + window.location.href = '/'; + } + }); + }); }; /** * Create a new whiteout account. This creates a new email data access object instance for that account and logs in via IMAP. * @param {String} options.emailAddress The account's email address */ -Account.prototype.create = function(options) {}; - -/** - * Logout of an email account. This creates a new email data access object instance for that account and logs in via IMAP. - * @param {String} options.emailAddress The account's email address - */ -Account.prototype.logout = function(options) {}; \ No newline at end of file +Account.prototype.create = function() {}; \ No newline at end of file diff --git a/src/js/email/email.js b/src/js/email/email.js index 85b07d8..9bc1631 100644 --- a/src/js/email/email.js +++ b/src/js/email/email.js @@ -50,12 +50,14 @@ var MSG_PART_TYPE_HTML = 'html'; * @param {Object} pgpbuilder Generates and encrypts MIME and SMTP messages * @param {Object} mailreader Parses MIME messages received from IMAP */ -function Email(keychain, pgp, devicestorage, pgpbuilder, mailreader) { +function Email(keychain, pgp, deviceStorage, pgpbuilder, mailreader, dialog) { this._keychain = keychain; this._pgp = pgp; - this._devicestorage = devicestorage; + this._devicestorage = deviceStorage; this._pgpbuilder = pgpbuilder; this._mailreader = mailreader; + + this.onError = dialog.error; } diff --git a/src/js/email/index.js b/src/js/email/index.js index e8c6d5a..1860dff 100644 --- a/src/js/email/index.js +++ b/src/js/email/index.js @@ -1,6 +1,9 @@ 'use strict'; -angular.module('woEmail', []); +angular.module('woEmail', ['woAppConfig', 'woUtil', 'woServices']); +require('./mailreader'); +require('./pgpbuilder'); require('./email'); +require('./outbox'); require('./account'); \ No newline at end of file diff --git a/src/js/email/outbox.js b/src/js/email/outbox.js index a81d06a..b4350d9 100644 --- a/src/js/email/outbox.js +++ b/src/js/email/outbox.js @@ -13,15 +13,15 @@ var util = require('crypto-lib').util, * The local outbox takes care of the emails before they are being sent. * It also checks periodically if there are any mails in the local device storage to be sent. */ -function Outbox(emailDao, keychain, devicestorage) { +function Outbox(email, keychain, deviceStorage) { /** @private */ - this._emailDao = emailDao; + this._emailDao = email; /** @private */ this._keychain = keychain; /** @private */ - this._devicestorage = devicestorage; + this._devicestorage = deviceStorage; /** * Semaphore-esque flag to avoid 'concurrent' calls to _processOutbox when the timeout fires, but a call is still in process. diff --git a/src/js/service/admin.js b/src/js/service/admin.js index 8c3fc13..5a3549b 100644 --- a/src/js/service/admin.js +++ b/src/js/service/admin.js @@ -4,8 +4,9 @@ var ngModule = angular.module('woServices'); ngModule.service('admin', Admin); module.exports = Admin; -function Admin(restDao) { +function Admin(restDao, appConfig) { this._restDao = restDao; + this._restDao.setBaseUri(appConfig.config.adminUrl); } /** diff --git a/src/js/service/app-config-store.js b/src/js/service/app-config-store.js new file mode 100644 index 0000000..a51dc67 --- /dev/null +++ b/src/js/service/app-config-store.js @@ -0,0 +1,15 @@ +'use strict'; + +var ngModule = angular.module('woServices'); +ngModule.service('appConfigStore', AppConfigStore); +module.exports = AppConfigStore; + +/** + * A service for storing app configuration and user credential data locally + */ +function AppConfigStore(lawnchairDAO) { + this._localDbDao = lawnchairDAO; + this._localDbDao.init('app-config'); +} + +// TODO: inherit DeviceStorage service api \ No newline at end of file diff --git a/src/js/service/auth.js b/src/js/service/auth.js index 6cb1863..4150d17 100644 --- a/src/js/service/auth.js +++ b/src/js/service/auth.js @@ -25,12 +25,26 @@ var SMTP_DB_KEY = 'smtp'; * auth.getCredentials(...); // called to gather all the information to connect to IMAP/SMTP, * username, password / oauth token, IMAP/SMTP server host names, ... */ -function Auth(deviceStorage, oauth, pgp) { - this._appConfigStore = deviceStorage; +function Auth(appConfigStore, oauth, pgp) { + this._appConfigStore = appConfigStore; this._oauth = oauth; this._pgp = pgp; } +/** + * Initialize the service + */ +Auth.prototype.init = function() { + this._initialized = true; +}; + +/** + * Check if the service has been initialized. + */ +Auth.prototype.isInitialized = function() { + return this._initialized; +}; + /** * Retrieves credentials and IMAP/SMTP settings: * 1) Fetches the credentials from disk, then... diff --git a/src/js/service/devicestorage.js b/src/js/service/devicestorage.js index b457e15..2296e10 100644 --- a/src/js/service/devicestorage.js +++ b/src/js/service/devicestorage.js @@ -1,22 +1,18 @@ 'use strict'; var ngModule = angular.module('woServices'); -ngModule.factory('deviceStorage', ['lawnchairDAO', - function(lawnchairDAO) { - return new DeviceStorage(lawnchairDAO); - } -]); +ngModule.service('deviceStorage', DeviceStorage); module.exports = DeviceStorage; /** - * High level storage api that handles all persistence on the device. + * High level storage api that handles all persistence of a user's data on the device. */ function DeviceStorage(lawnchairDAO) { this._localDbDao = lawnchairDAO; } -DeviceStorage.prototype.init = function(dbName, callback) { - this._localDbDao.init(dbName, callback); +DeviceStorage.prototype.init = function(dbName) { + this._localDbDao.init(dbName); }; /** diff --git a/src/js/service/index.js b/src/js/service/index.js index bbe35ab..7e18939 100644 --- a/src/js/service/index.js +++ b/src/js/service/index.js @@ -1,8 +1,17 @@ 'use strict'; -angular.module('woServices', []); +angular.module('woServices', ['woAppConfig', 'woUtil', 'woCrypto']); -require('./newsletter'); +require('./rest'); +require('./invitation'); require('./mail-config'); -require('./account'); -require('.pgpbuilder'); \ No newline at end of file +require('./newsletter'); +require('./oauth'); +require('./privatekey'); +require('./publickey'); +require('./admin'); +require('./lawnchair'); +require('./devicestorage'); +require('./app-config-store'); +require('./auth'); +require('./keychain'); \ No newline at end of file diff --git a/src/js/service/invitation.js b/src/js/service/invitation.js index 9a34211..75916cd 100644 --- a/src/js/service/invitation.js +++ b/src/js/service/invitation.js @@ -4,15 +4,13 @@ var ngModule = angular.module('woServices'); ngModule.service('invitation', Invitation); module.exports = Invitation; -var config = require('../app-config').config; - /** * The Invitation is a high level Data Access Object that access the invitation service REST endpoint. * @param {Object} restDao The REST Data Access Object abstraction */ -function Invitation(restDao) { +function Invitation(restDao, appConfig) { this._restDao = restDao; - this._restDao.setBaseUri(config.cloudUrl); + this._restDao.setBaseUri(appConfig.config.cloudUrl); } // diff --git a/src/js/service/keychain.js b/src/js/service/keychain.js index e284893..28b9405 100644 --- a/src/js/service/keychain.js +++ b/src/js/service/keychain.js @@ -16,18 +16,40 @@ var DB_PUBLICKEY = 'publickey', * A high-level Data-Access Api for handling Keypair synchronization * between the cloud service and the device's local storage */ -function Keychain(localDbDao, publicKeyDao, privateKeyDao, crypto, pgp) { - this._localDbDao = localDbDao; - this._publicKeyDao = publicKeyDao; - this._privateKeyDao = privateKeyDao; +function Keychain(lawnchairDAO, publicKey, privateKey, crypto, pgp, dialog, appConfig) { + this._lawnchairDAO = lawnchairDAO; + this._publicKeyDao = publicKey; + this._privateKeyDao = privateKey; this._crypto = crypto; this._pgp = pgp; + this._dialog = dialog; + this._appConfig = appConfig; } // // Public key functions // +/** + * Display confirmation dialog to request a public key update + * @param {Object} params.newKey The user's updated public key object + * @param {String} params.userId The user's email address + */ +Keychain.prototype.requestPermissionForKeyUpdate = function(params, callback) { + var str = this._appConfig.string; + var message = params.newKey ? str.updatePublicKeyMsgNewKey : str.updatePublicKeyMsgRemovedKey; + message = message.replace('{0}', params.userId); + + this._dialog.confirm({ + title: str.updatePublicKeyTitle, + message: message, + positiveBtnStr: str.updatePublicKeyPosBtn, + negativeBtnStr: str.updatePublicKeyNegBtn, + showNegativeBtn: true, + callback: callback + }); +}; + /** * Verifies the public key of a user o nthe public key store * @param {String} uuid The uuid to verify the key @@ -196,7 +218,7 @@ Keychain.prototype.getReceiverPublicKey = function(userId, callback) { var self = this; // search local keyring for public key - self._localDbDao.list(DB_PUBLICKEY, 0, null, function(err, allPubkeys) { + self._lawnchairDAO.list(DB_PUBLICKEY, 0, null, function(err, allPubkeys) { if (err) { callback(err); return; @@ -275,7 +297,7 @@ Keychain.prototype.setDeviceName = function(deviceName, callback) { return; } - this._localDbDao.persist(DB_DEVICENAME, deviceName, callback); + this._lawnchairDAO.persist(DB_DEVICENAME, deviceName, callback); }; /** @@ -285,7 +307,7 @@ Keychain.prototype.setDeviceName = function(deviceName, callback) { */ Keychain.prototype.getDeviceName = function(callback) { // check if deviceName is already persisted in storage - this._localDbDao.read(DB_DEVICENAME, function(err, deviceName) { + this._lawnchairDAO.read(DB_DEVICENAME, function(err, deviceName) { if (err) { callback(err); return; @@ -308,7 +330,7 @@ Keychain.prototype.getDeviceSecret = function(callback) { var self = this; // generate random deviceSecret or get from storage - self._localDbDao.read(DB_DEVICE_SECRET, function(err, storedDevSecret) { + self._lawnchairDAO.read(DB_DEVICE_SECRET, function(err, storedDevSecret) { if (err) { callback(err); return; @@ -323,7 +345,7 @@ Keychain.prototype.getDeviceSecret = function(callback) { // generate random deviceSecret var deviceSecret = util.random(config.symKeySize); // persist deviceSecret to local storage (in plaintext) - self._localDbDao.persist(DB_DEVICE_SECRET, deviceSecret, function(err) { + self._lawnchairDAO.persist(DB_DEVICE_SECRET, deviceSecret, function(err) { if (err) { callback(err); return; @@ -763,7 +785,7 @@ Keychain.prototype.getUserKeyPair = function(userId, callback) { var self = this; // search for user's public key locally - self._localDbDao.list(DB_PUBLICKEY, 0, null, function(err, allPubkeys) { + self._lawnchairDAO.list(DB_PUBLICKEY, 0, null, function(err, allPubkeys) { if (err) { callback(err); return; @@ -886,7 +908,7 @@ Keychain.prototype.lookupPublicKey = function(id, callback) { } // lookup in local storage - self._localDbDao.read(DB_PUBLICKEY + '_' + id, function(err, pubkey) { + self._lawnchairDAO.read(DB_PUBLICKEY + '_' + id, function(err, pubkey) { if (err) { callback(err); return; @@ -922,26 +944,26 @@ Keychain.prototype.lookupPublicKey = function(id, callback) { */ Keychain.prototype.listLocalPublicKeys = function(callback) { // search local keyring for public key - this._localDbDao.list(DB_PUBLICKEY, 0, null, callback); + this._lawnchairDAO.list(DB_PUBLICKEY, 0, null, callback); }; Keychain.prototype.removeLocalPublicKey = function(id, callback) { - this._localDbDao.remove(DB_PUBLICKEY + '_' + id, callback); + this._lawnchairDAO.remove(DB_PUBLICKEY + '_' + id, callback); }; Keychain.prototype.lookupPrivateKey = function(id, callback) { // lookup in local storage - this._localDbDao.read(DB_PRIVATEKEY + '_' + id, callback); + this._lawnchairDAO.read(DB_PRIVATEKEY + '_' + id, callback); }; Keychain.prototype.saveLocalPublicKey = function(pubkey, callback) { // persist public key (email, _id) var pkLookupKey = DB_PUBLICKEY + '_' + pubkey._id; - this._localDbDao.persist(pkLookupKey, pubkey, callback); + this._lawnchairDAO.persist(pkLookupKey, pubkey, callback); }; Keychain.prototype.saveLocalPrivateKey = function(privkey, callback) { // persist private key (email, _id) var prkLookupKey = DB_PRIVATEKEY + '_' + privkey._id; - this._localDbDao.persist(prkLookupKey, privkey, callback); + this._lawnchairDAO.persist(prkLookupKey, privkey, callback); }; \ No newline at end of file diff --git a/src/js/service/lawnchair.js b/src/js/service/lawnchair.js index fcdcee3..4d5e07b 100644 --- a/src/js/service/lawnchair.js +++ b/src/js/service/lawnchair.js @@ -15,25 +15,13 @@ function LawnchairDAO() {} * Initialize the lawnchair database * @param {String} dbName The name of the database */ -LawnchairDAO.prototype.init = function(dbName, callback) { +LawnchairDAO.prototype.init = function(dbName) { if (!dbName) { - callback({ - errMsg: 'Lawnchair DB name must be specified!' - }); - return; + throw new Error('Lawnchair DB name must be specified!'); } this._db = new Lawnchair({ name: dbName - }, function(lc) { - if (!lc) { - callback({ - errMsg: 'Lawnchair init failed!' - }); - return; - } - - callback(); }); }; diff --git a/src/js/service/privatekey.js b/src/js/service/privatekey.js index 0511506..5f00ee5 100644 --- a/src/js/service/privatekey.js +++ b/src/js/service/privatekey.js @@ -4,11 +4,9 @@ var ngModule = angular.module('woServices'); ngModule.service('privateKey', PrivateKey); module.exports = PrivateKey; -var config = require('../app-config').config; - -function PrivateKey(restDao) { +function PrivateKey(restDao, appConfig) { this._restDao = restDao; - this._restDao.setBaseUri(config.privkeyServerUrl); + this._restDao.setBaseUri(appConfig.config.privkeyServerUrl); } // diff --git a/src/js/service/publickey.js b/src/js/service/publickey.js index f2b9736..e162138 100644 --- a/src/js/service/publickey.js +++ b/src/js/service/publickey.js @@ -4,11 +4,9 @@ var ngModule = angular.module('woServices'); ngModule.service('publicKey', PublicKey); module.exports = PublicKey; -var config = require('../app-config').config; - -function PublicKey(restDao) { +function PublicKey(restDao, appConfig) { this._restDao = restDao; - this._restDao.setBaseUri(config.cloudUrl); + this._restDao.setBaseUri(appConfig.config.cloudUrl); } /** diff --git a/src/js/util/update/update-handler.js b/src/js/util/update/update-handler.js index e72fccb..ebfdd11 100644 --- a/src/js/util/update/update-handler.js +++ b/src/js/util/update/update-handler.js @@ -1,7 +1,7 @@ 'use strict'; var ngModule = angular.module('woUtil'); -ngModule.service('updateHandler', ['deviceStorage', 'deviceStorage', 'auth', UpdateHandler]); +ngModule.service('updateHandler', UpdateHandler); module.exports = UpdateHandler; var axe = require('axe-logger'), @@ -15,11 +15,12 @@ var axe = require('axe-logger'), /** * Handles database migration */ -function UpdateHandler(appConfigStorage, userStorage, auth) { - this._appConfigStorage = appConfigStorage; - this._userStorage = userStorage; +function UpdateHandler(appConfigStore, deviceStorage, auth, dialog) { + this._appConfigStorage = appConfigStore; + this._userStorage = deviceStorage; this._updateScripts = [updateV1, updateV2, updateV3, updateV4, updateV5]; this._auth = auth; + this._dialog = dialog; } /** @@ -99,14 +100,16 @@ UpdateHandler.prototype._applyUpdate = function(options, callback) { /** * Check application version and update correspondingly */ -UpdateHandler.prototype.checkForUpdate = function(dialog) { +UpdateHandler.prototype.checkForUpdate = function() { + var self = this; + // Chrome Packaged App if (typeof window.chrome !== 'undefined' && chrome.runtime && chrome.runtime.onUpdateAvailable) { // check for Chrome app update and restart chrome.runtime.onUpdateAvailable.addListener(function(details) { axe.debug('New Chrome App update... requesting reload.'); // Chrome downloaded a new app version - dialog({ + self._dialog.confirm({ title: 'Update available', message: 'A new version ' + details.version + ' of the app is available. Restart the app to update?', positiveBtnStr: 'Restart', From da5a9e2c17f9e214c132c0900a4a137fc0179352 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Thu, 20 Nov 2014 15:14:39 +0100 Subject: [PATCH 14/40] Refactor app controller to use new services --- src/js/app.js | 8 +- src/js/controller/app/about.js | 10 +-- src/js/controller/app/account.js | 21 ++--- src/js/controller/app/action-bar.js | 47 +++++------ src/js/controller/app/contacts.js | 17 ++-- src/js/controller/app/dialog.js | 65 +++++++++++++-- src/js/controller/app/mail-list.js | 96 +++++++++------------- src/js/controller/app/navigation.js | 27 +++--- src/js/controller/app/privatekey-upload.js | 25 +++--- src/js/controller/app/read.js | 33 +++----- src/js/controller/app/set-passphrase.js | 25 ++---- src/js/controller/app/status-display.js | 20 +++++ src/js/controller/app/write.js | 50 +++++------ src/js/controller/login/add-account.js | 6 +- src/js/email/account.js | 6 +- src/js/email/email.js | 4 +- src/js/email/outbox.js | 4 +- src/js/service/app-config-store.js | 15 ---- src/js/service/devicestorage.js | 25 ++++-- src/js/service/index.js | 1 - src/js/util/dialog.js | 12 ++- src/js/util/download.js | 18 ++-- src/js/util/error.js | 33 -------- src/js/util/index.js | 5 +- src/js/util/notification.js | 28 ++++--- src/js/util/status-display.js | 24 ++++++ src/tpl/dialog.html | 10 +-- src/tpl/mail-list.html | 8 +- src/tpl/nav.html | 6 +- 29 files changed, 322 insertions(+), 327 deletions(-) create mode 100644 src/js/controller/app/status-display.js delete mode 100644 src/js/service/app-config-store.js delete mode 100644 src/js/util/error.js create mode 100644 src/js/util/status-display.js diff --git a/src/js/app.js b/src/js/app.js index 738cefe..fae8d00 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -35,8 +35,8 @@ var axe = require('axe-logger'), WriteCtrl = require('./controller/write'), NavigationCtrl = require('./controller/navigation'), ActionBarCtrl = require('./controller/action-bar'), - errorUtil = require('./util/error'), - backButtonUtil = require('./util/backbutton-handler'); + backButtonUtil = require('./util/backbutton-handler'), + StatusDisplayCtrl = require('./controller/app/status-display'); // include angular modules require('./app-config'); @@ -121,9 +121,6 @@ app.run(function($rootScope) { // global state... inherited to all child scopes $rootScope.state = {}; - // attach global error handler - errorUtil.attachHandler($rootScope); - // attach the back button handler to the root scope backButtonUtil.attachHandler($rootScope); @@ -142,6 +139,7 @@ app.controller('ContactsCtrl', ContactsCtrl); app.controller('AboutCtrl', AboutCtrl); app.controller('DialogCtrl', DialogCtrl); app.controller('ActionBarCtrl', ActionBarCtrl); +app.controller('StatusDisplayCtrl', StatusDisplayCtrl); // // Manual angular bootstraping diff --git a/src/js/controller/app/about.js b/src/js/controller/app/about.js index ce627b0..b5f1269 100644 --- a/src/js/controller/app/about.js +++ b/src/js/controller/app/about.js @@ -1,12 +1,6 @@ 'use strict'; -var cfg = require('../app-config').config; - -// -// Controller -// - -var AboutCtrl = function($scope) { +var AboutCtrl = function($scope, appConfig) { $scope.state.about = { toggle: function(to) { @@ -18,7 +12,7 @@ var AboutCtrl = function($scope) { // scope variables // - $scope.version = cfg.appVersion + ' (beta)'; + $scope.version = appConfig.config.appVersion + ' (beta)'; $scope.date = new Date(); // diff --git a/src/js/controller/app/account.js b/src/js/controller/app/account.js index ba1a46b..1457c9b 100644 --- a/src/js/controller/app/account.js +++ b/src/js/controller/app/account.js @@ -1,18 +1,7 @@ 'use strict'; -var appController = require('../app-controller'), - dl = require('../util/download'), - config = require('../app-config').config, - pgp, keychain, userId; - -// -// Controller -// - -var AccountCtrl = function($scope) { - userId = appController._emailDao._account.emailAddress; - keychain = appController._keychain; - pgp = appController._pgp; +var AccountCtrl = function($scope, auth, keychain, pgp, appConfig, download, dialog) { + var userId = auth.emailAddress; $scope.state.account = { toggle: function(to) { @@ -31,7 +20,7 @@ var AccountCtrl = function($scope) { var fpr = keyParams.fingerprint; $scope.fingerprint = fpr.slice(0, 4) + ' ' + fpr.slice(4, 8) + ' ' + fpr.slice(8, 12) + ' ' + fpr.slice(12, 16) + ' ' + fpr.slice(16, 20) + ' ' + fpr.slice(20, 24) + ' ' + fpr.slice(24, 28) + ' ' + fpr.slice(28, 32) + ' ' + fpr.slice(32, 36) + ' ' + fpr.slice(36); $scope.keysize = keyParams.bitSize; - $scope.publicKeyUrl = config.cloudUrl + '/' + userId; + $scope.publicKeyUrl = appConfig.config.cloudUrl + '/' + userId; // // scope functions @@ -40,14 +29,14 @@ var AccountCtrl = function($scope) { $scope.exportKeyFile = function() { keychain.getUserKeyPair(userId, function(err, keys) { if (err) { - $scope.onError(err); + dialog.error(err); return; } var keyId = keys.publicKey._id; var file = 'whiteout_mail_' + userId + '_' + keyId.substring(8, keyId.length); - dl.createDownload({ + download.createDownload({ content: keys.publicKey.publicKey + '\r\n' + keys.privateKey.encryptedKey, filename: file + '.asc', contentType: 'text/plain' diff --git a/src/js/controller/app/action-bar.js b/src/js/controller/app/action-bar.js index d1d6aa3..dac48a8 100644 --- a/src/js/controller/app/action-bar.js +++ b/src/js/controller/app/action-bar.js @@ -1,15 +1,6 @@ 'use strict'; -var appController = require('../app-controller'), - emailDao; - -// -// Controller -// - -var ActionBarCtrl = function($scope) { - - emailDao = appController._emailDao; +var ActionBarCtrl = function($scope, email, dialog, statusDisplay) { /** * Move a single message from the currently selected folder to another folder @@ -24,9 +15,9 @@ var ActionBarCtrl = function($scope) { // close read state $scope.state.read.open = false; - $scope.state.mailList.updateStatus('Moving message...'); + statusDisplay.update('Moving message...'); - emailDao.moveMessage({ + email.moveMessage({ folder: currentFolder(), destination: destination, message: message @@ -35,14 +26,14 @@ var ActionBarCtrl = function($scope) { // show errors where appropriate if (err.code === 42) { $scope.select(message); - $scope.state.mailList.updateStatus('Unable to move message in offline mode!'); + statusDisplay.update('Unable to move message in offline mode!'); return; } - $scope.state.mailList.updateStatus('Error during move!'); - $scope.onError(err); + statusDisplay.update('Error during move!'); + dialog.error(err); return; } - $scope.state.mailList.updateStatus('Message moved.'); + statusDisplay.update('Message moved.'); $scope.$apply(); }); }; @@ -70,9 +61,9 @@ var ActionBarCtrl = function($scope) { // close read state $scope.state.read.open = false; - $scope.state.mailList.updateStatus('Deleting message...'); + statusDisplay.update('Deleting message...'); - emailDao.deleteMessage({ + email.deleteMessage({ folder: currentFolder(), message: message }, function(err) { @@ -80,14 +71,14 @@ var ActionBarCtrl = function($scope) { // show errors where appropriate if (err.code === 42) { $scope.select(message); - $scope.state.mailList.updateStatus('Unable to delete message in offline mode!'); + statusDisplay.update('Unable to delete message in offline mode!'); return; } - $scope.state.mailList.updateStatus('Error during delete!'); - $scope.onError(err); + statusDisplay.update('Error during delete!'); + dialog.error(err); return; } - $scope.state.mailList.updateStatus('Message deleted.'); + statusDisplay.update('Message deleted.'); $scope.$apply(); }); }; @@ -110,31 +101,31 @@ var ActionBarCtrl = function($scope) { return; } - $scope.state.mailList.updateStatus('Updating unread flag...'); + statusDisplay.update('Updating unread flag...'); // close read state $scope.state.read.open = false; var originalState = message.unread; message.unread = unread; - emailDao.setFlags({ + email.setFlags({ folder: currentFolder(), message: message }, function(err) { if (err && err.code === 42) { // offline, restore message.unread = originalState; - $scope.state.mailList.updateStatus('Unable to mark message in offline mode!'); + statusDisplay.update('Unable to mark message in offline mode!'); return; } if (err) { - $scope.state.mailList.updateStatus('Error on sync!'); - $scope.onError(err); + statusDisplay.update('Error on sync!'); + dialog.error(err); return; } - $scope.state.mailList.updateStatus('Online'); + statusDisplay.update('Online'); $scope.$apply(); }); }; diff --git a/src/js/controller/app/contacts.js b/src/js/controller/app/contacts.js index 4eb604d..d5ed11f 100644 --- a/src/js/controller/app/contacts.js +++ b/src/js/controller/app/contacts.js @@ -1,15 +1,10 @@ 'use strict'; -var appController = require('../app-controller'), - keychain, pgp; - // // Controller // -var ContactsCtrl = function($scope) { - keychain = appController._keychain, - pgp = appController._pgp; +var ContactsCtrl = function($scope, keychain, pgp, dialog) { $scope.state.contacts = { toggle: function(to) { @@ -26,7 +21,7 @@ var ContactsCtrl = function($scope) { $scope.listKeys = function() { keychain.listLocalPublicKeys(function(err, keys) { if (err) { - $scope.onError(err); + dialog.error(err); return; } @@ -54,7 +49,7 @@ var ContactsCtrl = function($scope) { // verifiy public key string if (publicKeyArmored.indexOf('-----BEGIN PGP PUBLIC KEY BLOCK-----') < 0) { - $scope.onError({ + dialog.error({ showBugReporter: false, message: 'Invalid public key!' }); @@ -64,7 +59,7 @@ var ContactsCtrl = function($scope) { try { keyParams = pgp.getKeyParams(publicKeyArmored); } catch (e) { - $scope.onError(new Error('Error reading public key params!')); + dialog.error(new Error('Error reading public key params!')); return; } @@ -78,7 +73,7 @@ var ContactsCtrl = function($scope) { keychain.saveLocalPublicKey(pubkey, function(err) { if (err) { - $scope.onError(err); + dialog.error(err); return; } @@ -90,7 +85,7 @@ var ContactsCtrl = function($scope) { $scope.removeKey = function(key) { keychain.removeLocalPublicKey(key._id, function(err) { if (err) { - $scope.onError(err); + dialog.error(err); return; } diff --git a/src/js/controller/app/dialog.js b/src/js/controller/app/dialog.js index 8e33e22..34ec12f 100644 --- a/src/js/controller/app/dialog.js +++ b/src/js/controller/app/dialog.js @@ -1,13 +1,66 @@ 'use strict'; -var DialogCtrl = function($scope) { - $scope.confirm = function(ok) { - $scope.state.dialog.open = false; +var axe = require('axe-logger'); - if ($scope.state.dialog.callback) { - $scope.state.dialog.callback(ok); +var DialogCtrl = function($scope, $q, dialog) { + + var callback; + + // + // Set dialog disply functions + // + + dialog.displayInfo = function(options) { + return $q(function(resolve) { + setOptions(options); + resolve(); + }); + }; + + dialog.displayError = function(options) { + return $q(function(resolve) { + if (options) { + axe.error((options.errMsg || options.message) + (options.stack ? ('\n' + options.stack) : '')); + + setOptions(options); + $scope.title = options.title || 'Error'; + $scope.showBugReporter = (typeof options.showBugReporter !== 'undefined' ? options.showBugReporter : !options.title); // if title is set, presume it's not an error by default + } + + resolve(); + }); + }; + + dialog.displayConfirm = function(options) { + return $q(function(resolve) { + setOptions(options); + resolve(); + }); + }; + + function setOptions(options) { + $scope.open = true; + $scope.title = options.title; + $scope.message = options.errMsg || options.message; + $scope.faqLink = options.faqLink; + $scope.positiveBtnStr = options.positiveBtnStr || 'Ok'; + $scope.negativeBtnStr = options.negativeBtnStr || 'Cancel'; + $scope.showNegativeBtn = options.showNegativeBtn || false; + + callback = options.callback; + } + + // + // Scope functions + // + + $scope.confirm = function(ok) { + $scope.open = false; + + if (callback) { + callback(ok); } - $scope.state.dialog.callback = undefined; + callback = undefined; }; }; diff --git a/src/js/controller/app/mail-list.js b/src/js/controller/app/mail-list.js index 968238c..8b50893 100644 --- a/src/js/controller/app/mail-list.js +++ b/src/js/controller/app/mail-list.js @@ -1,8 +1,6 @@ 'use strict'; -var appController = require('../app-controller'), - notification = require('../util/notification'), - emailDao, outboxBo, keychainDao, searchTimeout, firstSelect; +var searchTimeout, firstSelect; // // Constants @@ -13,15 +11,11 @@ var INIT_DISPLAY_LEN = 20, FOLDER_TYPE_INBOX = 'Inbox', NOTIFICATION_INBOX_TIMEOUT = 5000; -var MailListCtrl = function($scope, $routeParams) { +var MailListCtrl = function($scope, $routeParams, statusDisplay, notification, email, keychain, dialog) { // // Init // - emailDao = appController._emailDao; - outboxBo = appController._outboxBo; - keychainDao = appController._keychain; - /** * Gathers unread notifications to be cancelled later */ @@ -31,39 +25,39 @@ var MailListCtrl = function($scope, $routeParams) { // scope functions // - $scope.getBody = function(email) { - emailDao.getBody({ + $scope.getBody = function(message) { + email.getBody({ folder: currentFolder(), - message: email + message: message }, function(err) { if (err && err.code !== 42) { - $scope.onError(err); + dialog.error(err); return; } // display fetched body $scope.$digest(); - // automatically decrypt if it's the selected email - if (email === currentMessage()) { - emailDao.decryptBody({ - message: email - }, $scope.onError); + // automatically decrypt if it's the selected message + if (message === currentMessage()) { + email.decryptBody({ + message: message + }, dialog.error); } }); }; /** - * Called when clicking on an email list item + * Called when clicking on an message list item */ - $scope.select = function(email) { + $scope.select = function(message) { // unselect an item - if (!email) { + if (!message) { $scope.state.mailList.selected = undefined; return; } - $scope.state.mailList.selected = email; + $scope.state.mailList.selected = message; if (!firstSelect) { // only toggle to read view on 2nd select in mobile mode @@ -71,22 +65,22 @@ var MailListCtrl = function($scope, $routeParams) { } firstSelect = false; - keychainDao.refreshKeyForUserId({ - userId: email.from[0].address + keychain.refreshKeyForUserId({ + userId: message.from[0].address }, onKeyRefreshed); function onKeyRefreshed(err) { if (err) { - $scope.onError(err); + dialog.error(err); } - emailDao.decryptBody({ - message: email - }, $scope.onError); + email.decryptBody({ + message: message + }, dialog.error); - // if the email is unread, please sync the new state. + // if the message is unread, please sync the new state. // otherweise forget about it. - if (!email.unread) { + if (!message.unread) { return; } @@ -97,21 +91,16 @@ var MailListCtrl = function($scope, $routeParams) { } } - $scope.state.actionBar.markMessage(email, false); + $scope.state.actionBar.markMessage(message, false); } }; - // share local scope functions with root state - $scope.state.mailList = { - updateStatus: updateStatus - }; - // // watch tasks // /** - * List emails from folder when user changes folder + * List messages from folder when user changes folder */ $scope._stopWatchTask = $scope.$watch('state.nav.currentFolder', function() { if (!currentFolder()) { @@ -123,7 +112,7 @@ var MailListCtrl = function($scope, $routeParams) { // in development, display dummy mail objects if ($routeParams.dev) { - updateStatus('Last update: ', new Date()); + statusDisplay.update('Last update: ', new Date()); currentFolder().messages = createDummyMails(); return; } @@ -183,20 +172,20 @@ var MailListCtrl = function($scope, $routeParams) { if (!searchText) { // set display buffer to first messages $scope.displayMessages = currentFolder().messages.slice(0, INIT_DISPLAY_LEN); - setSearching(false); - updateStatus('Online'); + statusDisplay.setSearching(false); + statusDisplay.update('Online'); return; } // display searching spinner - setSearching(true); - updateStatus('Searching ...'); + statusDisplay.setSearching(true); + statusDisplay.update('Searching ...'); searchTimeout = setTimeout(function() { $scope.$apply(function() { // filter relevant messages $scope.displayMessages = $scope.search(currentFolder().messages, searchText); - setSearching(false); - updateStatus('Matches in this folder'); + statusDisplay.setSearching(false); + statusDisplay.update('Matches in this folder'); }); }, 500); }; @@ -276,10 +265,10 @@ var MailListCtrl = function($scope, $routeParams) { */ $scope.watchOnline = $scope.$watch('account.online', function(isOnline) { if (isOnline) { - updateStatus('Online'); + statusDisplay.update('Online'); openCurrentFolder(); } else { - updateStatus('Offline mode'); + statusDisplay.update('Offline mode'); } }, true); @@ -292,7 +281,7 @@ var MailListCtrl = function($scope, $routeParams) { return; } - emailDao.openFolder({ + email.openFolder({ folder: currentFolder() }, function(error) { // dont wait until scroll to load visible mail bodies @@ -302,19 +291,10 @@ var MailListCtrl = function($scope, $routeParams) { if (error && error.code === 42) { return; } - $scope.onError(error); + dialog.error(error); }); } - function updateStatus(lbl, time) { - $scope.state.mailList.lastUpdateLbl = lbl; - $scope.state.mailList.lastUpdate = (time) ? time : ''; - } - - function setSearching(state) { - $scope.state.mailList.searching = state; - } - function currentFolder() { return $scope.state.nav.currentFolder; } @@ -327,7 +307,7 @@ var MailListCtrl = function($scope, $routeParams) { // Notification API // - (emailDao || {}).onIncomingMessage = function(msgs) { + (email || {}).onIncomingMessage = function(msgs) { var note, title, message, unreadMsgs; unreadMsgs = msgs.filter(function(msg) { @@ -402,7 +382,7 @@ ngModule.directive('listScroll', function() { } for (var i = 0, len = listItems.length; i < len; i++) { - // the n-th list item (the dom representation of an email) corresponds to + // the n-th list item (the dom representation of an message) corresponds to // the n-th message model in the filteredMessages array listItem = listItems.item(i).getBoundingClientRect(); diff --git a/src/js/controller/app/navigation.js b/src/js/controller/app/navigation.js index 5fcc458..ceb9d8b 100644 --- a/src/js/controller/app/navigation.js +++ b/src/js/controller/app/navigation.js @@ -1,11 +1,6 @@ 'use strict'; -var appController = require('../app-controller'), - notification = require('../util/notification'), - backBtnHandler = require('../util/backbutton-handler'), - appCfg = require('../app-config'), - config = appCfg.config, - str = appCfg.string; +var backBtnHandler = require('../util/backbutton-handler'); // // Constants @@ -18,9 +13,12 @@ var NOTIFICATION_SENT_TIMEOUT = 2000; // Controller // -var NavigationCtrl = function($scope, $routeParams, $location, account, email, outbox) { +var NavigationCtrl = function($scope, $routeParams, $location, account, email, outbox, notification, appConfig, dialog) { !$routeParams.dev && !account.isLoggedIn() && $location.path('/'); // init app + var str = appConfig.string, + config = appConfig.config; + // // scope functions // @@ -39,7 +37,7 @@ var NavigationCtrl = function($scope, $routeParams, $location, account, email, o $scope.onOutboxUpdate = function(err, count) { if (err) { - $scope.onError(err); + dialog.error(err); return; } @@ -52,19 +50,18 @@ var NavigationCtrl = function($scope, $routeParams, $location, account, email, o email.refreshFolder({ folder: ob - }, $scope.onError); + }, dialog.error); }; $scope.logout = function() { - $scope.onError({ + dialog.confirm({ title: str.logoutTitle, message: str.logoutMessage, callback: function(confirm) { if (confirm) { - appController.logout(); + account.logout(); } - }, - sync: true + } }); }; @@ -82,9 +79,9 @@ var NavigationCtrl = function($scope, $routeParams, $location, account, email, o $scope.openFolder($scope.account.folders[0]); } // connect imap/smtp clients on first startup - appController.onConnect(function(err) { + account.onConnect(function(err) { if (err) { - $scope.onError(err); + dialog.error(err); return; } diff --git a/src/js/controller/app/privatekey-upload.js b/src/js/controller/app/privatekey-upload.js index 5a77c72..779a101 100644 --- a/src/js/controller/app/privatekey-upload.js +++ b/src/js/controller/app/privatekey-upload.js @@ -1,12 +1,8 @@ 'use strict'; -var appController = require('../app-controller'), - util = require('crypto-lib').util, - keychain, pgp; +var util = require('crypto-lib').util; -var PrivateKeyUploadCtrl = function($scope) { - keychain = appController._keychain; - pgp = keychain._pgp; +var PrivateKeyUploadCtrl = function($scope, keychain, pgp, dialog, auth) { $scope.state.privateKeyUpload = { toggle: function(to) { @@ -24,7 +20,7 @@ var PrivateKeyUploadCtrl = function($scope) { // close lightbox $scope.state.lightbox = undefined; // show message - $scope.onError({ + dialog.info({ title: 'Info', message: 'Your PGP key has already been synced.' }); @@ -64,7 +60,7 @@ var PrivateKeyUploadCtrl = function($scope) { keyId: keyParams._id }, function(err, privateKeySynced) { if (err) { - $scope.onError(err); + dialog.error(err); return; } @@ -93,8 +89,7 @@ var PrivateKeyUploadCtrl = function($scope) { if (inputCode.toUpperCase() !== $scope.code) { var err = new Error('The code does not match. Please go back and check the generated code.'); - err.sync = true; - $scope.onError(err); + dialog.error(err); return false; } @@ -106,7 +101,7 @@ var PrivateKeyUploadCtrl = function($scope) { }; $scope.encryptAndUploadKey = function(callback) { - var userId = appController._emailDao._account.emailAddress; + var userId = auth.emailAddress; var code = $scope.code; // register device to keychain service @@ -114,7 +109,7 @@ var PrivateKeyUploadCtrl = function($scope) { userId: userId }, function(err) { if (err) { - $scope.onError(err); + dialog.error(err); return; } @@ -147,7 +142,7 @@ var PrivateKeyUploadCtrl = function($scope) { // set device name to local storage $scope.setDeviceName(function(err) { if (err) { - $scope.onError(err); + dialog.error(err); return; } @@ -158,14 +153,14 @@ var PrivateKeyUploadCtrl = function($scope) { // init key sync $scope.encryptAndUploadKey(function(err) { if (err) { - $scope.onError(err); + dialog.error(err); return; } // close sync dialog $scope.state.privateKeyUpload.toggle(false); // show success message - $scope.onError({ + dialog.info({ title: 'Success', message: 'Whiteout Keychain setup successful!' }); diff --git a/src/js/controller/app/read.js b/src/js/controller/app/read.js index 26aea1c..4092644 100644 --- a/src/js/controller/app/read.js +++ b/src/js/controller/app/read.js @@ -1,21 +1,12 @@ 'use strict'; -var appController = require('../app-controller'), - download = require('../util/download'), - str = require('../app-config').string, - emailDao, invitationDao, outbox, pgp, keychain; - // // Controller // -var ReadCtrl = function($scope) { +var ReadCtrl = function($scope, email, invitation, outbox, pgp, keychain, appConfig, download, auth, dialog) { - emailDao = appController._emailDao; - invitationDao = appController._invitationDao; - outbox = appController._outboxBo; - pgp = appController._pgp; - keychain = appController._keychain; + var str = appConfig.string; // set default value so that the popover height is correct on init $scope.keyId = 'No key found.'; @@ -31,7 +22,7 @@ var ReadCtrl = function($scope) { $scope.keyId = 'Searching...'; keychain.getReceiverPublicKey(address, function(err, pubkey) { if (err) { - $scope.onError(err); + dialog.error(err); return; } @@ -71,7 +62,7 @@ var ReadCtrl = function($scope) { keychain.getReceiverPublicKey(user.address, function(err, pubkey) { if (err) { - $scope.onError(err); + dialog.error(err); return; } @@ -97,12 +88,12 @@ var ReadCtrl = function($scope) { } var folder = $scope.state.nav.currentFolder; - var email = $scope.state.mailList.selected; - emailDao.getAttachment({ + var message = $scope.state.mailList.selected; + email.getAttachment({ folder: folder, - uid: email.uid, + uid: message.uid, attachment: attachment - }, $scope.onError); + }, dialog.error); }; $scope.invite = function(user) { @@ -113,15 +104,15 @@ var ReadCtrl = function($scope) { $scope.keyId = 'Sending invitation...'; - var sender = emailDao._account.emailAddress, + var sender = auth.emailAddress, recipient = user.address; - invitationDao.invite({ + invitation.invite({ recipient: recipient, sender: sender }, function(err) { if (err) { - $scope.onError(err); + dialog.error(err); return; } @@ -139,7 +130,7 @@ var ReadCtrl = function($scope) { }; // send invitation mail - outbox.put(invitationMail, $scope.onError); + outbox.put(invitationMail, dialog.error); }); }; }; diff --git a/src/js/controller/app/set-passphrase.js b/src/js/controller/app/set-passphrase.js index 724af8d..acbe04b 100644 --- a/src/js/controller/app/set-passphrase.js +++ b/src/js/controller/app/set-passphrase.js @@ -1,15 +1,10 @@ 'use strict'; -var appController = require('../app-controller'), - pgp, keychain; +var SetPassphraseCtrl = function($scope, pgp, keychain, dialog) { -// -// Controller -// - -var SetPassphraseCtrl = function($scope) { - keychain = appController._keychain; - pgp = appController._pgp; + // + // scope variables + // $scope.state.setPassphrase = { toggle: function(to) { @@ -22,10 +17,6 @@ var SetPassphraseCtrl = function($scope) { } }; - // - // scope variables - // - // // scope functions // @@ -87,7 +78,7 @@ var SetPassphraseCtrl = function($scope) { var keyId = pgp.getKeyParams()._id; keychain.lookupPrivateKey(keyId, function(err, savedKey) { if (err) { - $scope.onError(err); + dialog.error(err); return; } @@ -102,7 +93,7 @@ var SetPassphraseCtrl = function($scope) { function onPassphraseChanged(err, newPrivateKeyArmored) { if (err) { err.showBugReporter = false; - $scope.onError(err); + dialog.error(err); return; } @@ -120,13 +111,13 @@ var SetPassphraseCtrl = function($scope) { function onKeyPersisted(err) { if (err) { - $scope.onError(err); + dialog.error(err); return; } $scope.state.setPassphrase.toggle(false); $scope.$apply(); - $scope.onError({ + dialog.info({ title: 'Success', message: 'Passphrase change complete.' }); diff --git a/src/js/controller/app/status-display.js b/src/js/controller/app/status-display.js new file mode 100644 index 0000000..f86d40d --- /dev/null +++ b/src/js/controller/app/status-display.js @@ -0,0 +1,20 @@ +'use strict'; + +var StatusDisplayCtrl = function($scope, statusDisplay) { + + // set the show functions + statusDisplay.showStatus = updateStatus; + statusDisplay.showSearching = setSearching; + + function updateStatus(lbl, time) { + $scope.lastUpdateLbl = lbl; + $scope.lastUpdate = (time) ? time : ''; + } + + function setSearching(state) { + $scope.searching = state; + } + +}; + +module.exports = StatusDisplayCtrl; \ No newline at end of file diff --git a/src/js/controller/app/write.js b/src/js/controller/app/write.js index 5713ec6..014b0d7 100644 --- a/src/js/controller/app/write.js +++ b/src/js/controller/app/write.js @@ -1,21 +1,15 @@ 'use strict'; -var appController = require('../app-controller'), - axe = require('axe-logger'), - util = require('crypto-lib').util, - str = require('../app-config').string, - pgp, emailDao, outbox, keychainDao, auth; +var axe = require('axe-logger'), + util = require('crypto-lib').util; // // Controller // -var WriteCtrl = function($scope, $filter, $q) { - pgp = appController._pgp; - auth = appController._auth; - emailDao = appController._emailDao; - outbox = appController._outboxBo; - keychainDao = appController._keychain; +var WriteCtrl = function($scope, $filter, $q, appConfig, auth, keychain, pgp, email, outbox, dialog) { + + var str = appConfig.string; // set default value so that the popover height is correct on init $scope.keyId = 'XXXXXXXX'; @@ -133,7 +127,7 @@ var WriteCtrl = function($scope, $filter, $q) { } if (replyAll) { re.to.concat(re.cc).forEach(function(recipient) { - var me = emailDao._account.emailAddress; + var me = auth.emailAddress; if (recipient.address === me && replyTo !== me) { // don't reply to yourself return; @@ -225,15 +219,15 @@ var WriteCtrl = function($scope, $filter, $q) { return; } - // keychainDao is undefined in local dev environment - if (keychainDao) { + // keychain is undefined in local dev environment + if (keychain) { // check if to address is contained in known public keys // when we write an email, we always need to work with the latest keys available - keychainDao.refreshKeyForUserId({ + keychain.refreshKeyForUserId({ userId: recipient.address }, function(err, key) { if (err) { - $scope.onError(err); + dialog.error(err); return; } @@ -318,13 +312,13 @@ var WriteCtrl = function($scope, $filter, $q) { // $scope.sendToOutbox = function() { - var email; + var message; // build email model for smtp-client - email = { + message = { from: [{ - name: emailDao._account.realname, - address: emailDao._account.emailAddress + name: auth.realname, + address: auth.emailAddress }], to: $scope.to.filter(filterEmptyAddresses), cc: $scope.cc.filter(filterEmptyAddresses), @@ -337,11 +331,11 @@ var WriteCtrl = function($scope, $filter, $q) { }; if ($scope.inReplyTo) { - email.headers['in-reply-to'] = '<' + $scope.inReplyTo + '>'; + message.headers['in-reply-to'] = '<' + $scope.inReplyTo + '>'; } if ($scope.references && $scope.references.length) { - email.headers.references = $scope.references.map(function(reference) { + message.headers.references = $scope.references.map(function(reference) { return '<' + reference + '>'; }).join(' '); } @@ -350,9 +344,9 @@ var WriteCtrl = function($scope, $filter, $q) { $scope.state.writer.close(); // persist the email to disk for later sending - outbox.put(email, function(err) { + outbox.put(message, function(err) { if (err) { - $scope.onError(err); + dialog.error(err); return; } @@ -363,12 +357,12 @@ var WriteCtrl = function($scope, $filter, $q) { } $scope.replyTo.answered = true; - emailDao.setFlags({ + email.setFlags({ folder: currentFolder(), message: $scope.replyTo }, function(err) { if (err && err.code !== 42) { - $scope.onError(err); + dialog.error(err); return; } @@ -396,9 +390,9 @@ var WriteCtrl = function($scope, $filter, $q) { if (!$scope.addressBookCache) { // populate address book cache - keychainDao.listLocalPublicKeys(function(err, keys) { + keychain.listLocalPublicKeys(function(err, keys) { if (err) { - $scope.onError(err); + dialog.error(err); return; } diff --git a/src/js/controller/login/add-account.js b/src/js/controller/login/add-account.js index 82e2184..18e1698 100644 --- a/src/js/controller/login/add-account.js +++ b/src/js/controller/login/add-account.js @@ -1,6 +1,6 @@ 'use strict'; -var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth) { +var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth, dialog) { !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app $scope.getAccountSettings = function() { @@ -36,7 +36,7 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth) $scope.oauthPossible = function() { // ask user to use the platform's native OAuth api - $scope.onError({ + dialog.confirm({ title: 'Google Account Login', message: 'You are signing into a Google account. Would you like to sign in with Google or just continue with a password login?', positiveBtnStr: 'Google sign in', @@ -59,7 +59,7 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth) // fetches the email address from the chrome identity api auth.getOAuthToken(function(err) { if (err) { - return $scope.onError(err); + return dialog.error(err); } $scope.setCredentials(); $scope.$apply(); diff --git a/src/js/email/account.js b/src/js/email/account.js index debca95..947bfed 100644 --- a/src/js/email/account.js +++ b/src/js/email/account.js @@ -9,7 +9,7 @@ var axe = require('axe-logger'), PgpMailer = require('pgpmailer'), ImapClient = require('imap-client'); -function Account(appConfig, auth, admin, mailConfig, keychain, pgpbuilder, email, outbox, deviceStorage, updateHandler) { +function Account(appConfig, auth, admin, mailConfig, keychain, pgpbuilder, email, outbox, accountStore, updateHandler) { this._appConfig = appConfig; this._auth = auth; this._admin = admin; @@ -18,7 +18,7 @@ function Account(appConfig, auth, admin, mailConfig, keychain, pgpbuilder, email this._emailDao = email; this._pgpbuilder = pgpbuilder; this._outbox = outbox; - this._deviceStorage = deviceStorage; + this._accountStore = accountStore; this._updateHandler = updateHandler; this._accounts = []; // init accounts list } @@ -61,7 +61,7 @@ Account.prototype.init = function(options, callback) { // Pre-Flight check: initialize and prepare user's local database function prepareDatabase() { - self._deviceStorage.init(options.emailAddress, function(err) { + self._accountStore.init(options.emailAddress, function(err) { if (err) { return callback(err); } diff --git a/src/js/email/email.js b/src/js/email/email.js index 9bc1631..ffe9191 100644 --- a/src/js/email/email.js +++ b/src/js/email/email.js @@ -50,10 +50,10 @@ var MSG_PART_TYPE_HTML = 'html'; * @param {Object} pgpbuilder Generates and encrypts MIME and SMTP messages * @param {Object} mailreader Parses MIME messages received from IMAP */ -function Email(keychain, pgp, deviceStorage, pgpbuilder, mailreader, dialog) { +function Email(keychain, pgp, accountStore, pgpbuilder, mailreader, dialog) { this._keychain = keychain; this._pgp = pgp; - this._devicestorage = deviceStorage; + this._devicestorage = accountStore; this._pgpbuilder = pgpbuilder; this._mailreader = mailreader; diff --git a/src/js/email/outbox.js b/src/js/email/outbox.js index b4350d9..e543010 100644 --- a/src/js/email/outbox.js +++ b/src/js/email/outbox.js @@ -13,7 +13,7 @@ var util = require('crypto-lib').util, * The local outbox takes care of the emails before they are being sent. * It also checks periodically if there are any mails in the local device storage to be sent. */ -function Outbox(email, keychain, deviceStorage) { +function Outbox(email, keychain, accountStore) { /** @private */ this._emailDao = email; @@ -21,7 +21,7 @@ function Outbox(email, keychain, deviceStorage) { this._keychain = keychain; /** @private */ - this._devicestorage = deviceStorage; + this._devicestorage = accountStore; /** * Semaphore-esque flag to avoid 'concurrent' calls to _processOutbox when the timeout fires, but a call is still in process. diff --git a/src/js/service/app-config-store.js b/src/js/service/app-config-store.js deleted file mode 100644 index a51dc67..0000000 --- a/src/js/service/app-config-store.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -var ngModule = angular.module('woServices'); -ngModule.service('appConfigStore', AppConfigStore); -module.exports = AppConfigStore; - -/** - * A service for storing app configuration and user credential data locally - */ -function AppConfigStore(lawnchairDAO) { - this._localDbDao = lawnchairDAO; - this._localDbDao.init('app-config'); -} - -// TODO: inherit DeviceStorage service api \ No newline at end of file diff --git a/src/js/service/devicestorage.js b/src/js/service/devicestorage.js index 2296e10..6bc9460 100644 --- a/src/js/service/devicestorage.js +++ b/src/js/service/devicestorage.js @@ -1,18 +1,29 @@ 'use strict'; var ngModule = angular.module('woServices'); -ngModule.service('deviceStorage', DeviceStorage); +ngModule.factory('deviceStorage', function(lawnchairDAO) { + return new DeviceStorage(lawnchairDAO); +}); module.exports = DeviceStorage; +// expose an instance with the static dbName 'app-config' to store configuration data +ngModule.factory('appConfigStore', function(deviceStorage) { + deviceStorage.init('app-config'); + return deviceStorage; +}); + +// expose a singleton instance of DeviceStorage called 'accountStore' to persist user data +ngModule.service('accountStore', DeviceStorage); + /** * High level storage api that handles all persistence of a user's data on the device. */ function DeviceStorage(lawnchairDAO) { - this._localDbDao = lawnchairDAO; + this._lawnchairDAO = lawnchairDAO; } DeviceStorage.prototype.init = function(dbName) { - this._localDbDao.init(dbName); + this._lawnchairDAO.init(dbName); }; /** @@ -46,14 +57,14 @@ DeviceStorage.prototype.storeList = function(list, type, callback) { }); }); - this._localDbDao.batch(items, callback); + this._lawnchairDAO.batch(items, callback); }; /** * Deletes items of a certain type from storage */ DeviceStorage.prototype.removeList = function(type, callback) { - this._localDbDao.removeList(type, callback); + this._lawnchairDAO.removeList(type, callback); }; /** @@ -64,14 +75,14 @@ DeviceStorage.prototype.removeList = function(type, callback) { */ DeviceStorage.prototype.listItems = function(type, offset, num, callback) { // fetch all items of a certain type from the data-store - this._localDbDao.list(type, offset, num, callback); + this._lawnchairDAO.list(type, offset, num, callback); }; /** * Clear the whole device data-store */ DeviceStorage.prototype.clear = function(callback) { - this._localDbDao.clear(callback); + this._lawnchairDAO.clear(callback); }; // diff --git a/src/js/service/index.js b/src/js/service/index.js index 7e18939..62d1a59 100644 --- a/src/js/service/index.js +++ b/src/js/service/index.js @@ -12,6 +12,5 @@ require('./publickey'); require('./admin'); require('./lawnchair'); require('./devicestorage'); -require('./app-config-store'); require('./auth'); require('./keychain'); \ No newline at end of file diff --git a/src/js/util/dialog.js b/src/js/util/dialog.js index e1852a5..8d5a330 100644 --- a/src/js/util/dialog.js +++ b/src/js/util/dialog.js @@ -6,8 +6,14 @@ module.exports = Dialog; function Dialog() {} -Dialog.prototype.error = function() {}; +Dialog.prototype.info = function(options) { + this.displayInfo(options); +}; -Dialog.prototype.info = function() {}; +Dialog.prototype.error = function(options) { + this.displayError(options); +}; -Dialog.prototype.confirm = function() {}; \ No newline at end of file +Dialog.prototype.confirm = function(options) { + this.displayConfirm(options); +}; \ No newline at end of file diff --git a/src/js/util/download.js b/src/js/util/download.js index a8bf537..bd9a93b 100644 --- a/src/js/util/download.js +++ b/src/js/util/download.js @@ -1,10 +1,20 @@ 'use strict'; +var ngModule = angular.module('woUtil'); +ngModule.service('download', Download); +module.exports = Download; + var util = require('crypto-lib').util; -var dl = {}; +/** + * A download helper to abstract platform specific behavior + */ +function Download() {} -dl.createDownload = function(options) { +/** + * Create download link and click on it. + */ +Download.prototype.createDownload = function(options) { var contentType = options.contentType || 'application/octet-stream'; var filename = options.filename || 'file'; var content = options.content; @@ -53,6 +63,4 @@ dl.createDownload = function(options) { } window.open('data:' + contentType + ';base64,' + btoa(content), "_blank"); } -}; - -module.exports = dl; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/util/error.js b/src/js/util/error.js deleted file mode 100644 index a7b4e5c..0000000 --- a/src/js/util/error.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -var axe = require('axe-logger'); - -var er = {}; -er.attachHandler = function(scope) { - scope.onError = function(options) { - if (!options) { - scope.$apply(); - return; - } - - axe.error((options.errMsg || options.message) + (options.stack ? ('\n' + options.stack) : '')); - - scope.state.dialog = { - open: true, - title: options.title || 'Error', - message: options.errMsg || options.message, - faqLink: options.faqLink, - positiveBtnStr: options.positiveBtnStr || 'Ok', - negativeBtnStr: options.negativeBtnStr || 'Cancel', - showNegativeBtn: options.showNegativeBtn || false, - showBugReporter: (typeof options.showBugReporter !== 'undefined' ? options.showBugReporter : !options.title), // if title is set, presume it's not an error by default - callback: options.callback - }; - // don't call apply for synchronous calls - if (!options.sync) { - scope.$apply(); - } - }; -}; - -module.exports = er; \ No newline at end of file diff --git a/src/js/util/index.js b/src/js/util/index.js index 1e50e5f..191f042 100644 --- a/src/js/util/index.js +++ b/src/js/util/index.js @@ -4,4 +4,7 @@ angular.module('woUtil', []); require('./dialog'); require('./connection-doctor'); -require('./update/update-handler'); \ No newline at end of file +require('./update/update-handler'); +require('./status-display'); +require('./download'); +require('./notification'); \ No newline at end of file diff --git a/src/js/util/notification.js b/src/js/util/notification.js index f3663b9..670d4cb 100644 --- a/src/js/util/notification.js +++ b/src/js/util/notification.js @@ -1,11 +1,15 @@ 'use strict'; -var cfg = require('../app-config').config; +var ngModule = angular.module('woUtil'); +ngModule.service('notification', Notif); +module.exports = Notif; -var notif = {}; +function Notif(appConfig) { + this._appConfig = appConfig; -if (window.Notification) { - notif.hasPermission = Notification.permission === "granted"; + if (window.Notification) { + this.hasPermission = Notification.permission === "granted"; + } } /** @@ -17,25 +21,27 @@ if (window.Notification) { * @param {Function} options.onClick (optional) callback when the notification is clicked * @returns {Notification} A notification instance */ -notif.create = function(options) { +Notif.prototype.create = function(options) { + var self = this; + options.onClick = options.onClick || function() {}; if (!window.Notification) { return; } - if (!notif.hasPermission) { + if (!self.hasPermission) { // don't wait until callback returns Notification.requestPermission(function(permission) { if (permission === "granted") { - notif.hasPermission = true; + self.hasPermission = true; } }); } var notification = new Notification(options.title, { body: options.message, - icon: cfg.iconPath + icon: self._appConfig.config.iconPath }); notification.onclick = function() { window.focus(); @@ -51,8 +57,6 @@ notif.create = function(options) { return notification; }; -notif.close = function(notification) { +Notif.prototype.close = function(notification) { notification.close(); -}; - -module.exports = notif; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/util/status-display.js b/src/js/util/status-display.js new file mode 100644 index 0000000..5461ea3 --- /dev/null +++ b/src/js/util/status-display.js @@ -0,0 +1,24 @@ +'use strict'; + +var ngModule = angular.module('woUtil'); +ngModule.service('statusDisplay', StatusDisplay); +module.exports = StatusDisplay; + +function StatusDisplay() {} + +/** + * Update the status disply in the lower left of the screen + * @param {String} msg The status message that is to be displayed to the user + * @param {Date} time The time of the last update + */ +StatusDisplay.prototype.update = function(msg, time) { + this.showStatus(msg, time); +}; + +/** + * Update the searching status to show a spinner while searching + * @param {Boolean} state If the spinner should be displayed or not + */ +StatusDisplay.prototype.setSearching = function(state) { + this.showSearching(state); +}; \ No newline at end of file diff --git a/src/tpl/dialog.html b/src/tpl/dialog.html index 8a79bd1..f265fdb 100644 --- a/src/tpl/dialog.html +++ b/src/tpl/dialog.html @@ -1,19 +1,19 @@ \ No newline at end of file diff --git a/src/tpl/mail-list.html b/src/tpl/mail-list.html index 7894810..2230f35 100644 --- a/src/tpl/mail-list.html +++ b/src/tpl/mail-list.html @@ -17,7 +17,7 @@ Search + placeholder="Search"> @@ -71,13 +71,13 @@ -