diff --git a/app/scripts/app.js b/app/scripts/app.js index d013e834..b902c7da 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -4,11 +4,13 @@ var AppModel = require('./models/app-model'), AppView = require('./views/app-view'), KeyHandler = require('./comp/key-handler'), Alerts = require('./comp/alerts'), - DropboxLink = require('./comp/dropbox-link'); + DropboxLink = require('./comp/dropbox-link'), + Updater = require('./comp/updater'); $(function() { require('./mixins/view'); + Updater.check(); if (location.href.indexOf('state=') >= 0) { DropboxLink.receive(); return; diff --git a/app/scripts/comp/launcher.js b/app/scripts/comp/launcher.js index bc9f3df3..504c8334 100644 --- a/app/scripts/comp/launcher.js +++ b/app/scripts/comp/launcher.js @@ -4,6 +4,7 @@ var Backbone = require('backbone'); var Launcher; if (window.process && window.process.versions && window.process.versions.electron) { + /* jshint node:true */ Launcher = { name: 'electron', version: window.process.versions.electron, @@ -35,6 +36,27 @@ if (window.process && window.process.versions && window.process.versions.electro }, fileExists: function(path) { return this.req('fs').existsSync(path); + }, + httpGet: function(config) { + var http = require(config.url.lastIndexOf('https', 0) === 0 ? 'https' : 'http'); + http.get(config.url, function(res) { + var data = []; + res.on('data', function (chunk) { data.push(chunk); }); + res.on('end', function() { + console.log('data', data); + data = Buffer.concat(data); + console.log('data', data); + if (config.utf8) { + data = data.toString('utf8'); + } + console.log('data', data); + if (config.complete) { + config.copmlete(null, data); + } + }); + }).on('error', function(err) { + if (config.complete) { config.complete(err); } + }); } }; window.launcherOpen = function(path) { diff --git a/app/scripts/comp/updater.js b/app/scripts/comp/updater.js new file mode 100644 index 00000000..917c0854 --- /dev/null +++ b/app/scripts/comp/updater.js @@ -0,0 +1,50 @@ +'use strict'; + +var RuntimeInfo = require('./runtime-info'), + Links = require('../const/links'), + Launcher = require('../comp/launcher'); + +var Updater = { + lastCheckDate: null, + lastVersion: null, + lastVersionReleaseDate: null, + needUpdate: null, + status: 'ready', + check: function(complete) { + if (!Launcher) { + return; + } + this.status = 'checking'; + Launcher.httpGet({ + url: Links.WebApp + 'manifest.appcache', + utf8: true, + complete: (function (err, data) { + if (err) { + this.status = 'err'; + if (complete) { + complete(err); + } + return; + } + var match = data.match('#\s*(\d+\-\d+\-\d+):v([\d+\.\w]+)'); + if (!match) { + this.status = 'err'; + if (complete) { + complete(err); + } + return; + } + this.lastVersionReleaseDate = new Date(match[1]); + this.lastVersion = match[2]; + this.lastCheckDate = new Date(); + this.status = 'ok'; + this.needUpdate = this.lastVersion === RuntimeInfo.version; + if (complete) { + complete(); + } + }).bind(this) + }); + } +}; + +module.exports = Updater; diff --git a/app/scripts/models/app-settings-model.js b/app/scripts/models/app-settings-model.js index 2afd7692..4d80f6d0 100644 --- a/app/scripts/models/app-settings-model.js +++ b/app/scripts/models/app-settings-model.js @@ -5,7 +5,8 @@ var Backbone = require('backbone'); var AppSettingsModel = Backbone.Model.extend({ defaults: { theme: 'd', - lastOpenFile: '' + lastOpenFile: '', + autoUpdate: true }, initialize: function() { diff --git a/app/scripts/views/settings/settings-general-view.js b/app/scripts/views/settings/settings-general-view.js index c2bcd8d5..33790a5e 100644 --- a/app/scripts/views/settings/settings-general-view.js +++ b/app/scripts/views/settings/settings-general-view.js @@ -2,13 +2,16 @@ var Backbone = require('backbone'), Launcher = require('../../comp/launcher'), + Updater = require('../../comp/updater'), + Format = require('../../util/format'), AppSettingsModel = require('../../models/app-settings-model'); var SettingsGeneralView = Backbone.View.extend({ template: require('templates/settings/settings-general.html'), events: { - 'change .settings__general-theme': 'changeTheme', + 'change #settings__general-theme': 'changeTheme', + 'change #settings__general-auto-update': 'changeAutoUpdate', 'click .settings__general-dev-tools-link': 'openDevTools' }, @@ -19,10 +22,30 @@ var SettingsGeneralView = Backbone.View.extend({ }, render: function() { - var activeTheme = AppSettingsModel.instance.get('theme'); + var lastUpdateCheck; + switch (Updater.status) { + case 'checking': + lastUpdateCheck = 'Checking...'; + break; + case 'err': + lastUpdateCheck = 'Error checking'; + break; + case 'ok': + lastUpdateCheck = Format.dtStr(Updater.lastCheckDate) + ': ' + + (Updater.needUpdate ? 'New version available: ' + Updater.lastVersion + + ' (released ' + Format.dStr(Updater.lastVersionReleaseDate) + ')' + : 'You are using the latest version'); + break; + default: + lastUpdateCheck = 'Never'; + break; + } this.renderTemplate({ themes: this.allThemes, - activeTheme: activeTheme, + activeTheme: AppSettingsModel.instance.get('theme'), + autoUpdate: AppSettingsModel.instance.get('autoUpdate'), + canAutoUpdate: !!Launcher, + lastUpdateCheck: lastUpdateCheck, devTools: Launcher && Launcher.devTools }); }, @@ -32,6 +55,14 @@ var SettingsGeneralView = Backbone.View.extend({ AppSettingsModel.instance.set('theme', theme); }, + changeAutoUpdate: function(e) { + var autoUpdate = e.target.checked; + AppSettingsModel.instance.set('autoUpdate', autoUpdate); + if (autoUpdate) { + Updater.check(); + } + }, + openDevTools: function() { if (Launcher) { Launcher.openDevTools(); diff --git a/app/templates/settings/settings-general.html b/app/templates/settings/settings-general.html index eeea06cc..0e64725b 100644 --- a/app/templates/settings/settings-general.html +++ b/app/templates/settings/settings-general.html @@ -3,12 +3,20 @@

Appearance

- <% _.forEach(themes, function(name, key) { %> <% }); %>
+ <% if (canAutoUpdate) { %> +

Function

+
+ /> + +
Last update check: <%- lastUpdateCheck %>
+
+ <% } %> <% if (devTools) { %>

Advanced

Show dev tools diff --git a/electron/loading.html b/electron/loading.html new file mode 100644 index 00000000..3ce680e5 --- /dev/null +++ b/electron/loading.html @@ -0,0 +1,32 @@ + + + + + KeeWeb + + + + +

Loading... Please wait

+ + diff --git a/electron/main.js b/electron/main.js index a15c55c1..55e903a8 100644 --- a/electron/main.js +++ b/electron/main.js @@ -10,12 +10,13 @@ var app = require('app'), var mainWindow = null, openFile = process.argv.filter(function(arg) { return /\.kdbx$/i.test(arg); })[0], - ready = false; + ready = false, + htmlPath = path.join(app.getPath('userData'), 'index.html'); + +htmlPath = path.join(__dirname, '../tmp/index.html'); app.on('window-all-closed', function() { app.quit(); }); app.on('ready', function() { - var htmlPath = path.join(app.getPath('userData'), 'index.html'); - mainWindow = new BrowserWindow({ show: false, width: 1000, height: 700, 'min-width': 600, 'min-height': 300, @@ -25,12 +26,14 @@ app.on('ready', function() { if (fs.existsSync(htmlPath)) { mainWindow.loadUrl('file://' + htmlPath); } else { - mainWindow.loadUrl('https://antelle.github.io/keeweb/index.html'); + downloadFile(); } mainWindow.webContents.on('dom-ready', function() { - mainWindow.show(); - ready = true; - notifyOpenFile(); + setTimeout(function() { + mainWindow.show(); + ready = true; + notifyOpenFile(); + }, 50); }); mainWindow.on('closed', function() { mainWindow = null; }); }); @@ -48,3 +51,36 @@ function notifyOpenFile() { openFile = null; } } + +function downloadFile() { + console.log('Downloading file...'); + mainWindow.loadUrl('file://' + path.join(__dirname, 'loading.html')); + var fileData = []; + require('https').get('https://antelle.github.io/keeweb/index.html', function(res) { + res.on('data', function (chunk) { + fileData.push(chunk); + }); + res.on('end', function() { + fileData = Buffer.concat(fileData); + var fileDataStr = fileData.toString('utf8'); + if (/^\s*\s*$/.test(fileDataStr) && fileData.byteLength > 100000) { + fs.writeFileSync(htmlPath, fileData); + if (mainWindow) { + mainWindow.loadUrl('file://' + htmlPath); + } + } else { + showDownloadError('Invalid file downloaded'); + } + }); + }).on('error', function(err) { + showDownloadError(err); + }); +} + +function showDownloadError(err) { + console.error(err); + if (mainWindow) { + mainWindow.webContents.executeJavaScript('setTitle("Failed to download the app. Please restart me.
' + + 'This app requires Internet connection to start for the first time.")'); + } +}