diff --git a/app/scripts/app.js b/app/scripts/app.js index 7a59a689..3caa72de 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -5,6 +5,7 @@ var AppModel = require('./models/app-model'), KeyHandler = require('./comp/key-handler'), IdleTracker = require('./comp/idle-tracker'), PopupNotifier = require('./comp/popup-notifier'), + SingleInstanceChecker = require('./comp/single-instance-checker'), Alerts = require('./comp/alerts'), Updater = require('./comp/updater'), AuthReceiver = require('./comp/auth-receiver'), @@ -78,6 +79,7 @@ $(() => { function showView() { new AppView({ model: appModel }).render(); Updater.init(); + SingleInstanceChecker.init(); } function getConfigParam() { diff --git a/app/scripts/comp/single-instance-checker.js b/app/scripts/comp/single-instance-checker.js new file mode 100644 index 00000000..f72a210c --- /dev/null +++ b/app/scripts/comp/single-instance-checker.js @@ -0,0 +1,40 @@ +'use strict'; + +const Backbone = require('backbone'); +const Launcher = require('./launcher'); + +const LocalStorageKeyName = 'instanceCheck'; +const LocalStorageResponseKeyName = 'instanceMaster'; + +let instanceKey = Date.now().toString(); + +let SingleInstanceChecker = { + init: function() { + if (Launcher) { + return; + } + window.addEventListener('storage', SingleInstanceChecker.storageChanged); + SingleInstanceChecker.setKey(LocalStorageKeyName, instanceKey); + }, + + storageChanged: function(e) { + if (!e.newValue) { + return; + } + if (e.key === LocalStorageKeyName && e.newValue !== instanceKey) { + SingleInstanceChecker.setKey(LocalStorageResponseKeyName, instanceKey + Math.random().toString()); + } else if (e.key === LocalStorageResponseKeyName && e.newValue.indexOf(instanceKey) < 0) { + window.removeEventListener('storage', SingleInstanceChecker.storageChanged); + Backbone.trigger('second-instance'); + } + }, + + setKey: function(key, value) { + try { + localStorage.setItem(key, value); + setTimeout(() => { localStorage.removeItem(key); }, 100); + } catch (e) {} + } +}; + +module.exports = SingleInstanceChecker; diff --git a/app/scripts/locales/base.json b/app/scripts/locales/base.json index e454bfce..5d6aff1d 100644 --- a/app/scripts/locales/base.json +++ b/app/scripts/locales/base.json @@ -285,6 +285,8 @@ "appSaveErrorBodyMul": "Failed to auto-save files:", "appSettingsError": "Error loading app", "appSettingsErrorBody": "There was an error loading app settings. Please double check app url or contact your administrator.", + "appTabWarn": "Too many tabs", + "appTabWarnBody": "KeeWeb cannot be used in two browser tabs simultaneously, please close this tab.", "setGenTitle": "General Settings", "setGenUpdate": "Update", diff --git a/app/scripts/views/app-view.js b/app/scripts/views/app-view.js index 5c4ca3e8..90a7c480 100644 --- a/app/scripts/views/app-view.js +++ b/app/scripts/views/app-view.js @@ -77,6 +77,7 @@ var AppView = Backbone.View.extend({ this.listenTo(Backbone, 'user-idle', this.userIdle); this.listenTo(Backbone, 'app-minimized', this.appMinimized); this.listenTo(Backbone, 'show-context-menu', this.showContextMenu); + this.listenTo(Backbone, 'second-instance', this.showSingleInstanceAlert); this.listenTo(UpdateModel.instance, 'change:updateReady', this.updateApp); @@ -617,6 +618,14 @@ var AppView = Backbone.View.extend({ Backbone.trigger('context-menu-select', e); }, + showSingleInstanceAlert: function() { + this.hideOpenFile(); + Alerts.error({ + header: Locale.appTabWarn, body: Locale.appTabWarnBody, + esc: false, enter: false, click: false, buttons: [] + }); + }, + dragover: function(e) { e.preventDefault(); }, diff --git a/release-notes.md b/release-notes.md index 0841b08b..076044f7 100644 --- a/release-notes.md +++ b/release-notes.md @@ -3,6 +3,7 @@ Release notes ##### v1.4.0 (WIP) `+` allow password copy on mobile Safari `+` password generator usability improvements +`+` warning about several tabs ##### v1.3.2 (2016-09-13) `-` fix #342: url detection in Microsoft Edge