Merge branch 'alex-shpak-cordova' into develop

This commit is contained in:
antelle 2017-04-14 23:22:33 +02:00
commit 04f2548a22
8 changed files with 287 additions and 18 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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();
}
});
}
}
});

View File

@ -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) {

View File

@ -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;

View File

@ -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) {

View File

@ -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) {