From 1311cd1c5ec719b143996beb55fc41d2113adf37 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Fri, 19 Sep 2014 21:51:37 +0200 Subject: [PATCH 1/6] Firefox App and AppCache * Add app manifest for firefox packaged app * Ask user to reload when appcache is updated * Fix boxshadow on all inputs and textareas * Fix AppCache for Firefox * Move Chrome App update code to updatehandler --- Gruntfile.js | 6 ++--- server.js | 14 ++++++---- src/js/app-controller.js | 19 +------------- src/js/app.js | 18 +++++++++++-- src/js/controller/login.js | 5 ++-- src/js/util/update/update-handler.js | 38 +++++++++++++++++++++++++++- src/manifest.webapp | 26 +++++++++++++++++++ src/sass/_scaffolding.scss | 2 ++ 8 files changed, 97 insertions(+), 31 deletions(-) create mode 100644 src/manifest.webapp diff --git a/Gruntfile.js b/Gruntfile.js index e6d7413..fa57114 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -93,7 +93,7 @@ module.exports = function(grunt) { tasks: ['copy:lib', 'manifest'] }, app: { - files: ['src/*.js', 'src/**/*.html', 'src/**/*.json', 'src/img/**/*', 'src/font/**/*'], + files: ['src/*.js', 'src/**/*.html', 'src/**/*.json', 'src/manifest.*', 'src/img/**/*', 'src/font/**/*'], tasks: ['copy:app', 'copy:ca', 'copy:tpl', 'copy:img', 'copy:font', 'manifest-dev', 'manifest'] } }, @@ -179,7 +179,7 @@ module.exports = function(grunt) { app: { expand: true, cwd: 'src/', - src: ['*.html', '*.js', '*.json'], + src: ['*.html', '*.js', '*.json', 'manifest.*'], dest: 'dist/' }, integration: { @@ -220,7 +220,7 @@ module.exports = function(grunt) { timestamp: true, hash: true, cache: ['socket.io/socket.io.js'], - exclude: ['appcache.manifest'], + exclude: ['appcache.manifest', 'manifest.webapp'], master: ['index.html'] }, src: ['**/*.*'], diff --git a/server.js b/server.js index 23c12c2..fcb4872 100644 --- a/server.js +++ b/server.js @@ -71,7 +71,6 @@ app.disable('x-powered-by'); // var port = process.env.PORT || 8585, - oneDay = 86400000, development = process.argv[2] === '--dev'; // set HTTP headers @@ -81,7 +80,14 @@ app.use(function(req, res, next) { // CSP var iframe = development ? "http://" + req.hostname + ":" + port : "https://" + req.hostname; // allow iframe to load assets res.set('Content-Security-Policy', "default-src 'self' " + iframe + "; object-src 'none'; connect-src *; style-src 'self' 'unsafe-inline' " + iframe + "; img-src 'self' data:"); - return next(); + // set Cache-control Header (for AppCache) + res.set('Cache-control', 'public, max-age=0'); + next(); +}); + +app.use('/appcache.manifest', function(req, res, next) { + res.set('Cache-control', 'no-cache'); + next(); }); // redirect all http traffic to https @@ -97,9 +103,7 @@ app.use(function(req, res, next) { app.use(compression()); // server static files -app.use(express.static(__dirname + '/dist', { - maxAge: oneDay -})); +app.use(express.static(__dirname + '/dist')); // // Socket.io proxy diff --git a/src/js/app-controller.js b/src/js/app-controller.js index 3011948..f515f91 100644 --- a/src/js/app-controller.js +++ b/src/js/app-controller.js @@ -173,24 +173,7 @@ define(function(require) { }; self.checkForUpdate = function() { - if (!window.chrome || !chrome.runtime || !chrome.runtime.onUpdateAvailable) { - return; - } - - // check for update and restart - chrome.runtime.onUpdateAvailable.addListener(function(details) { - axe.debug("Updating to version " + details.version); - chrome.runtime.reload(); - }); - chrome.runtime.requestUpdateCheck(function(status) { - if (status === "update_found") { - axe.debug("Update pending..."); - } else if (status === "no_update") { - axe.debug("No update found."); - } else if (status === "throttled") { - axe.debug("Checking updates too frequently."); - } - }); + self._updateHandler.checkForUpdate(self.onError); }; /** diff --git a/src/js/app.js b/src/js/app.js index 0051cda..cfb3fd9 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -1,3 +1,19 @@ +'use strict'; + +// Check if a new ApaCache is available on page load. +if (typeof window.applicationCache !== 'undefined') { + window.onload = function() { + window.applicationCache.onupdateready = function() { + if (window.applicationCache.status === window.applicationCache.UPDATEREADY) { + // Browser downloaded a new app cache + if (window.confirm('A new version of Whiteout Mail is available. Restart the app to update?')) { + window.location.reload(); + } + } + }; + }; +} + // hey Angular, we're bootstrapping manually! window.name = 'NG_DEFER_BOOTSTRAP!'; @@ -54,8 +70,6 @@ requirejs([ backButtonUtil, FastClick ) { - 'use strict'; - // reset window.name window.name = util.UUID(); diff --git a/src/js/controller/login.js b/src/js/controller/login.js index e366398..72be59a 100644 --- a/src/js/controller/login.js +++ b/src/js/controller/login.js @@ -4,8 +4,6 @@ define(function(require) { var appController = require('js/app-controller'); var LoginCtrl = function($scope, $location) { - // check for app update - appController.checkForUpdate(); // start main application controller appController.start({ @@ -16,6 +14,9 @@ define(function(require) { return; } + // check for app update + appController.checkForUpdate(); + initializeUser(); }); diff --git a/src/js/util/update/update-handler.js b/src/js/util/update/update-handler.js index a622128..a34252a 100644 --- a/src/js/util/update/update-handler.js +++ b/src/js/util/update/update-handler.js @@ -1,7 +1,8 @@ define(function(require) { 'use strict'; - var cfg = require('js/app-config').config, + var axe = require('axe'), + cfg = require('js/app-config').config, updateV1 = require('js/util/update/update-v1'), updateV2 = require('js/util/update/update-v2'), updateV3 = require('js/util/update/update-v3'), @@ -92,5 +93,40 @@ define(function(require) { executeNextUpdate(); }; + /** + * Check application version and update correspondingly + */ + UpdateHandler.prototype.checkForUpdate = function(dialog) { + // 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({ + title: 'Update available', + message: 'A new version ' + details.version + ' of the app is available. Restart the app to update?', + positiveBtnStr: 'Restart', + negativeBtnStr: 'Not now', + showNegativeBtn: true, + callback: function(agree) { + if (agree) { + chrome.runtime.reload(); + } + } + }); + }); + chrome.runtime.requestUpdateCheck(function(status) { + if (status === "update_found") { + axe.debug("Update pending..."); + } else if (status === "no_update") { + axe.debug("No update found."); + } else if (status === "throttled") { + axe.debug("Checking updates too frequently."); + } + }); + } + }; + return UpdateHandler; }); \ No newline at end of file diff --git a/src/manifest.webapp b/src/manifest.webapp new file mode 100644 index 0000000..82b2c0f --- /dev/null +++ b/src/manifest.webapp @@ -0,0 +1,26 @@ +{ + "name": "Whiteout Mail", + "description": "Simple and elegant email client with integrated end-to-end encryption. Keeping your emails safe has never been so easy.", + "version": "0.17.1.0", + "launch_path": "/index.html", + "icons": { + "128": "/img/icon-128.png" + }, + "developer": { + "name": "Whiteout Networks GmbH", + "url": "https://whiteout.io" + }, + "default_locale": "en", + "type": "privileged", + "permissions": { + "tcp-socket": { + "description": "Required to connect to mail servers via IMAP/SMTP" + }, + "desktop-notification": { + "description": "Required to show notifications on incoming mails." + }, + "storage": { + "description": "Required to store messages for offline use." + } + } +} \ No newline at end of file diff --git a/src/sass/_scaffolding.scss b/src/sass/_scaffolding.scss index ac8a627..b76ff3d 100755 --- a/src/sass/_scaffolding.scss +++ b/src/sass/_scaffolding.scss @@ -35,6 +35,8 @@ textarea { line-height: inherit; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + // disable box shadow on firefox + background-image: none; } fieldset { From 513d29ad7910b2103613946dc1f53f146307fea0 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 22 Sep 2014 13:15:45 +0200 Subject: [PATCH 2/6] Strip spaces from phone number --- src/js/controller/add-account.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/controller/add-account.js b/src/js/controller/add-account.js index 8668278..35c3288 100644 --- a/src/js/controller/add-account.js +++ b/src/js/controller/add-account.js @@ -29,7 +29,7 @@ define(function(require) { appCtrl._adminDao.createUser({ emailAddress: $scope.emailAddress, password: $scope.pass, - phone: $scope.phone, + phone: $scope.phone.replace(/\s+/g, ''), // remove spaces from the phone number betaCode: $scope.betaCode.toUpperCase() }, function(err) { $scope.busy = false; From 1cc224dcf298dc38b59c60db8d570e27df12af6c Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 22 Sep 2014 13:18:27 +0200 Subject: [PATCH 3/6] Don't focus on passphrase input in login-new-device --- src/tpl/login-new-device.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tpl/login-new-device.html b/src/tpl/login-new-device.html index 683875c..4db441b 100644 --- a/src/tpl/login-new-device.html +++ b/src/tpl/login-new-device.html @@ -16,7 +16,7 @@
- +
Lost your keyfile or passphrase?
From 8199ee741d343ed24549442ec4df943a93e67a0b Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 22 Sep 2014 13:59:05 +0200 Subject: [PATCH 4/6] Fix tests --- test/unit/add-account-ctrl-test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unit/add-account-ctrl-test.js b/test/unit/add-account-ctrl-test.js index 89288e1..4f42e92 100644 --- a/test/unit/add-account-ctrl-test.js +++ b/test/unit/add-account-ctrl-test.js @@ -60,6 +60,7 @@ define(function(require) { it('should fail to error creating user', function(done) { scope.form.$invalid = false; scope.betaCode = 'asfd'; + scope.phone = '12345'; adminStub.createUser.yieldsAsync(new Error('asdf')); scope.$apply = function() { @@ -76,6 +77,7 @@ define(function(require) { it('should work', function(done) { scope.form.$invalid = false; scope.betaCode = 'asfd'; + scope.phone = '12345'; adminStub.createUser.yieldsAsync(); scope.$apply = function() { From f3ad08b066173ae165342ee5a429120b65512ff7 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 22 Sep 2014 14:19:06 +0200 Subject: [PATCH 5/6] Fix scope apply causing rendering error in login controller --- src/js/controller/login.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js/controller/login.js b/src/js/controller/login.js index 72be59a..bb6bf62 100644 --- a/src/js/controller/login.js +++ b/src/js/controller/login.js @@ -93,8 +93,9 @@ define(function(require) { } function goTo(location) { - $location.path(location); - $scope.$apply(); + $scope.$apply(function() { + $location.path(location); + }); } }; From 74f6b3312e9bc6052c9f8b0be2c232bf3b499af2 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 22 Sep 2014 15:28:22 +0200 Subject: [PATCH 6/6] [WO-598] Replace contentEditable in writer with textarea --- src/js/controller/write.js | 57 ++++-------------------------------- src/sass/views/_write.scss | 29 +++++------------- src/tpl/write.html | 8 +---- test/unit/write-ctrl-test.js | 4 --- 4 files changed, 14 insertions(+), 84 deletions(-) diff --git a/src/js/controller/write.js b/src/js/controller/write.js index b9b4e9e..3b0f532 100644 --- a/src/js/controller/write.js +++ b/src/js/controller/write.js @@ -5,7 +5,6 @@ define(function(require) { _ = require('underscore'), appController = require('js/app-controller'), axe = require('axe'), - aes = require('js/crypto/aes-gcm'), util = require('js/crypto/util'), str = require('js/app-config').string, pgp, emailDao, outbox, keychainDao, auth; @@ -37,7 +36,6 @@ define(function(require) { // fill fields depending on replyTo fillFields(replyTo, replyAll, forward); - $scope.updatePreview(); $scope.verify($scope.to[0]); }, @@ -61,7 +59,6 @@ define(function(require) { $scope.bcc = []; $scope.subject = ''; $scope.body = ''; - $scope.ciphertextPreview = ''; $scope.attachments = []; $scope.addressBookCache = undefined; } @@ -321,21 +318,6 @@ define(function(require) { // Editing email body // - // generate key,iv for encryption preview - var key = util.random(128), - iv = util.random(128); - - $scope.updatePreview = function() { - if (!$scope.sendBtnSecure || !$scope.body.trim()) { - $scope.ciphertextPreview = undefined; - return; - } - - // Although this does encrypt live using AES, this is just for show. The plaintext is encrypted seperately before sending the email. - $scope.ciphertextPreview = aes.encrypt($scope.body, key, iv); - }; - $scope.$watch('sendBtnSecure', $scope.updatePreview); - $scope.sendToOutbox = function() { var email; @@ -466,27 +448,6 @@ define(function(require) { // var ngModule = angular.module('write', []); - ngModule.directive('contenteditable', function() { - return { - require: 'ngModel', - link: function(scope, elm, attrs, ctrl) { - // view -> model - elm.on('keyup keydown', function() { - // set model - ctrl.$setViewValue(elm[0].innerText); - scope.$digest(); - }); - - // model -> view - ctrl.$render = function() { - elm[0].innerText = ctrl.$viewValue; - }; - - // load init value from DOM - ctrl.$setViewValue(elm[0].innerText); - } - }; - }); ngModule.directive('focusMe', function($timeout, $parse) { return { @@ -496,7 +457,12 @@ define(function(require) { scope.$watch(model, function(value) { if (value === true) { $timeout(function() { - element[0].focus(); + var el = element[0]; + el.focus(); + if (typeof el.selectionStart !== 'undefined' && typeof el.selectionEnd !== 'undefined') { + el.selectionStart = 0; + el.selectionEnd = 0; + } }, 100); } }); @@ -504,17 +470,6 @@ define(function(require) { }; }); - ngModule.directive('focusChild', function() { - return { - //scope: true, // optionally create a child scope - link: function(scope, element) { - element.on('click', function() { - element[0].children[0].focus(); - }); - } - }; - }); - ngModule.directive('focusInput', function($timeout, $parse) { return { //scope: true, // optionally create a child scope diff --git a/src/sass/views/_write.scss b/src/sass/views/_write.scss index 14fbb94..c95d2c5 100644 --- a/src/sass/views/_write.scss +++ b/src/sass/views/_write.scss @@ -65,7 +65,7 @@ .subject-box { flex-shrink: 0; position: relative; - margin: 20px 0 7px 0; + margin: 20px 0; input[type=file] { visibility: hidden; @@ -146,33 +146,18 @@ } } - .body { + textarea { flex-grow: 1; + width: 100%; + border: none; + outline: none; + color: $color-grey-dark; line-height: 1.5em; - user-select: text; - overflow-y: scroll; + overflow-y: auto; // allow scrolling on iOS -webkit-overflow-scrolling: touch; // put layer on GPU transform: translatez(0); - - *[contentEditable] { - outline: 0px; - cursor: text; - } - - .encrypt-preview { - font-size: 0.9em; - margin-top: 3em; - font-family: monospace; - color: $color-grey-light; - word-wrap: break-word; - transition: opacity 0.5s; - } - - .invisible { - opacity: 0; - } } .send-control { diff --git a/src/tpl/write.html b/src/tpl/write.html index 2baa4a9..e8ef6a5 100644 --- a/src/tpl/write.html +++ b/src/tpl/write.html @@ -58,13 +58,7 @@
-
-

- -
-

-----BEGIN ENCRYPTED PREVIEW-----
{{ciphertextPreview}}
-----END ENCRYPTED PREVIEW-----

-
-
+
diff --git a/test/unit/write-ctrl-test.js b/test/unit/write-ctrl-test.js index 76b811b..b53ffd3 100644 --- a/test/unit/write-ctrl-test.js +++ b/test/unit/write-ctrl-test.js @@ -64,7 +64,6 @@ define(function(require) { expect(scope.state.writer.close).to.exist; expect(scope.verify).to.exist; expect(scope.checkSendStatus).to.exist; - expect(scope.updatePreview).to.exist; expect(scope.sendToOutbox).to.exist; expect(scope.tagStyle).to.exist; expect(scope.lookupAddressBook).to.exist; @@ -91,7 +90,6 @@ define(function(require) { expect(scope.to).to.deep.equal([]); expect(scope.subject).to.equal(''); expect(scope.body).to.equal(''); - expect(scope.ciphertextPreview).to.equal(undefined); expect(verifyMock.calledOnce).to.be.true; scope.verify.restore(); @@ -124,7 +122,6 @@ define(function(require) { expect(scope.subject).to.equal('Re: ' + subject); expect(scope.body).to.contain(body); expect(scope.references).to.deep.equal(['ghi', 'def', 'abc']); - expect(scope.ciphertextPreview).to.not.be.empty; expect(verifyMock.called).to.be.true; scope.verify.restore(); @@ -156,7 +153,6 @@ define(function(require) { expect(scope.to).to.deep.equal([]); expect(scope.subject).to.equal('Fwd: ' + subject); expect(scope.body).to.contain(body); - expect(scope.ciphertextPreview).to.be.undefined; expect(verifyMock.called).to.be.true; expect(scope.attachments).to.not.equal(re.attachments); // not the same reference expect(scope.attachments).to.deep.equal(re.attachments); // but the same content