mirror of https://github.com/keeweb/keeweb.git
Merge branch 'alex-shpak-cordova' into develop
This commit is contained in:
commit
04f2548a22
|
@ -0,0 +1,216 @@
|
|||
/* global FingerprintAuth */
|
||||
|
||||
const Launcher = {
|
||||
name: 'cordova',
|
||||
version: '6.0.0',
|
||||
autoTypeSupported: false,
|
||||
thirdPartyStoragesSupported: false,
|
||||
ready: function(callback) {
|
||||
document.addEventListener('deviceready', callback, false);
|
||||
},
|
||||
platform: function() {
|
||||
return 'cordova';
|
||||
},
|
||||
openLink: function(href) {
|
||||
window.open(href, '_system');
|
||||
},
|
||||
devTools: false,
|
||||
// openDevTools: function() { },
|
||||
getSaveFileName: function(defaultPath, callback) { /* skip in cordova */ },
|
||||
getDataPath: function() {
|
||||
const storagePath = window.cordova.file.externalDataDirectory;
|
||||
return [storagePath].concat(Array.from(arguments)).filter(s => !!s);
|
||||
},
|
||||
getUserDataPath: function(fileName) {
|
||||
return this.getDataPath('userdata', fileName).join('/');
|
||||
},
|
||||
getTempPath: function(fileName) {
|
||||
return this.getDataPath('temp', fileName).join('/');
|
||||
},
|
||||
getDocumentsPath: function(fileName) {
|
||||
return this.getDataPath('documents', fileName).join('/');
|
||||
},
|
||||
getAppPath: function(fileName) {
|
||||
return this.getDataPath(fileName).join('/');
|
||||
},
|
||||
getWorkDirPath: function(fileName) {
|
||||
return this.getDataPath(fileName).join('/');
|
||||
},
|
||||
joinPath: function(...parts) {
|
||||
return [...parts].join('/');
|
||||
},
|
||||
writeFile: function(path, data, callback) {
|
||||
const writeFile = fileEntry => {
|
||||
fileEntry.createWriter(fileWriter => {
|
||||
fileWriter.onerror = callback;
|
||||
fileWriter.onwriteend = () => callback();
|
||||
fileWriter.write(data);
|
||||
}, callback);
|
||||
};
|
||||
|
||||
window.resolveLocalFileSystemURL(path, writeFile, callback, callback);
|
||||
},
|
||||
readFile: function(path, encoding, callback) {
|
||||
window.resolveLocalFileSystemURL(path, fileEntry => {
|
||||
fileEntry.file(file => {
|
||||
const reader = new FileReader();
|
||||
reader.onerror = callback;
|
||||
reader.onloadend = () => {
|
||||
const contents = new Uint8Array(reader.result);
|
||||
callback(encoding ? String.fromCharCode.apply(null, contents) : contents);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}, err => callback(undefined, err));
|
||||
}, err => callback(undefined, err));
|
||||
},
|
||||
fileExists: function(path, callback) {
|
||||
window.resolveLocalFileSystemURL(path, fileEntry => callback(true), () => callback(false));
|
||||
},
|
||||
deleteFile: function(path, callback) {
|
||||
window.resolveLocalFileSystemURL(path, fileEntry => {
|
||||
fileEntry.remove(callback, callback, callback);
|
||||
}, callback);
|
||||
},
|
||||
statFile: function(path, callback) {
|
||||
window.resolveLocalFileSystemURL(path, fileEntry => {
|
||||
fileEntry.file(file => {
|
||||
callback({
|
||||
ctime: new Date(file.lastModified),
|
||||
mtime: new Date(file.lastModified)
|
||||
});
|
||||
}, err => callback(undefined, err));
|
||||
}, err => callback(undefined, err));
|
||||
},
|
||||
statFileSync: function(path) {
|
||||
this.req('fs').statSync(path);
|
||||
},
|
||||
mkdir: function(dir, callback) {
|
||||
const basePath = this.getDataPath().join('/');
|
||||
const createDir = (dirEntry, path, callback) => {
|
||||
const name = path.shift();
|
||||
dirEntry.getDirectory(name, { create: true }, dirEntry => {
|
||||
if (path.length) { // there is more to create
|
||||
createDir(dirEntry, path, callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}, callback);
|
||||
};
|
||||
|
||||
const localPath = dir.replace(basePath, '').split('/').filter(s => !!s);
|
||||
|
||||
if (localPath.length) {
|
||||
window.resolveLocalFileSystemURL(basePath, dirEntry => {
|
||||
createDir(dirEntry, localPath, callback);
|
||||
}, callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
parsePath: function(fileName) {
|
||||
const parts = fileName.split('/');
|
||||
|
||||
return {
|
||||
path: fileName,
|
||||
dir: parts.pop(),
|
||||
file: parts.join('/')
|
||||
};
|
||||
},
|
||||
createFsWatcher: function(path) {
|
||||
return null; // not in android with content provider
|
||||
},
|
||||
// ensureRunnable: function(path) { },
|
||||
preventExit: function(e) {
|
||||
e.returnValue = false;
|
||||
return false;
|
||||
},
|
||||
exit: function() {
|
||||
this.hideApp();
|
||||
},
|
||||
requestExit: function() { /* skip in cordova */ },
|
||||
requestRestart: function() {
|
||||
window.location.reload();
|
||||
},
|
||||
cancelRestart: function() { /* skip in cordova */ },
|
||||
setClipboardText: function(text) {
|
||||
// TODO
|
||||
},
|
||||
getClipboardText: function() {
|
||||
// TODO
|
||||
},
|
||||
clearClipboardText: function() {
|
||||
// TODO
|
||||
},
|
||||
minimizeApp: function() {
|
||||
this.hideApp();
|
||||
},
|
||||
canMinimize: function() {
|
||||
return false;
|
||||
},
|
||||
updaterEnabled: function() {
|
||||
return false;
|
||||
},
|
||||
// getMainWindow: function() { },
|
||||
resolveProxy: function(url, callback) { /* skip in cordova */ },
|
||||
openWindow: function(opts) { /* skip in cordova */ },
|
||||
hideApp: function() { /* skip in cordova */ },
|
||||
isAppFocused: function() {
|
||||
return false; /* skip in cordova */
|
||||
},
|
||||
showMainWindow: function() { /* skip in cordova */ },
|
||||
// spawn: function(config) { },
|
||||
openFileChooser: function(callback) {
|
||||
const onFileSelected = function(selected) {
|
||||
window.resolveLocalFileSystemURL(selected.uri, fileEntry => {
|
||||
fileEntry.file(file => {
|
||||
file.path = file.localURL;
|
||||
file.name = selected.name;
|
||||
callback(null, file);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.cordova.exec(onFileSelected, callback, 'FileChooser', 'choose');
|
||||
},
|
||||
|
||||
fingerprints: {
|
||||
config: {
|
||||
disableBackup: true,
|
||||
clientId: 'keeweb'
|
||||
},
|
||||
|
||||
register: function(fileId, password, callback) {
|
||||
FingerprintAuth.isAvailable(result => {
|
||||
if (!result.isAvailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const encryptConfig = _.extend({}, this.config, {
|
||||
username: fileId,
|
||||
password: password
|
||||
});
|
||||
|
||||
FingerprintAuth.encrypt(encryptConfig, result => {
|
||||
callback(result.token);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
auth: function(fileId, token, callback) {
|
||||
if (!token) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
const decryptConfig = _.extend({}, this.config, {
|
||||
username: fileId,
|
||||
token: token
|
||||
});
|
||||
|
||||
FingerprintAuth.decrypt(decryptConfig, result => {
|
||||
callback(result.password);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Launcher;
|
|
@ -8,6 +8,7 @@ const Launcher = {
|
|||
name: 'electron',
|
||||
version: window.process.versions.electron,
|
||||
autoTypeSupported: true,
|
||||
thirdPartyStoragesSupported: true,
|
||||
req: window.require,
|
||||
platform: function() {
|
||||
return process.platform;
|
||||
|
@ -28,31 +29,31 @@ const Launcher = {
|
|||
openDevTools: function() {
|
||||
this.electron().remote.getCurrentWindow().openDevTools();
|
||||
},
|
||||
getSaveFileName: function(defaultPath, cb) {
|
||||
getSaveFileName: function(defaultPath, callback) {
|
||||
if (defaultPath) {
|
||||
const homePath = this.remReq('electron').app.getPath('userDesktop');
|
||||
defaultPath = this.req('path').join(homePath, defaultPath);
|
||||
defaultPath = this.joinPath(homePath, defaultPath);
|
||||
}
|
||||
this.remReq('electron').dialog.showSaveDialog({
|
||||
title: Locale.launcherSave,
|
||||
defaultPath: defaultPath,
|
||||
filters: [{ name: Locale.launcherFileFilter, extensions: ['kdbx'] }]
|
||||
}, cb);
|
||||
}, callback);
|
||||
},
|
||||
getUserDataPath: function(fileName) {
|
||||
return this.req('path').join(this.remoteApp().getPath('userData'), fileName || '');
|
||||
return this.joinPath(this.remoteApp().getPath('userData'), fileName || '');
|
||||
},
|
||||
getTempPath: function(fileName) {
|
||||
return this.req('path').join(this.remoteApp().getPath('temp'), fileName || '');
|
||||
return this.joinPath(this.remoteApp().getPath('temp'), fileName || '');
|
||||
},
|
||||
getDocumentsPath: function(fileName) {
|
||||
return this.req('path').join(this.remoteApp().getPath('documents'), fileName || '');
|
||||
return this.joinPath(this.remoteApp().getPath('documents'), fileName || '');
|
||||
},
|
||||
getAppPath: function(fileName) {
|
||||
return this.req('path').join(this.remoteApp().getAppPath(), fileName || '');
|
||||
return this.joinPath(this.remoteApp().getAppPath(), fileName || '');
|
||||
},
|
||||
getWorkDirPath: function(fileName) {
|
||||
return this.req('path').join(process.cwd(), fileName || '');
|
||||
return this.joinPath(process.cwd(), fileName || '');
|
||||
},
|
||||
joinPath: function(...parts) {
|
||||
return this.req('path').join(...parts);
|
||||
|
|
|
@ -3,7 +3,7 @@ let Launcher;
|
|||
if (window.process && window.process.versions && window.process.versions.electron) {
|
||||
Launcher = require('./launcher-electron');
|
||||
} else if (window.cordova) {
|
||||
// Launcher = require('./launcher-cordova');
|
||||
// Launcher = require('./launcher-cordova'); // commented out, while we don't support cordova
|
||||
}
|
||||
|
||||
module.exports = Launcher;
|
||||
|
|
|
@ -16,6 +16,7 @@ const FeatureDetector = require('../util/feature-detector');
|
|||
const Format = require('../util/format');
|
||||
const UrlUtil = require('../util/url-util');
|
||||
const AutoType = require('../auto-type');
|
||||
const Launcher = require('../comp/launcher');
|
||||
|
||||
require('../mixins/protected-value-ex');
|
||||
|
||||
|
@ -463,7 +464,8 @@ const AppModel = Backbone.Model.extend({
|
|||
path: params.path,
|
||||
keyFileName: params.keyFileName,
|
||||
keyFilePath: params.keyFilePath,
|
||||
backup: fileInfo && fileInfo.get('backup') || null
|
||||
backup: fileInfo && fileInfo.get('backup') || null,
|
||||
fingerprint: fileInfo && fileInfo.get('fingerprint') || null
|
||||
});
|
||||
file.open(params.password, data, params.keyFileData, err => {
|
||||
if (err) {
|
||||
|
@ -492,7 +494,7 @@ const AppModel = Backbone.Model.extend({
|
|||
this.addToLastOpenFiles(file, rev);
|
||||
this.addFile(file);
|
||||
callback(null, file);
|
||||
this.fileOpened(file, data);
|
||||
this.fileOpened(file, data, params);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -529,7 +531,8 @@ const AppModel = Backbone.Model.extend({
|
|||
rev: rev,
|
||||
syncDate: file.get('syncDate') || dt,
|
||||
openDate: dt,
|
||||
backup: file.get('backup')
|
||||
backup: file.get('backup'),
|
||||
fingerprint: file.get('fingerprint')
|
||||
});
|
||||
switch (this.settings.get('rememberKeyFiles')) {
|
||||
case 'data':
|
||||
|
@ -565,7 +568,7 @@ const AppModel = Backbone.Model.extend({
|
|||
}
|
||||
},
|
||||
|
||||
fileOpened: function(file, data) {
|
||||
fileOpened: function(file, data, params) {
|
||||
if (file.get('storage') === 'file') {
|
||||
Storage.file.watch(file.get('path'), _.debounce(() => {
|
||||
this.syncFile(file);
|
||||
|
@ -578,6 +581,7 @@ const AppModel = Backbone.Model.extend({
|
|||
if (data && backup && backup.enabled && backup.pending) {
|
||||
this.scheduleBackupFile(file, data);
|
||||
}
|
||||
this.saveFileFingerprint(file, params.password);
|
||||
},
|
||||
|
||||
fileClosed: function(file) {
|
||||
|
@ -925,6 +929,18 @@ const AppModel = Backbone.Model.extend({
|
|||
if (needBackup) {
|
||||
this.backupFile(file, data, _.noop);
|
||||
}
|
||||
},
|
||||
|
||||
saveFileFingerprint: function(file, password) {
|
||||
if (Launcher && Launcher.fingerprints && password) {
|
||||
const fileInfo = this.fileInfos.get(file.id);
|
||||
Launcher.fingerprints.register(file.id, this.params.password, token => {
|
||||
if (token) {
|
||||
fileInfo.set('fingerprint', token);
|
||||
this.model.fileInfos.save();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -14,7 +14,8 @@ const FileInfoModel = Backbone.Model.extend({
|
|||
keyFileName: null,
|
||||
keyFileHash: null,
|
||||
opts: null,
|
||||
backup: null
|
||||
backup: null,
|
||||
fingerprint: null
|
||||
},
|
||||
|
||||
initialize: function(data, options) {
|
||||
|
|
|
@ -12,4 +12,9 @@ const ThirdPartyStorage = {
|
|||
onedrive: require('./storage-onedrive')
|
||||
};
|
||||
|
||||
module.exports = _.extend({}, BuiltInStorage, ThirdPartyStorage);
|
||||
const storage = BuiltInStorage;
|
||||
if (!Launcher || Launcher.thirdPartyStoragesSupported) {
|
||||
_.extend(storage, ThirdPartyStorage);
|
||||
}
|
||||
|
||||
module.exports = storage;
|
||||
|
|
|
@ -9,7 +9,7 @@ const StorageFileCache = StorageBase.extend({
|
|||
path: null,
|
||||
|
||||
getPath: function(id) {
|
||||
return Launcher.req('path').join(this.path, id);
|
||||
return Launcher.joinPath(this.path, id);
|
||||
},
|
||||
|
||||
initFs: function(callback) {
|
||||
|
|
|
@ -11,6 +11,7 @@ const Locale = require('../util/locale');
|
|||
const UrlUtil = require('../util/url-util');
|
||||
const InputFx = require('../util/input-fx');
|
||||
const Storage = require('../storage');
|
||||
const Launcher = require('../comp/launcher');
|
||||
|
||||
const logger = new Logger('open-view');
|
||||
|
||||
|
@ -317,7 +318,20 @@ const OpenView = Backbone.View.extend({
|
|||
openAny: function(reading, ext) {
|
||||
this.reading = reading;
|
||||
this.params[reading] = null;
|
||||
this.$el.find('.open__file-ctrl').attr('accept', ext || '').val(null).click();
|
||||
|
||||
const fileInput = this.$el.find('.open__file-ctrl').attr('accept', ext || '').val(null);
|
||||
|
||||
if (Launcher && Launcher.openFileChooser) {
|
||||
Launcher.openFileChooser((err, file) => {
|
||||
if (err) {
|
||||
logger.error('Error opening file chooser', err);
|
||||
} else {
|
||||
this.processFile(file);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fileInput.click();
|
||||
}
|
||||
},
|
||||
|
||||
openLast: function(e) {
|
||||
|
@ -344,7 +358,23 @@ const OpenView = Backbone.View.extend({
|
|||
this.removeFile(id);
|
||||
return;
|
||||
}
|
||||
this.showOpenFileInfo(this.model.fileInfos.get(id));
|
||||
|
||||
const fileInfo = this.model.fileInfos.get(id);
|
||||
this.showOpenFileInfo(fileInfo);
|
||||
|
||||
if (fileInfo && Launcher && Launcher.fingerprints) {
|
||||
this.openFileWithFingerprint(fileInfo);
|
||||
}
|
||||
},
|
||||
|
||||
openFileWithFingerprint(fileInfo) {
|
||||
if (fileInfo.get('fingerprint')) {
|
||||
Launcher.fingerprints.auth(fileInfo.id, fileInfo.get('fingerprint'), password => {
|
||||
this.inputEl.val(password);
|
||||
this.inputEl.trigger('input');
|
||||
this.openDb();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
removeFile: function(id) {
|
||||
|
|
Loading…
Reference in New Issue