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 @@ -