mirror of https://github.com/keeweb/keeweb.git
Merge branch 'master' into develop
This commit is contained in:
commit
7e0565df3b
|
@ -20,7 +20,7 @@ The app is quite stable now. Basic stuff, as well as more advanced operations, s
|
|||
|
||||
# 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.
|
||||
|
||||
If you are using Docker:
|
||||
|
|
|
@ -9,7 +9,7 @@ AutoTypeHelper.prototype.getActiveWindowInfo = function(callback) {
|
|||
complete(err, out) {
|
||||
let windowInfo;
|
||||
if (out) {
|
||||
const [id, title] = out.trim().split('\n');
|
||||
const [title, id] = out.trim().split('\n');
|
||||
windowInfo = {
|
||||
id,
|
||||
title
|
||||
|
|
|
@ -28,6 +28,7 @@ const AutoType = {
|
|||
Events.on('auto-type', e => this.handleEvent(e));
|
||||
Events.on('main-window-blur', e => this.resetPendingEvent(e));
|
||||
Events.on('main-window-will-close', e => this.resetPendingEvent(e));
|
||||
appModel.files.on('change', () => this.processPendingEvent());
|
||||
},
|
||||
|
||||
handleEvent(e) {
|
||||
|
@ -209,7 +210,6 @@ const AutoType = {
|
|||
const evt = { filter, windowInfo };
|
||||
if (!this.appModel.files.hasOpenFiles()) {
|
||||
this.pendingEvent = evt;
|
||||
this.appModel.files.once('change', this.processPendingEvent, this);
|
||||
logger.debug('auto-type event delayed');
|
||||
this.focusMainWindow();
|
||||
} else {
|
||||
|
@ -262,7 +262,6 @@ const AutoType = {
|
|||
resetPendingEvent() {
|
||||
if (this.pendingEvent) {
|
||||
this.pendingEvent = null;
|
||||
this.appModel.files.off('change', this.processPendingEvent, this);
|
||||
logger.debug('auto-type event cancelled');
|
||||
}
|
||||
},
|
||||
|
@ -273,7 +272,6 @@ const AutoType = {
|
|||
}
|
||||
logger.debug('processing pending auto-type event');
|
||||
const evt = this.pendingEvent;
|
||||
this.appModel.files.off('change', this.processPendingEvent, this);
|
||||
this.pendingEvent = null;
|
||||
this.processEventWithFilter(evt);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { Storage } from 'storage';
|
||||
import { Features } from 'util/features';
|
||||
|
||||
const AuthReceiver = {
|
||||
receive() {
|
||||
if (!Features.isPopup && !Features.isStandalone) {
|
||||
if (!Features.isPopup) {
|
||||
return false;
|
||||
}
|
||||
const opener = window.opener || window.parent;
|
||||
|
@ -12,16 +11,9 @@ const AuthReceiver = {
|
|||
if (!hasKeys) {
|
||||
return false;
|
||||
}
|
||||
if (Features.isStandalone) {
|
||||
if (sessionStorage.authStorage) {
|
||||
Storage[sessionStorage.authStorage].handleOAuthReturnMessage(message);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
opener.postMessage(message, window.location.origin);
|
||||
window.close();
|
||||
return true;
|
||||
}
|
||||
opener.postMessage(message, window.location.origin);
|
||||
window.close();
|
||||
return true;
|
||||
},
|
||||
|
||||
urlArgsToMessage(url) {
|
||||
|
|
|
@ -4,9 +4,11 @@ import { Tip } from 'util/ui/tip';
|
|||
import { KeyHandler } from 'comp/browser/key-handler';
|
||||
import { Logger } from 'util/logger';
|
||||
|
||||
const OnlyDirectEvents = {
|
||||
const DoesNotBubble = {
|
||||
mouseenter: true,
|
||||
mouseleave: true
|
||||
mouseleave: true,
|
||||
blur: true,
|
||||
focus: true
|
||||
};
|
||||
|
||||
class View extends EventEmitter {
|
||||
|
@ -19,6 +21,7 @@ class View extends EventEmitter {
|
|||
hidden = false;
|
||||
removed = false;
|
||||
eventListeners = {};
|
||||
elementEventListeners = [];
|
||||
debugLogger = localStorage.debugViews ? new Logger('view', this.constructor.name) : undefined;
|
||||
|
||||
constructor(model = undefined, options = {}) {
|
||||
|
@ -59,6 +62,7 @@ class View extends EventEmitter {
|
|||
if (this.el) {
|
||||
const mountRoot = this.options.ownParent ? this.el.firstChild : this.el;
|
||||
morphdom(mountRoot, html);
|
||||
this.bindElementEvents();
|
||||
} else {
|
||||
let parent = this.options.parent || this.parent;
|
||||
if (parent) {
|
||||
|
@ -102,11 +106,9 @@ class View extends EventEmitter {
|
|||
if (spaceIx > 0) {
|
||||
event = eventDef.substr(0, spaceIx);
|
||||
selector = eventDef.substr(spaceIx + 1);
|
||||
if (OnlyDirectEvents[event]) {
|
||||
throw new Error(
|
||||
`Event listener ${eventDef} defined in ${this.constructor.name} ` +
|
||||
`can be installed only on the view itself`
|
||||
);
|
||||
if (DoesNotBubble[event]) {
|
||||
this.elementEventListeners.push({ event, selector, method, els: [] });
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
event = eventDef;
|
||||
|
@ -117,17 +119,48 @@ class View extends EventEmitter {
|
|||
eventsMap[event].push({ selector, method });
|
||||
}
|
||||
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);
|
||||
this.eventListeners[event] = listener;
|
||||
this.el.addEventListener(event, listener);
|
||||
}
|
||||
this.bindElementEvents();
|
||||
}
|
||||
|
||||
unbindEvents() {
|
||||
for (const [event, listener] of Object.entries(this.eventListeners)) {
|
||||
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) {
|
||||
|
|
|
@ -24,6 +24,7 @@ FileInfoModel.defineModelProperties({
|
|||
openDate: null,
|
||||
keyFileName: null,
|
||||
keyFileHash: null,
|
||||
keyFilePath: null,
|
||||
opts: null,
|
||||
backup: null,
|
||||
fingerprint: null
|
||||
|
|
|
@ -33,22 +33,33 @@ const PluginGallery = {
|
|||
this.logger.error('Network error loading plugins');
|
||||
resolve();
|
||||
});
|
||||
}).then(data => {
|
||||
return this.verifySignature(data).then(gallery => {
|
||||
})
|
||||
.then(data => {
|
||||
this.loading = false;
|
||||
this.loadError = !gallery;
|
||||
if (gallery) {
|
||||
this.logger.debug(
|
||||
`Loaded ${gallery.plugins.length} plugins`,
|
||||
this.logger.ts(ts)
|
||||
);
|
||||
this.gallery = gallery;
|
||||
this.saveGallery(gallery);
|
||||
if (!data) {
|
||||
this.loadError = true;
|
||||
Events.emit('plugin-gallery-load-complete');
|
||||
return;
|
||||
}
|
||||
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');
|
||||
return gallery;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
verifySignature(gallery) {
|
||||
|
|
|
@ -64,7 +64,7 @@ class PluginManager extends Model {
|
|||
}
|
||||
|
||||
installIfNew(url, expectedManifest, skipSignatureValidation) {
|
||||
const plugin = this.plugins.find({ url });
|
||||
const plugin = this.plugins.find(p => p.url === url);
|
||||
if (plugin && plugin.status !== 'invalid') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Events } from 'framework/events';
|
|||
import { Links } from 'const/links';
|
||||
import { AppSettingsModel } from 'models/app-settings-model';
|
||||
import { RuntimeDataModel } from 'models/runtime-data-model';
|
||||
import { Features } from 'util/features';
|
||||
import { Logger } from 'util/logger';
|
||||
|
||||
const MaxRequestRetries = 3;
|
||||
|
@ -30,18 +29,6 @@ class StorageBase {
|
|||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -49,10 +36,6 @@ class StorageBase {
|
|||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
handleOAuthReturnMessage(message) {
|
||||
this._oauthReturnMessage = message;
|
||||
}
|
||||
|
||||
_xhr(config) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
if (config.responseType) {
|
||||
|
@ -94,8 +77,10 @@ class StorageBase {
|
|||
if (this._oauthToken && !config.skipAuth) {
|
||||
xhr.setRequestHeader('Authorization', 'Bearer ' + this._oauthToken.accessToken);
|
||||
}
|
||||
for (const [key, value] of Object.entries(config.headers)) {
|
||||
xhr.setRequestHeader(key, value);
|
||||
if (config.headers) {
|
||||
for (const [key, value] of Object.entries(config.headers)) {
|
||||
xhr.setRequestHeader(key, value);
|
||||
}
|
||||
}
|
||||
let data = config.data;
|
||||
if (data instanceof ArrayBuffer) {
|
||||
|
@ -135,9 +120,6 @@ class StorageBase {
|
|||
settings = Object.keys(settings)
|
||||
.map(key => key + '=' + settings[key])
|
||||
.join(',');
|
||||
if (Features.isStandalone) {
|
||||
sessionStorage.authStorage = this.name;
|
||||
}
|
||||
|
||||
return window.open(url, title, settings);
|
||||
}
|
||||
|
|
|
@ -115,7 +115,8 @@ const KdbxwebInit = {
|
|||
});
|
||||
},
|
||||
|
||||
workerPostRun() {
|
||||
// eslint-disable-next-line object-shorthand
|
||||
workerPostRun: function() {
|
||||
self.postMessage({ op: 'postRun' });
|
||||
self.onmessage = e => {
|
||||
try {
|
||||
|
@ -128,7 +129,8 @@ const KdbxwebInit = {
|
|||
};
|
||||
},
|
||||
|
||||
calcHash(Module, args) {
|
||||
// eslint-disable-next-line object-shorthand
|
||||
calcHash: function(Module, args) {
|
||||
let { password, salt } = args;
|
||||
const { memory, iterations, length, parallelism, type, version } = args;
|
||||
const passwordLen = password.byteLength;
|
||||
|
|
|
@ -235,7 +235,7 @@ class SettingsPluginsView extends View {
|
|||
pluginMatchesFilter(plugin) {
|
||||
const searchStr = this.searchStr;
|
||||
const manifest = plugin.manifest;
|
||||
return (
|
||||
return !!(
|
||||
!searchStr ||
|
||||
manifest.name.toLowerCase().indexOf(searchStr) >= 0 ||
|
||||
(manifest.description && manifest.description.toLowerCase().indexOf(searchStr) >= 0) ||
|
||||
|
|
|
@ -50,6 +50,8 @@ app.setPath('userData', path.join(tempUserDataPath, tempUserDataPathRand));
|
|||
setEnv();
|
||||
restorePreferences();
|
||||
|
||||
const appSettings = readAppSettings() || {};
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (restartPending) {
|
||||
app.relaunch();
|
||||
|
@ -62,11 +64,10 @@ app.on('window-all-closed', () => {
|
|||
});
|
||||
app.on('ready', () => {
|
||||
appReady = true;
|
||||
const appSettings = readAppSettings() || {};
|
||||
setAppOptions();
|
||||
setSystemAppearance();
|
||||
createMainWindow(appSettings);
|
||||
setGlobalShortcuts(appSettings);
|
||||
createMainWindow();
|
||||
setGlobalShortcuts();
|
||||
subscribePowerEvents();
|
||||
deleteOldTempFiles();
|
||||
hookRequestHeaders();
|
||||
|
@ -154,7 +155,7 @@ function setSystemAppearance() {
|
|||
}
|
||||
}
|
||||
|
||||
function createMainWindow(appSettings) {
|
||||
function createMainWindow() {
|
||||
const windowOptions = {
|
||||
show: false,
|
||||
width: 1000,
|
||||
|
@ -394,7 +395,7 @@ function notifyOpenFile() {
|
|||
}
|
||||
}
|
||||
|
||||
function setGlobalShortcuts(appSettings) {
|
||||
function setGlobalShortcuts() {
|
||||
const defaultShortcutModifiers = process.platform === 'darwin' ? 'Ctrl+Alt+' : 'Shift+Alt+';
|
||||
const defaultShortcuts = {
|
||||
CopyPassword: { shortcut: defaultShortcutModifiers + 'C', event: 'copy-password' },
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "KeeWeb",
|
||||
"version": "1.11.0",
|
||||
"version": "1.11.5",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "KeeWeb",
|
||||
"version": "1.11.0",
|
||||
"version": "1.11.5",
|
||||
"description": "Free cross-platform password manager compatible with KeePass",
|
||||
"main": "main.js",
|
||||
"homepage": "https://keeweb.info",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "keeweb",
|
||||
"version": "1.11.0",
|
||||
"version": "1.11.5",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "keeweb",
|
||||
"version": "1.11.0",
|
||||
"version": "1.11.5",
|
||||
"description": "Free cross-platform password manager compatible with KeePass",
|
||||
"main": "Gruntfile.js",
|
||||
"private": true,
|
||||
|
|
|
@ -1,5 +1,26 @@
|
|||
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)
|
||||
`+` #1125: field actions: copy, reveal, auto-type
|
||||
`+` #107: multiline custom fields support
|
||||
|
|
Loading…
Reference in New Issue