Merge branch 'master' into develop

This commit is contained in:
antelle 2019-09-30 19:33:21 +02:00
commit 7e0565df3b
17 changed files with 113 additions and 72 deletions

View File

@ -20,7 +20,7 @@ The app is quite stable now. Basic stuff, as well as more advanced operations, s
# Self-hosting # Self-hosting
Everything you need to host this app on your server is any static file server. The app is a single HTML file + cache manifest (optionally; for offline access). Everything you need to host this app on your server is any static file server. The app is a single HTML file + a service worker (optionally; for offline access).
You can download the latest distribution files from [gh-pages](https://github.com/keeweb/keeweb/archive/gh-pages.zip) branch. You can download the latest distribution files from [gh-pages](https://github.com/keeweb/keeweb/archive/gh-pages.zip) branch.
If you are using Docker: If you are using Docker:

View File

@ -9,7 +9,7 @@ AutoTypeHelper.prototype.getActiveWindowInfo = function(callback) {
complete(err, out) { complete(err, out) {
let windowInfo; let windowInfo;
if (out) { if (out) {
const [id, title] = out.trim().split('\n'); const [title, id] = out.trim().split('\n');
windowInfo = { windowInfo = {
id, id,
title title

View File

@ -28,6 +28,7 @@ const AutoType = {
Events.on('auto-type', e => this.handleEvent(e)); Events.on('auto-type', e => this.handleEvent(e));
Events.on('main-window-blur', e => this.resetPendingEvent(e)); Events.on('main-window-blur', e => this.resetPendingEvent(e));
Events.on('main-window-will-close', e => this.resetPendingEvent(e)); Events.on('main-window-will-close', e => this.resetPendingEvent(e));
appModel.files.on('change', () => this.processPendingEvent());
}, },
handleEvent(e) { handleEvent(e) {
@ -209,7 +210,6 @@ const AutoType = {
const evt = { filter, windowInfo }; const evt = { filter, windowInfo };
if (!this.appModel.files.hasOpenFiles()) { if (!this.appModel.files.hasOpenFiles()) {
this.pendingEvent = evt; this.pendingEvent = evt;
this.appModel.files.once('change', this.processPendingEvent, this);
logger.debug('auto-type event delayed'); logger.debug('auto-type event delayed');
this.focusMainWindow(); this.focusMainWindow();
} else { } else {
@ -262,7 +262,6 @@ const AutoType = {
resetPendingEvent() { resetPendingEvent() {
if (this.pendingEvent) { if (this.pendingEvent) {
this.pendingEvent = null; this.pendingEvent = null;
this.appModel.files.off('change', this.processPendingEvent, this);
logger.debug('auto-type event cancelled'); logger.debug('auto-type event cancelled');
} }
}, },
@ -273,7 +272,6 @@ const AutoType = {
} }
logger.debug('processing pending auto-type event'); logger.debug('processing pending auto-type event');
const evt = this.pendingEvent; const evt = this.pendingEvent;
this.appModel.files.off('change', this.processPendingEvent, this);
this.pendingEvent = null; this.pendingEvent = null;
this.processEventWithFilter(evt); this.processEventWithFilter(evt);
} }

View File

@ -1,9 +1,8 @@
import { Storage } from 'storage';
import { Features } from 'util/features'; import { Features } from 'util/features';
const AuthReceiver = { const AuthReceiver = {
receive() { receive() {
if (!Features.isPopup && !Features.isStandalone) { if (!Features.isPopup) {
return false; return false;
} }
const opener = window.opener || window.parent; const opener = window.opener || window.parent;
@ -12,16 +11,9 @@ const AuthReceiver = {
if (!hasKeys) { if (!hasKeys) {
return false; return false;
} }
if (Features.isStandalone) { opener.postMessage(message, window.location.origin);
if (sessionStorage.authStorage) { window.close();
Storage[sessionStorage.authStorage].handleOAuthReturnMessage(message); return true;
}
return false;
} else {
opener.postMessage(message, window.location.origin);
window.close();
return true;
}
}, },
urlArgsToMessage(url) { urlArgsToMessage(url) {

View File

@ -4,9 +4,11 @@ import { Tip } from 'util/ui/tip';
import { KeyHandler } from 'comp/browser/key-handler'; import { KeyHandler } from 'comp/browser/key-handler';
import { Logger } from 'util/logger'; import { Logger } from 'util/logger';
const OnlyDirectEvents = { const DoesNotBubble = {
mouseenter: true, mouseenter: true,
mouseleave: true mouseleave: true,
blur: true,
focus: true
}; };
class View extends EventEmitter { class View extends EventEmitter {
@ -19,6 +21,7 @@ class View extends EventEmitter {
hidden = false; hidden = false;
removed = false; removed = false;
eventListeners = {}; eventListeners = {};
elementEventListeners = [];
debugLogger = localStorage.debugViews ? new Logger('view', this.constructor.name) : undefined; debugLogger = localStorage.debugViews ? new Logger('view', this.constructor.name) : undefined;
constructor(model = undefined, options = {}) { constructor(model = undefined, options = {}) {
@ -59,6 +62,7 @@ class View extends EventEmitter {
if (this.el) { if (this.el) {
const mountRoot = this.options.ownParent ? this.el.firstChild : this.el; const mountRoot = this.options.ownParent ? this.el.firstChild : this.el;
morphdom(mountRoot, html); morphdom(mountRoot, html);
this.bindElementEvents();
} else { } else {
let parent = this.options.parent || this.parent; let parent = this.options.parent || this.parent;
if (parent) { if (parent) {
@ -102,11 +106,9 @@ class View extends EventEmitter {
if (spaceIx > 0) { if (spaceIx > 0) {
event = eventDef.substr(0, spaceIx); event = eventDef.substr(0, spaceIx);
selector = eventDef.substr(spaceIx + 1); selector = eventDef.substr(spaceIx + 1);
if (OnlyDirectEvents[event]) { if (DoesNotBubble[event]) {
throw new Error( this.elementEventListeners.push({ event, selector, method, els: [] });
`Event listener ${eventDef} defined in ${this.constructor.name} ` + continue;
`can be installed only on the view itself`
);
} }
} else { } else {
event = eventDef; event = eventDef;
@ -117,17 +119,48 @@ class View extends EventEmitter {
eventsMap[event].push({ selector, method }); eventsMap[event].push({ selector, method });
} }
for (const [event, handlers] of Object.entries(eventsMap)) { for (const [event, handlers] of Object.entries(eventsMap)) {
this.debugLogger && this.debugLogger.debug('Bind', event, handlers); this.debugLogger && this.debugLogger.debug('Bind', 'view', event, handlers);
const listener = e => this.eventListener(e, handlers); const listener = e => this.eventListener(e, handlers);
this.eventListeners[event] = listener; this.eventListeners[event] = listener;
this.el.addEventListener(event, listener); this.el.addEventListener(event, listener);
} }
this.bindElementEvents();
} }
unbindEvents() { unbindEvents() {
for (const [event, listener] of Object.entries(this.eventListeners)) { for (const [event, listener] of Object.entries(this.eventListeners)) {
this.el.removeEventListener(event, listener); this.el.removeEventListener(event, listener);
} }
this.unbindElementEvents();
}
bindElementEvents() {
if (!this.elementEventListeners.length) {
return;
}
this.unbindElementEvents();
for (const cfg of this.elementEventListeners) {
const els = this.el.querySelectorAll(cfg.selector);
this.debugLogger &&
this.debugLogger.debug('Bind', 'element', cfg.event, cfg.selector, els.length);
cfg.listener = e => this.eventListener(e, [cfg]);
for (const el of els) {
el.addEventListener(cfg.event, cfg.listener);
cfg.els.push(el);
}
}
}
unbindElementEvents() {
if (!this.elementEventListeners.length) {
return;
}
for (const cfg of this.elementEventListeners) {
for (const el of cfg.els) {
el.removeEventListener(cfg.event, cfg.listener);
}
cfg.els = [];
}
} }
eventListener(e, handlers) { eventListener(e, handlers) {

View File

@ -24,6 +24,7 @@ FileInfoModel.defineModelProperties({
openDate: null, openDate: null,
keyFileName: null, keyFileName: null,
keyFileHash: null, keyFileHash: null,
keyFilePath: null,
opts: null, opts: null,
backup: null, backup: null,
fingerprint: null fingerprint: null

View File

@ -33,22 +33,33 @@ const PluginGallery = {
this.logger.error('Network error loading plugins'); this.logger.error('Network error loading plugins');
resolve(); resolve();
}); });
}).then(data => { })
return this.verifySignature(data).then(gallery => { .then(data => {
this.loading = false; this.loading = false;
this.loadError = !gallery; if (!data) {
if (gallery) { this.loadError = true;
this.logger.debug( Events.emit('plugin-gallery-load-complete');
`Loaded ${gallery.plugins.length} plugins`, return;
this.logger.ts(ts)
);
this.gallery = gallery;
this.saveGallery(gallery);
} }
return this.verifySignature(data).then(gallery => {
this.loadError = !gallery;
if (gallery) {
this.logger.debug(
`Loaded ${gallery.plugins.length} plugins`,
this.logger.ts(ts)
);
this.gallery = gallery;
this.saveGallery(gallery);
}
Events.emit('plugin-gallery-load-complete');
return gallery;
});
})
.catch(e => {
this.loadError = true;
this.logger.error('Error loading plugin gallery', e);
Events.emit('plugin-gallery-load-complete'); Events.emit('plugin-gallery-load-complete');
return gallery;
}); });
});
}, },
verifySignature(gallery) { verifySignature(gallery) {

View File

@ -64,7 +64,7 @@ class PluginManager extends Model {
} }
installIfNew(url, expectedManifest, skipSignatureValidation) { installIfNew(url, expectedManifest, skipSignatureValidation) {
const plugin = this.plugins.find({ url }); const plugin = this.plugins.find(p => p.url === url);
if (plugin && plugin.status !== 'invalid') { if (plugin && plugin.status !== 'invalid') {
return Promise.resolve(); return Promise.resolve();
} }

View File

@ -2,7 +2,6 @@ import { Events } from 'framework/events';
import { Links } from 'const/links'; import { Links } from 'const/links';
import { AppSettingsModel } from 'models/app-settings-model'; import { AppSettingsModel } from 'models/app-settings-model';
import { RuntimeDataModel } from 'models/runtime-data-model'; import { RuntimeDataModel } from 'models/runtime-data-model';
import { Features } from 'util/features';
import { Logger } from 'util/logger'; import { Logger } from 'util/logger';
const MaxRequestRetries = 3; const MaxRequestRetries = 3;
@ -30,18 +29,6 @@ class StorageBase {
} }
} }
this.logger = new Logger('storage-' + this.name); this.logger = new Logger('storage-' + this.name);
if (this._oauthReturnMessage) {
this.logger.debug('OAuth return message', this._oauthReturnMessage);
this._oauthProcessReturn(this._oauthReturnMessage);
delete this._oauthReturnMessage;
delete sessionStorage.authStorage;
if (Features.isStandalone) {
const [url, urlParams] = location.href.split(/[?#]/);
if (urlParams) {
location.href = url;
}
}
}
return this; return this;
} }
@ -49,10 +36,6 @@ class StorageBase {
this.enabled = enabled; this.enabled = enabled;
} }
handleOAuthReturnMessage(message) {
this._oauthReturnMessage = message;
}
_xhr(config) { _xhr(config) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
if (config.responseType) { if (config.responseType) {
@ -94,8 +77,10 @@ class StorageBase {
if (this._oauthToken && !config.skipAuth) { if (this._oauthToken && !config.skipAuth) {
xhr.setRequestHeader('Authorization', 'Bearer ' + this._oauthToken.accessToken); xhr.setRequestHeader('Authorization', 'Bearer ' + this._oauthToken.accessToken);
} }
for (const [key, value] of Object.entries(config.headers)) { if (config.headers) {
xhr.setRequestHeader(key, value); for (const [key, value] of Object.entries(config.headers)) {
xhr.setRequestHeader(key, value);
}
} }
let data = config.data; let data = config.data;
if (data instanceof ArrayBuffer) { if (data instanceof ArrayBuffer) {
@ -135,9 +120,6 @@ class StorageBase {
settings = Object.keys(settings) settings = Object.keys(settings)
.map(key => key + '=' + settings[key]) .map(key => key + '=' + settings[key])
.join(','); .join(',');
if (Features.isStandalone) {
sessionStorage.authStorage = this.name;
}
return window.open(url, title, settings); return window.open(url, title, settings);
} }

View File

@ -115,7 +115,8 @@ const KdbxwebInit = {
}); });
}, },
workerPostRun() { // eslint-disable-next-line object-shorthand
workerPostRun: function() {
self.postMessage({ op: 'postRun' }); self.postMessage({ op: 'postRun' });
self.onmessage = e => { self.onmessage = e => {
try { try {
@ -128,7 +129,8 @@ const KdbxwebInit = {
}; };
}, },
calcHash(Module, args) { // eslint-disable-next-line object-shorthand
calcHash: function(Module, args) {
let { password, salt } = args; let { password, salt } = args;
const { memory, iterations, length, parallelism, type, version } = args; const { memory, iterations, length, parallelism, type, version } = args;
const passwordLen = password.byteLength; const passwordLen = password.byteLength;

View File

@ -235,7 +235,7 @@ class SettingsPluginsView extends View {
pluginMatchesFilter(plugin) { pluginMatchesFilter(plugin) {
const searchStr = this.searchStr; const searchStr = this.searchStr;
const manifest = plugin.manifest; const manifest = plugin.manifest;
return ( return !!(
!searchStr || !searchStr ||
manifest.name.toLowerCase().indexOf(searchStr) >= 0 || manifest.name.toLowerCase().indexOf(searchStr) >= 0 ||
(manifest.description && manifest.description.toLowerCase().indexOf(searchStr) >= 0) || (manifest.description && manifest.description.toLowerCase().indexOf(searchStr) >= 0) ||

View File

@ -50,6 +50,8 @@ app.setPath('userData', path.join(tempUserDataPath, tempUserDataPathRand));
setEnv(); setEnv();
restorePreferences(); restorePreferences();
const appSettings = readAppSettings() || {};
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
if (restartPending) { if (restartPending) {
app.relaunch(); app.relaunch();
@ -62,11 +64,10 @@ app.on('window-all-closed', () => {
}); });
app.on('ready', () => { app.on('ready', () => {
appReady = true; appReady = true;
const appSettings = readAppSettings() || {};
setAppOptions(); setAppOptions();
setSystemAppearance(); setSystemAppearance();
createMainWindow(appSettings); createMainWindow();
setGlobalShortcuts(appSettings); setGlobalShortcuts();
subscribePowerEvents(); subscribePowerEvents();
deleteOldTempFiles(); deleteOldTempFiles();
hookRequestHeaders(); hookRequestHeaders();
@ -154,7 +155,7 @@ function setSystemAppearance() {
} }
} }
function createMainWindow(appSettings) { function createMainWindow() {
const windowOptions = { const windowOptions = {
show: false, show: false,
width: 1000, width: 1000,
@ -394,7 +395,7 @@ function notifyOpenFile() {
} }
} }
function setGlobalShortcuts(appSettings) { function setGlobalShortcuts() {
const defaultShortcutModifiers = process.platform === 'darwin' ? 'Ctrl+Alt+' : 'Shift+Alt+'; const defaultShortcutModifiers = process.platform === 'darwin' ? 'Ctrl+Alt+' : 'Shift+Alt+';
const defaultShortcuts = { const defaultShortcuts = {
CopyPassword: { shortcut: defaultShortcutModifiers + 'C', event: 'copy-password' }, CopyPassword: { shortcut: defaultShortcutModifiers + 'C', event: 'copy-password' },

View File

@ -1,6 +1,6 @@
{ {
"name": "KeeWeb", "name": "KeeWeb",
"version": "1.11.0", "version": "1.11.5",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "KeeWeb", "name": "KeeWeb",
"version": "1.11.0", "version": "1.11.5",
"description": "Free cross-platform password manager compatible with KeePass", "description": "Free cross-platform password manager compatible with KeePass",
"main": "main.js", "main": "main.js",
"homepage": "https://keeweb.info", "homepage": "https://keeweb.info",

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "keeweb", "name": "keeweb",
"version": "1.11.0", "version": "1.11.5",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "keeweb", "name": "keeweb",
"version": "1.11.0", "version": "1.11.5",
"description": "Free cross-platform password manager compatible with KeePass", "description": "Free cross-platform password manager compatible with KeePass",
"main": "Gruntfile.js", "main": "Gruntfile.js",
"private": true, "private": true,

View File

@ -1,5 +1,26 @@
Release notes Release notes
------------- -------------
##### v1.11.5 (2019-09-29)
`-` fix #1279: error opening files with saved keyfiles
##### v1.11.4 (2019-09-29)
`-` fix #1277: auto-type window matching on Linux
`-` fix #1278: entry selection auto-type window issues
`-` fixed displaying errors on the plugins page
##### v1.11.3 (2019-09-29)
`-` fix #1275: starting the app after closing on macOS
`-` fix #1276 GDrive connection issues
##### v1.11.2 (2019-09-29)
`-` fix #1272: Argon2 error
`-` fixed Dropbox connection on iOS 13 homescreen
`-` fixed plugin search filtering
##### v1.11.1 (2019-09-28)
`-` fix #1270: password change control focus
`-` fix #1271: loading custom plugins from config
##### v1.11.0 (2019-09-28) ##### v1.11.0 (2019-09-28)
`+` #1125: field actions: copy, reveal, auto-type `+` #1125: field actions: copy, reveal, auto-type
`+` #107: multiline custom fields support `+` #107: multiline custom fields support