From d08321d34573311ecee73a2e52d80d8095965be8 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 9 Dec 2013 19:21:52 +0100 Subject: [PATCH] switching between offline and online state works --- Gruntfile.js | 2 +- src/font/icons.eot | Bin 3080 -> 3188 bytes src/font/icons.svg | 1 + src/font/icons.ttf | Bin 2924 -> 3032 bytes src/font/icons.woff | Bin 3000 -> 3108 bytes src/js/app-controller.js | 394 +++++++++++++++++++-------- src/js/bo/outbox.js | 14 +- src/js/controller/login.js | 17 +- src/js/controller/mail-list.js | 35 ++- src/js/controller/write.js | 10 +- src/js/dao/email-dao.js | 166 +++++++++-- src/sass/views/_mail-list.scss | 6 + src/tpl/mail-list.html | 7 +- src/tpl/navigation.html | 9 +- test/new-unit/app-controller-test.js | 276 +++++++++++++++++-- test/new-unit/email-dao-test.js | 213 ++++++++++++--- test/new-unit/login-ctrl-test.js | 73 ++--- test/new-unit/write-ctrl-test.js | 69 +++++ 18 files changed, 1004 insertions(+), 288 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index ba47af9..56e7405 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -244,7 +244,7 @@ module.exports = function(grunt) { // Test/Dev tasks grunt.registerTask('dev', ['connect:dev']); - grunt.registerTask('test', ['jshint', 'connect:test', 'mocha', 'qunit']); + grunt.registerTask('test', ['jshint', 'connect:test', 'mocha']); grunt.registerTask('prod', ['connect:prod']); // diff --git a/src/font/icons.eot b/src/font/icons.eot index 038851388220883c7c744a654ed628408833afa0..03a6e2bcc58f60b3a349ef7dfd98e4603e14bc5e 100755 GIT binary patch delta 365 zcmW-bJxjv?5J2ymq-j!94N($A+6vN^799Mls9-=Gx=071po4~5$k3Re9lAB6qamY3 z`~h90LmWB;9F@*OLAtoP2z4y=;&9x(dw1O3X%mfW%>jIPT$wF+HQv*0c^?1<6qj|& zaK^dugBPxJo`Ssu0Ga@aQ?p@Q#m+{b075jkV^TwCa5nLTc+Iq0?Tv4^0T4Cf-KJeP z7KYN0xJsP1jJ5+y@JQ?sXRi&bF*wP55#Q5|ykp8PiaiaALVcvb0*T&x7H9?k&u z04pv@l@}x_S1c*LDcK;HL8!Zcbee*%d4-GlgI`Fp(MK?Re|x6H{TIF_mNDg5#Vy|z Zvx{tzOthDRF2pGV#QdM6(tj0?@ed@PS~&m! delta 260 zcmew&(ILUg!Nb6iGm+JtCGJLP<3xviK`RCZhCM)>ker)X@IdXstceF?>a!n<7Bes~ zW-u_Yf5=EpOkwi6SHr--839yhmH`ytOl8dj@+ScKDjB&Y6?$(Nih%qrK)y*%esUtu z65ew_{tF;qCO5I7fI);|9s>gx3y`mnmzbNn$4~w#kgo!?L%krsxP*ZjD8s<*1CnQ8 zW|;VPH6S + \ No newline at end of file diff --git a/src/font/icons.ttf b/src/font/icons.ttf index 2e45a9598fd830e5e0172d046de9f11ca252d664..067cfb73a5489e881df4750b5f0c5b009a2cab68 100755 GIT binary patch delta 369 zcmaDOc0;_LfsuiMft#U$ftkU;KUm+0Z^K_}pvWE|PDsv8EO?;yU^W8-qYRMmlAc&x z0Hg(g`~)D)k)BhTwqUyz2aulu=YRqMKmobj#EJq2QHFUyKFA`4yu{qpJ$~{}f&2+T zOVkVUi%WnG1p@9J6L+{X3Ql5lW)$4)!YIK}|Nj6B1M>j}DUdowbyjsfCN_3HMma`# zMrLtFaY18yMpH#*hV6{o8~)y2Bdo2Z#>>meE-t+0b_1j5_6COo{}~t^wljJ*042rQ zIeB^2w6ulS`~}Kx-~ONBfI|Z~pn!qJ9zIbcp5Nvx12+rEAq?lvoZbkeSiy|RHJs** be3Q3unoNGesldge1(X2=FyCe_t|UeP7{^(r delta 278 zcmca1{zj~xfsuiMft#U$ftkU;KUm+0Z^K_JpvWE|PDsv8EO?;yU={-dqYRMmlAc&x z0Hg(g`~)D)k)BhTmi<_?7|73HU||1{k(!vo(i$0Qo8z zxg`~PZy1Vz{4GGfNlt#UWg^cK-g7{K7eE1-+{B6k1`&pN3=CW>K)ym=Vs7dlKl!IX zz6#J1^@9B35}-qYfZJ!{4tGX@NsP{n0-IeJB{&p79%(GS5f{&I^Ob=cq@97`+>x_z odh!EKb4K3D5?m&e1Gr=a!2afCn8UyZR>3s+Ah+=5$y~0C071?_6aWAK diff --git a/src/font/icons.woff b/src/font/icons.woff index bf8962ead64515062d0d20fe9f81f9ab578209dc..cc50cae05174ec404e9e2ba466f793a157feeaac 100755 GIT binary patch delta 416 zcmXYt%}WAN5Wr`2{n~9!S!1Px8ic}nhy)IQ_RTJ;w5>InePdX^ZD!`^3+Bsqoh zIK!8Bm5W-fPL6tEg)#RH=cV#7^=}?Ef4xBbjvPMk0k~0ps$Bz+=qEkjWIKPRDQ%u+0!v=>54rBR4mNuRFpkFQB0)Lk~yHNWever^wAh;l%9j;^wIeG w4+tE+6QKL{`bfbw+88gsW%OV33&t!T3vnqb&=K%M3)~a|95j8YUFCQ14_`K21poj5 delta 316 zcmZ1?u|r&>+~3WOfsp|SxOXscgXx^ff;_^L6?j-CYKzocCFdpV&=)!A`@1Y|&%X9@4Q+{6l?#asbEz5*DF zFwDzK%uNM~fgGd`!h8JWpBCg7mjHbB%}==8U|PQ@Bhf&*7300EY}O!yE=SunHzn$ZWpP<;n;E)lx#Q diff --git a/src/js/app-controller.js b/src/js/app-controller.js index 529c58a..9be7d30 100644 --- a/src/js/app-controller.js +++ b/src/js/app-controller.js @@ -24,7 +24,7 @@ define(function(require) { /** * Start the application */ - self.start = function(callback) { + self.start = function(options, callback) { // are we running in native app or in browser? if (document.URL.indexOf("http") === 0 || document.URL.indexOf("app") === 0 || document.URL.indexOf("chrome") === 0) { console.log('Assuming Browser environment...'); @@ -36,12 +36,132 @@ define(function(require) { function onDeviceReady() { console.log('Starting app.'); + + // Handle offline and online gracefully + window.addEventListener('online', self.onConnect.bind(self, options.onError)); + window.addEventListener('offline', self.onDisconnect.bind(self, options.onError)); + // init app config storage self._appConfigStore = new DeviceStorageDAO(new LawnchairDAO()); self._appConfigStore.init('app-config', callback); } }; + self.onDisconnect = function(callback) { + if (!self._emailDao) { + // the following code only makes sense if the email dao has been initialized + return; + } + + self._emailDao.onDisconnect(null, callback); + }; + + self.onConnect = function(callback) { + if (!self._emailDao) { + // the following code only makes sense if the email dao has been initialized + return; + } + + if (!self.isOnline()) { + // prevent connection infinite loop + console.log('Not connecting since user agent is offline.'); + callback(); + return; + } + + // fetch pinned local ssl certificate + self.getCertficate(function(err, certificate) { + if (err) { + callback(err); + return; + } + + // get a fresh oauth token + self.fetchOAuthToken(function(err, oauth) { + if (err) { + callback(err); + return; + } + + initClients(oauth, certificate); + }); + }); + + function initClients(oauth, certificate) { + var auth, imapOptions, imapClient, smtpOptions, smtpClient; + + auth = { + XOAuth2: { + user: oauth.emailAddress, + clientId: config.gmail.clientId, + accessToken: oauth.token + } + }; + imapOptions = { + secure: config.gmail.imap.secure, + port: config.gmail.imap.port, + host: config.gmail.imap.host, + auth: auth, + ca: [certificate] + }; + smtpOptions = { + secure: config.gmail.smtp.secure, + port: config.gmail.smtp.port, + host: config.gmail.smtp.host, + auth: auth, + ca: [certificate] + }; + + imapClient = new ImapClient(imapOptions); + smtpClient = new SmtpClient(smtpOptions); + + imapClient.onError = function(err) { + console.log('IMAP error... reconnecting.', err); + // re-init client modules on error + self.onConnect(callback); + }; + + // connect to clients + self._emailDao.onConnect({ + imapClient: imapClient, + smtpClient: smtpClient + }, callback); + } + }; + + self.getCertficate = function(localCallback) { + var xhr; + + if (self.certificate) { + localCallback(null, self.certificate); + return; + } + + // fetch pinned local ssl certificate + xhr = new XMLHttpRequest(); + xhr.open('GET', '/ca/Google_Internet_Authority_G2.pem'); + xhr.onload = function() { + if (xhr.readyState === 4 && xhr.status === 200 && xhr.responseText) { + self.certificate = xhr.responseText; + localCallback(null, self.certificate); + } else { + localCallback({ + errMsg: 'Could not fetch pinned certificate!' + }); + } + }; + xhr.onerror = function() { + localCallback({ + errMsg: 'Could not fetch pinned certificate!' + }); + }; + xhr.send(); + }; + + self.isOnline = function() { + return navigator.onLine; + }; + self.checkForUpdate = function() { if (!chrome || !chrome.runtime || !chrome.runtime.onUpdateAvailable) { return; @@ -63,6 +183,121 @@ define(function(require) { }); }; + /** + * Gracefully try to fetch the user's email address from local storage. + * If not yet stored, handle online/offline cases on first use. + */ + self.getEmailAddress = function(callback) { + // try to fetch email address from local storage + self.getEmailAddressFromConfig(function(err, cachedEmailAddress) { + if (err) { + callback(err); + return; + } + + if (cachedEmailAddress) { + // not first time login... address cached + callback(null, cachedEmailAddress); + return; + } + + if (!cachedEmailAddress && !self.isOnline()) { + // first time login... must be online + callback({ + errMsg: 'The app must be online on first use!' + }); + return; + } + + self.fetchOAuthToken(function(err, oauth) { + if (err) { + callback(err); + return; + } + + callback(null, oauth.emailAddress); + }); + }); + }; + + /** + * Get the user's email address from local storage + */ + self.getEmailAddressFromConfig = function(callback) { + self._appConfigStore.listItems('emailaddress', 0, null, function(err, cachedItems) { + if (err) { + callback(err); + return; + } + + // no email address is cached yet + if (!cachedItems || cachedItems.length < 1) { + callback(); + return; + } + + callback(null, cachedItems[0]); + }); + }; + + /** + * Lookup the user's email address. Check local cache if available + * otherwise query google's token info api to learn the user's email address + */ + self.queryEmailAddress = function(token, callback) { + var itemKey = 'emailaddress'; + + self.getEmailAddressFromConfig(function(err, cachedEmailAddress) { + if (err) { + callback(err); + return; + } + + // do roundtrip to google api if no email address is cached yet + if (!cachedEmailAddress) { + queryGoogleApi(); + return; + } + + callback(null, cachedEmailAddress); + }); + + function queryGoogleApi() { + if (!token) { + callback({ + errMsg: 'Invalid OAuth token!' + }); + return; + } + + // fetch gmail user's email address from the Google Authorization Server endpoint + $.ajax({ + url: 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + token, + type: 'GET', + dataType: 'json', + success: function(info) { + if (!info || !info.email) { + callback({ + errMsg: 'Error looking up email address on google api!' + }); + return; + } + + // cache the email address on the device + self._appConfigStore.storeList([info.email], itemKey, function(err) { + callback(err, info.email); + }); + }, + error: function(xhr, textStatus, err) { + callback({ + errMsg: xhr.status + ': ' + xhr.statusText, + err: err + }); + } + }); + } + }; + /** * Request an OAuth token from chrome for gmail users */ @@ -100,138 +335,59 @@ define(function(require) { ); }; - /** - * Lookup the user's email address. Check local cache if available, otherwise query google's token info api to learn the user's email address - */ - self.queryEmailAddress = function(token, callback) { - var itemKey = 'emailaddress'; + self.buildModules = function() { + var lawnchairDao, restDao, pubkeyDao, invitationDao, + emailDao, keychain, pgp, userStorage; - self._appConfigStore.listItems(itemKey, 0, null, function(err, cachedItems) { - if (err) { - callback(err); - return; - } + // init objects and inject dependencies + restDao = new RestDAO(); + pubkeyDao = new PublicKeyDAO(restDao); + invitationDao = new InvitationDAO(restDao); + lawnchairDao = new LawnchairDAO(); + userStorage = new DeviceStorageDAO(lawnchairDao); - // do roundtrip to google api if no email address is cached yet - if (!cachedItems || cachedItems.length < 1) { - queryGoogleApi(); - return; - } - - callback(null, cachedItems[0]); - }); - - function queryGoogleApi() { - // fetch gmail user's email address from the Google Authorization Server endpoint - $.ajax({ - url: 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + token, - type: 'GET', - dataType: 'json', - success: function(info) { - if (!info || !info.email) { - callback({ - errMsg: 'Error looking up email address on google api!' - }); - return; - } - - // cache the email address on the device - self._appConfigStore.storeList([info.email], itemKey, function(err) { - callback(err, info.email); - }); - }, - error: function(xhr, textStatus, err) { - callback({ - errMsg: xhr.status + ': ' + xhr.statusText, - err: err - }); - } - }); - } + keychain = new KeychainDAO(lawnchairDao, pubkeyDao); + self._keychain = keychain; + pgp = new PGP(); + self._crypto = pgp; + self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage); + self._outboxBo = new OutboxBO(emailDao, keychain, userStorage, invitationDao); }; /** * Instanciate the mail email data access object and its dependencies. Login to imap on init. */ - self.init = function(userId, token, callback) { - var auth, imapOptions, smtpOptions, certificate, - lawnchairDao, restDao, pubkeyDao, invitationDao, emailDao, - keychain, imapClient, smtpClient, pgp, userStorage, xhr; + self.init = function(options, callback) { + self.buildModules(); - // fetch pinned local ssl certificate - xhr = new XMLHttpRequest(); - xhr.open('GET', '/ca/Google_Internet_Authority_G2.pem'); - xhr.onload = function() { - if (xhr.readyState === 4 && xhr.status === 200 && xhr.responseText) { - certificate = xhr.responseText; - setupDaos(); - } else { - callback({ - errMsg: 'Could not fetch pinned certificate!' - }); + // init email dao + var account = { + emailAddress: options.emailAddress, + asymKeySize: config.asymKeySize + }; + + self._emailDao.init({ + account: account + }, function(err, keypair) { + // dont handle offline case this time + if (err && err.code !== 42) { + callback(err); + return; } - }; - xhr.onerror = function() { - callback({ - errMsg: 'Could not fetch pinned certificate!' - }); - }; - xhr.send(); - function setupDaos() { - // create mail credentials objects for imap/smtp - auth = { - XOAuth2: { - user: userId, - clientId: config.gmail.clientId, - accessToken: token + // connect tcp clients on first startup + self.onConnect(function(err) { + if (err) { + callback(err); + return; } - }; - imapOptions = { - secure: config.gmail.imap.secure, - port: config.gmail.imap.port, - host: config.gmail.imap.host, - auth: auth, - ca: [certificate] - }; - smtpOptions = { - secure: config.gmail.smtp.secure, - port: config.gmail.smtp.port, - host: config.gmail.smtp.host, - auth: auth, - ca: [certificate] - }; - // init objects and inject dependencies - restDao = new RestDAO(); - pubkeyDao = new PublicKeyDAO(restDao); - lawnchairDao = new LawnchairDAO(); - keychain = new KeychainDAO(lawnchairDao, pubkeyDao); - self._keychain = keychain; - imapClient = new ImapClient(imapOptions); - smtpClient = new SmtpClient(smtpOptions); - pgp = new PGP(); - self._crypto = pgp; - userStorage = new DeviceStorageDAO(lawnchairDao); - self._emailDao = emailDao = new EmailDAO(keychain, imapClient, smtpClient, pgp, userStorage); - - invitationDao = new InvitationDAO(restDao); - self._outboxBo = new OutboxBO(emailDao, keychain, userStorage, invitationDao); - - // init email dao - var account = { - emailAddress: userId, - asymKeySize: config.asymKeySize - }; - - self._emailDao.init({ - account: account - }, function(err, keypair) { + // init outbox self._outboxBo.init(); - callback(err, keypair); - }); - } + callback(null, keypair); + }); + }); }; return self; diff --git a/src/js/bo/outbox.js b/src/js/bo/outbox.js index 35dca95..6969982 100644 --- a/src/js/bo/outbox.js +++ b/src/js/bo/outbox.js @@ -232,7 +232,12 @@ define(function(require) { self._emailDao.sendPlaintext(invitationMail, function(err) { if (err) { self._outboxBusy = false; - callback(err); + if (err.code === 42) { + // offline try again later + callback(); + } else { + callback(err); + } return; } @@ -251,7 +256,12 @@ define(function(require) { }, function(err) { if (err) { self._outboxBusy = false; - callback(err); + if (err.code === 42) { + // offline try again later + callback(); + } else { + callback(err); + } return; } diff --git a/src/js/controller/login.js b/src/js/controller/login.js index d6b8c5c..e0658b3 100644 --- a/src/js/controller/login.js +++ b/src/js/controller/login.js @@ -14,32 +14,29 @@ define(function(require) { appController.checkForUpdate(); // start main application controller - appController.start(function(err) { + appController.start({ + onError: $scope.onError + }, function(err) { if (err) { $scope.onError(err); return; } - if (!window.chrome || !chrome.identity) { - $location.path('/desktop'); - $scope.$apply(); - return; - } - - // login to imap initializeUser(); }); function initializeUser() { // get OAuth token from chrome - appController.fetchOAuthToken(function(err, auth) { + appController.getEmailAddress(function(err, emailAddress) { if (err) { $scope.onError(err); return; } // initiate controller by creating email dao - appController.init(auth.emailAddress, auth.token, function(err, availableKeys) { + appController.init({ + emailAddress: emailAddress + }, function(err, availableKeys) { if (err) { $scope.onError(err); return; diff --git a/src/js/controller/mail-list.js b/src/js/controller/mail-list.js index 1970bec..f87328e 100644 --- a/src/js/controller/mail-list.js +++ b/src/js/controller/mail-list.js @@ -91,6 +91,13 @@ define(function(require) { return; } + if (err && err.code === 42) { + // offline + updateStatus('Offline mode'); + $scope.$apply(); + return; + } + if (err) { updateStatus('Error on sync!'); $scope.onError(err); @@ -150,6 +157,19 @@ define(function(require) { } }; + // share local scope functions with root state + $scope.state.mailList = { + remove: $scope.remove, + synchronize: $scope.synchronize + }; + + // + // watch tasks + // + + /** + * List emails from folder when user changes folder + */ $scope._stopWatchTask = $scope.$watch('state.nav.currentFolder', function() { if (!getFolder()) { return; @@ -177,11 +197,16 @@ define(function(require) { $scope.synchronize(); }); - // share local scope functions with root state - $scope.state.mailList = { - remove: $scope.remove, - synchronize: $scope.synchronize - }; + /** + * Sync current folder when client comes back online + */ + $scope.$watch('account.online', function(isOnline) { + if (isOnline) { + $scope.synchronize(); + } else { + updateStatus('Offline mode'); + } + }, true); // // helper functions diff --git a/src/js/controller/write.js b/src/js/controller/write.js index 771913d..9771a79 100644 --- a/src/js/controller/write.js +++ b/src/js/controller/write.js @@ -181,7 +181,15 @@ define(function(require) { $scope.replyTo.answered = true; emailDao.sync({ folder: $scope.state.nav.currentFolder.path - }, $scope.onError); + }, function(err) { + if (err && err.code === 42) { + // offline + $scope.onError(); + return; + } + + $scope.onError(err); + }); } }; diff --git a/src/js/dao/email-dao.js b/src/js/dao/email-dao.js index 3c9f433..baa99c7 100644 --- a/src/js/dao/email-dao.js +++ b/src/js/dao/email-dao.js @@ -6,21 +6,12 @@ define(function(require) { str = require('js/app-config').string, config = require('js/app-config').config; - var EmailDAO = function(keychain, imapClient, smtpClient, crypto, devicestorage) { + var EmailDAO = function(keychain, crypto, devicestorage) { var self = this; self._keychain = keychain; - self._imapClient = imapClient; - self._smtpClient = smtpClient; self._crypto = crypto; self._devicestorage = devicestorage; - - // delegation-esque pattern to mitigate between node-style events and plain js - self._imapClient.onIncomingMessage = function(message) { - if (typeof self.onIncomingMessage === 'function') { - self.onIncomingMessage(message); - } - }; }; // @@ -33,6 +24,7 @@ define(function(require) { self._account = options.account; self._account.busy = false; + self._account.online = false; // validate email address var emailAddress = self._account.emailAddress; @@ -63,31 +55,70 @@ define(function(require) { } function initFolders() { - self._imapLogin(function(err) { + // try init folders from memory, since imap client not initiated yet + self._imapListFolders(function(err, folders) { if (err) { callback(err); return; } - self._imapListFolders(function(err, folders) { - if (err) { - callback(err); - return; - } - - // every folder is initially created with an unread count of 0. - // the unread count will be updated after every sync - folders.forEach(function(folder){ - folder.count = 0; - }); - - self._account.folders = folders; - callback(null, keypair); - }); + self._account.folders = folders; + callback(null, keypair); }); } }; + EmailDAO.prototype.onConnect = function(options, callback) { + var self = this; + + self._imapClient = options.imapClient; + self._smtpClient = options.smtpClient; + + // delegation-esque pattern to mitigate between node-style events and plain js + self._imapClient.onIncomingMessage = function(message) { + if (typeof self.onIncomingMessage === 'function') { + self.onIncomingMessage(message); + } + }; + + // connect to newly created imap client + self._imapLogin(function(err) { + if (err) { + callback(err); + return; + } + + // set status to online + self._account.online = true; + + // check memory + if (self._account.folders) { + // no need to init folder again on connect... already in memory + callback(); + return; + } + + // init folders + self._imapListFolders(function(err, folders) { + if (err) { + callback(err); + return; + } + + self._account.folders = folders; + callback(); + }); + }); + }; + + EmailDAO.prototype.onDisconnect = function(options, callback) { + // set status to online + this._account.online = false; + this._imapClient = undefined; + this._smtpClient = undefined; + + callback(); + }; EmailDAO.prototype.unlock = function(options, callback) { var self = this; @@ -180,6 +211,7 @@ define(function(require) { return; } + // check busy status if (self._account.busy) { callback({ errMsg: 'Sync aborted: Previous sync still in progress', @@ -188,6 +220,7 @@ define(function(require) { return; } + // not busy -> set busy self._account.busy = true; folder = _.findWhere(self._account.folders, { @@ -547,7 +580,6 @@ define(function(require) { }); }); } - }); } @@ -705,6 +737,14 @@ define(function(require) { }; EmailDAO.prototype._imapMark = function(options, callback) { + if (!this._imapClient) { + callback({ + errMsg: 'Client is currently offline!', + code: 42 + }); + return; + } + this._imapClient.updateFlags({ path: options.folder, uid: options.uid, @@ -714,6 +754,14 @@ define(function(require) { }; EmailDAO.prototype.move = function(options, callback) { + if (!this._imapClient) { + callback({ + errMsg: 'Client is currently offline!', + code: 42 + }); + return; + } + this._imapClient.moveMessage({ path: options.folder, uid: options.uid, @@ -725,6 +773,14 @@ define(function(require) { var self = this, email = options.email; + if (!self._smtpClient) { + callback({ + errMsg: 'Client is currently offline!', + code: 42 + }); + return; + } + // validate the email input if (!email.to || !email.from || !email.to[0].address || !email.from[0].address) { callback({ @@ -782,6 +838,14 @@ define(function(require) { }; EmailDAO.prototype.sendPlaintext = function(options, callback) { + if (!this._smtpClient) { + callback({ + errMsg: 'Client is currently offline!', + code: 42 + }); + return; + } + this._smtpClient.send(options.email, callback); }; @@ -866,6 +930,14 @@ define(function(require) { * Login the imap client */ EmailDAO.prototype._imapLogin = function(callback) { + if (!this._imapClient) { + callback({ + errMsg: 'Client is currently offline!', + code: 42 + }); + return; + } + // login IMAP client if existent this._imapClient.login(callback); }; @@ -874,6 +946,14 @@ define(function(require) { * Cleanup by logging the user off. */ EmailDAO.prototype._imapLogout = function(callback) { + if (!this._imapClient) { + callback({ + errMsg: 'Client is currently offline!', + code: 42 + }); + return; + } + this._imapClient.logout(callback); }; @@ -882,6 +962,14 @@ define(function(require) { * @param {String} options.folderName The name of the imap folder. */ EmailDAO.prototype._imapListMessages = function(options, callback) { + if (!this._imapClient) { + callback({ + errMsg: 'Client is currently offline!', + code: 42 + }); + return; + } + this._imapClient.listMessages({ path: options.folder, offset: 0, @@ -890,6 +978,14 @@ define(function(require) { }; EmailDAO.prototype._imapDeleteMessage = function(options, callback) { + if (!this._imapClient) { + callback({ + errMsg: 'Client is currently offline!', + code: 42 + }); + return; + } + this._imapClient.deleteMessage({ path: options.folder, uid: options.uid @@ -901,6 +997,14 @@ define(function(require) { * @param {String} options.messageId The */ EmailDAO.prototype._imapGetMessage = function(options, callback) { + if (!this._imapClient) { + callback({ + errMsg: 'Client is currently offline!', + code: 42 + }); + return; + } + this._imapClient.getMessagePreview({ path: options.folder, uid: options.uid @@ -933,6 +1037,14 @@ define(function(require) { function fetchFromServer() { var folders; + if (!self._imapClient) { + callback({ + errMsg: 'Client is currently offline!', + code: 42 + }); + return; + } + // fetch list from imap server self._imapClient.listWellKnownFolders(function(err, wellKnownFolders) { if (err) { diff --git a/src/sass/views/_mail-list.scss b/src/sass/views/_mail-list.scss index cb96a8f..2a09d43 100755 --- a/src/sass/views/_mail-list.scss +++ b/src/sass/views/_mail-list.scss @@ -63,6 +63,12 @@ color: $color-grey-dark; line-height: em(28,12); + .offline { + &[data-icon]:before { + padding-right: 0.5em; + } + } + &.syncing { .spinner { top: 6.5px; diff --git a/src/tpl/mail-list.html b/src/tpl/mail-list.html index 6131bdb..e69ba99 100644 --- a/src/tpl/mail-list.html +++ b/src/tpl/mail-list.html @@ -23,6 +23,11 @@
- {{lastUpdateLbl}} {{lastUpdate | date:'shortTime'}} + + + + + {{lastUpdateLbl}} {{lastUpdate | date:'shortTime'}} +
\ No newline at end of file diff --git a/src/tpl/navigation.html b/src/tpl/navigation.html index 4a8c19c..a9ffa8a 100644 --- a/src/tpl/navigation.html +++ b/src/tpl/navigation.html @@ -4,18 +4,19 @@