2019-09-16 22:57:56 +02:00
|
|
|
import { Events } from 'framework/events';
|
2020-03-29 10:59:40 +02:00
|
|
|
import { StartProfiler } from 'comp/app/start-profiler';
|
2019-10-26 22:56:36 +02:00
|
|
|
import { RuntimeInfo } from 'const/runtime-info';
|
2019-09-15 14:16:32 +02:00
|
|
|
import { Locale } from 'util/locale';
|
|
|
|
import { Logger } from 'util/logger';
|
2019-09-18 07:08:23 +02:00
|
|
|
import { noop } from 'util/fn';
|
2015-10-22 20:03:44 +02:00
|
|
|
|
2017-02-05 11:08:26 +01:00
|
|
|
const logger = new Logger('launcher');
|
|
|
|
|
|
|
|
const Launcher = {
|
|
|
|
name: 'electron',
|
|
|
|
version: window.process.versions.electron,
|
|
|
|
autoTypeSupported: true,
|
2017-04-12 00:06:44 +02:00
|
|
|
thirdPartyStoragesSupported: true,
|
2018-06-01 09:39:02 +02:00
|
|
|
clipboardSupported: true,
|
2017-02-05 11:08:26 +01:00
|
|
|
req: window.require,
|
2019-08-18 10:17:09 +02:00
|
|
|
platform() {
|
2017-02-05 11:08:26 +01:00
|
|
|
return process.platform;
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
electron() {
|
2017-02-05 11:08:26 +01:00
|
|
|
return this.req('electron');
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
remoteApp() {
|
2017-02-05 11:08:26 +01:00
|
|
|
return this.electron().remote.app;
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
remReq(mod) {
|
2017-02-05 11:08:26 +01:00
|
|
|
return this.electron().remote.require(mod);
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
openLink(href) {
|
2020-04-03 18:25:26 +02:00
|
|
|
if (/^(http|https|ftp|sftp|mailto):/i.test(href)) {
|
|
|
|
this.electron().shell.openExternal(href);
|
|
|
|
}
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
|
|
|
devTools: true,
|
2019-08-18 10:17:09 +02:00
|
|
|
openDevTools() {
|
2020-06-01 16:53:51 +02:00
|
|
|
this.electron().remote.getCurrentWindow().webContents.openDevTools({ mode: 'bottom' });
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
getSaveFileName(defaultPath, callback) {
|
2017-02-05 11:08:26 +01:00
|
|
|
if (defaultPath) {
|
|
|
|
const homePath = this.remReq('electron').app.getPath('userDesktop');
|
2017-04-14 23:22:33 +02:00
|
|
|
defaultPath = this.joinPath(homePath, defaultPath);
|
2017-02-05 11:08:26 +01:00
|
|
|
}
|
2019-11-06 19:46:17 +01:00
|
|
|
this.remReq('electron')
|
|
|
|
.dialog.showSaveDialog({
|
2019-08-16 23:05:39 +02:00
|
|
|
title: Locale.launcherSave,
|
2019-08-18 10:17:09 +02:00
|
|
|
defaultPath,
|
2019-08-16 23:05:39 +02:00
|
|
|
filters: [{ name: Locale.launcherFileFilter, extensions: ['kdbx'] }]
|
2019-11-06 19:46:17 +01:00
|
|
|
})
|
2020-06-01 16:53:51 +02:00
|
|
|
.then((res) => callback(res.filePath));
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
getUserDataPath(fileName) {
|
2017-06-05 13:33:12 +02:00
|
|
|
if (!this.userDataPath) {
|
2020-05-13 17:03:23 +02:00
|
|
|
this.userDataPath = this.remoteApp().getPath('userData');
|
2017-06-05 13:33:12 +02:00
|
|
|
}
|
|
|
|
return this.joinPath(this.userDataPath, fileName || '');
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
getTempPath(fileName) {
|
2017-04-14 23:22:33 +02:00
|
|
|
return this.joinPath(this.remoteApp().getPath('temp'), fileName || '');
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
getDocumentsPath(fileName) {
|
2017-04-14 23:22:33 +02:00
|
|
|
return this.joinPath(this.remoteApp().getPath('documents'), fileName || '');
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
getAppPath(fileName) {
|
2017-12-02 20:38:13 +01:00
|
|
|
const dirname = this.req('path').dirname;
|
|
|
|
const appPath = __dirname.endsWith('app.asar') ? __dirname : this.remoteApp().getAppPath();
|
|
|
|
return this.joinPath(dirname(appPath), fileName || '');
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
getWorkDirPath(fileName) {
|
2017-04-14 23:22:33 +02:00
|
|
|
return this.joinPath(process.cwd(), fileName || '');
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
joinPath(...parts) {
|
2017-04-08 17:34:27 +02:00
|
|
|
return this.req('path').join(...parts);
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
writeFile(path, data, callback) {
|
2019-01-06 11:44:10 +01:00
|
|
|
this.req('fs').writeFile(path, window.Buffer.from(data), callback);
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
readFile(path, encoding, callback) {
|
2017-02-05 11:49:22 +01:00
|
|
|
this.req('fs').readFile(path, encoding, (err, contents) => {
|
2017-02-05 11:08:26 +01:00
|
|
|
const data = typeof contents === 'string' ? contents : new Uint8Array(contents);
|
2017-02-05 11:49:22 +01:00
|
|
|
callback(data, err);
|
|
|
|
});
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
fileExists(path, callback) {
|
2020-06-05 16:41:38 +02:00
|
|
|
const fs = this.req('fs');
|
|
|
|
fs.access(path, fs.constants.F_OK, (err) => callback(!err));
|
|
|
|
},
|
|
|
|
fileExistsSync(path) {
|
|
|
|
const fs = this.req('fs');
|
|
|
|
return !fs.accessSync(path, fs.constants.F_OK);
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
deleteFile(path, callback) {
|
2019-09-18 07:08:23 +02:00
|
|
|
this.req('fs').unlink(path, callback || noop);
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
statFile(path, callback) {
|
2017-02-05 11:49:22 +01:00
|
|
|
this.req('fs').stat(path, (err, stats) => callback(stats, err));
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
mkdir(dir, callback) {
|
2017-02-05 11:08:26 +01:00
|
|
|
const fs = this.req('fs');
|
|
|
|
const path = this.req('path');
|
|
|
|
const stack = [];
|
2017-02-05 23:34:37 +01:00
|
|
|
|
2020-06-01 16:53:51 +02:00
|
|
|
const collect = function (dir, stack, callback) {
|
|
|
|
fs.exists(dir, (exists) => {
|
2017-02-05 23:34:37 +01:00
|
|
|
if (exists) {
|
|
|
|
return callback();
|
|
|
|
}
|
|
|
|
|
|
|
|
stack.unshift(dir);
|
|
|
|
const newDir = path.dirname(dir);
|
|
|
|
if (newDir === dir || !newDir || newDir === '.' || newDir === '/') {
|
|
|
|
return callback();
|
|
|
|
}
|
|
|
|
|
|
|
|
collect(newDir, stack, callback);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2020-06-01 16:53:51 +02:00
|
|
|
const create = function (stack, callback) {
|
2017-02-05 23:34:37 +01:00
|
|
|
if (!stack.length) {
|
2017-02-21 19:44:45 +01:00
|
|
|
return callback();
|
2017-02-05 11:08:26 +01:00
|
|
|
}
|
2017-02-05 23:34:37 +01:00
|
|
|
|
2020-06-01 16:53:51 +02:00
|
|
|
fs.mkdir(stack.shift(), (err) => (err ? callback(err) : create(stack, callback)));
|
2017-02-05 23:34:37 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
collect(dir, stack, () => create(stack, callback));
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
parsePath(fileName) {
|
2017-02-05 11:08:26 +01:00
|
|
|
const path = this.req('path');
|
|
|
|
return {
|
|
|
|
path: fileName,
|
|
|
|
dir: path.dirname(fileName),
|
|
|
|
file: path.basename(fileName)
|
|
|
|
};
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
createFsWatcher(path) {
|
2017-02-05 11:08:26 +01:00
|
|
|
return this.req('fs').watch(path, { persistent: false });
|
|
|
|
},
|
2020-05-22 20:46:03 +02:00
|
|
|
loadConfig(name) {
|
|
|
|
return this.remoteApp().loadConfig(name);
|
|
|
|
},
|
|
|
|
saveConfig(name, data) {
|
|
|
|
return this.remoteApp().saveConfig(name, data);
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
ensureRunnable(path) {
|
2017-02-05 11:08:26 +01:00
|
|
|
if (process.platform !== 'win32') {
|
|
|
|
const fs = this.req('fs');
|
|
|
|
const stat = fs.statSync(path);
|
|
|
|
if ((stat.mode & 0o0111) === 0) {
|
|
|
|
const mode = stat.mode | 0o0100;
|
|
|
|
logger.info(`chmod 0${mode.toString(8)} ${path}`);
|
|
|
|
fs.chmodSync(path, mode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
preventExit(e) {
|
2017-02-05 11:08:26 +01:00
|
|
|
e.returnValue = false;
|
|
|
|
return false;
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
exit() {
|
2017-02-05 11:08:26 +01:00
|
|
|
this.exitRequested = true;
|
|
|
|
this.requestExit();
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
requestExit() {
|
2017-02-05 11:08:26 +01:00
|
|
|
const app = this.remoteApp();
|
2020-06-01 16:59:32 +02:00
|
|
|
app.setHookBeforeQuitEvent(false);
|
2017-02-05 11:08:26 +01:00
|
|
|
if (this.restartPending) {
|
|
|
|
app.restartApp();
|
|
|
|
} else {
|
|
|
|
app.quit();
|
|
|
|
}
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
requestRestart() {
|
2017-02-05 11:08:26 +01:00
|
|
|
this.restartPending = true;
|
|
|
|
this.requestExit();
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
cancelRestart() {
|
2017-02-05 11:08:26 +01:00
|
|
|
this.restartPending = false;
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
setClipboardText(text) {
|
2017-02-05 11:08:26 +01:00
|
|
|
return this.electron().clipboard.writeText(text);
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
getClipboardText() {
|
2017-02-05 11:08:26 +01:00
|
|
|
return this.electron().clipboard.readText();
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
clearClipboardText() {
|
2020-05-13 21:20:26 +02:00
|
|
|
const { clipboard } = this.electron();
|
|
|
|
clipboard.clear();
|
|
|
|
if (process.platform === 'linux') {
|
|
|
|
clipboard.clear('selection');
|
|
|
|
}
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2020-06-01 14:42:06 +02:00
|
|
|
quitOnRealQuitEventIfMinimizeOnQuitIsEnabled() {
|
|
|
|
return this.platform() === 'darwin';
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
minimizeApp() {
|
2019-10-05 08:37:10 +02:00
|
|
|
this.remoteApp().minimizeApp({
|
|
|
|
restore: Locale.menuRestoreApp.replace('{}', 'KeeWeb'),
|
|
|
|
quit: Locale.menuQuitApp.replace('{}', 'KeeWeb')
|
|
|
|
});
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
canDetectOsSleep() {
|
2017-06-12 21:07:09 +02:00
|
|
|
return process.platform !== 'linux';
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
updaterEnabled() {
|
2017-02-05 11:08:26 +01:00
|
|
|
return this.electron().remote.process.argv.indexOf('--disable-updater') === -1;
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
getMainWindow() {
|
2017-02-05 11:08:26 +01:00
|
|
|
return this.remoteApp().getMainWindow();
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
resolveProxy(url, callback) {
|
2017-02-05 11:08:26 +01:00
|
|
|
const window = this.getMainWindow();
|
|
|
|
const session = window.webContents.session;
|
2020-06-01 16:53:51 +02:00
|
|
|
session.resolveProxy(url).then((proxy) => {
|
2017-02-05 11:08:26 +01:00
|
|
|
const match = /^proxy\s+([\w\.]+):(\d+)+\s*/i.exec(proxy);
|
|
|
|
proxy = match && match[1] ? { host: match[1], port: +match[2] } : null;
|
|
|
|
callback(proxy);
|
|
|
|
});
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
hideApp() {
|
2017-02-05 11:08:26 +01:00
|
|
|
const app = this.remoteApp();
|
2020-06-01 14:42:06 +02:00
|
|
|
if (this.platform() === 'darwin') {
|
2017-02-05 11:08:26 +01:00
|
|
|
app.hide();
|
2020-06-01 14:42:06 +02:00
|
|
|
} else {
|
|
|
|
app.minimizeThenHideIfInTray();
|
2017-02-05 11:08:26 +01:00
|
|
|
}
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
isAppFocused() {
|
2017-02-05 11:08:26 +01:00
|
|
|
return !!this.electron().remote.BrowserWindow.getFocusedWindow();
|
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
showMainWindow() {
|
2020-05-15 22:41:39 +02:00
|
|
|
this.remoteApp().showAndFocusMainWindow();
|
2017-02-05 11:08:26 +01:00
|
|
|
},
|
2019-08-18 10:17:09 +02:00
|
|
|
spawn(config) {
|
2017-02-05 11:08:26 +01:00
|
|
|
const ts = logger.ts();
|
|
|
|
let complete = config.complete;
|
2019-08-22 22:15:04 +02:00
|
|
|
const ps = this.req('child_process').spawn(config.cmd, config.args);
|
2020-06-01 16:53:51 +02:00
|
|
|
[ps.stdin, ps.stdout, ps.stderr].forEach((s) => s.setEncoding('utf-8'));
|
2017-02-05 11:08:26 +01:00
|
|
|
let stderr = '';
|
|
|
|
let stdout = '';
|
2020-06-01 16:53:51 +02:00
|
|
|
ps.stderr.on('data', (d) => {
|
2019-08-16 23:05:39 +02:00
|
|
|
stderr += d.toString('utf-8');
|
2020-05-06 19:30:30 +02:00
|
|
|
if (config.throwOnStdErr) {
|
|
|
|
try {
|
|
|
|
ps.kill();
|
|
|
|
} catch {}
|
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
});
|
2020-06-01 16:53:51 +02:00
|
|
|
ps.stdout.on('data', (d) => {
|
2019-08-16 23:05:39 +02:00
|
|
|
stdout += d.toString('utf-8');
|
|
|
|
});
|
2020-06-01 16:53:51 +02:00
|
|
|
ps.on('close', (code) => {
|
2017-02-05 11:08:26 +01:00
|
|
|
stdout = stdout.trim();
|
|
|
|
stderr = stderr.trim();
|
|
|
|
const msg = 'spawn ' + config.cmd + ': ' + code + ', ' + logger.ts(ts);
|
2020-04-15 16:50:01 +02:00
|
|
|
if (code !== 0) {
|
2017-02-05 11:08:26 +01:00
|
|
|
logger.error(msg + '\n' + stdout + '\n' + stderr);
|
|
|
|
} else {
|
2020-04-15 16:50:01 +02:00
|
|
|
logger.info(msg + (stdout && !config.noStdOutLogging ? '\n' + stdout : ''));
|
2017-02-05 11:08:26 +01:00
|
|
|
}
|
|
|
|
if (complete) {
|
2020-05-07 19:00:18 +02:00
|
|
|
complete(code !== 0 ? 'Exit code ' + code : null, stdout, code);
|
2017-02-05 11:08:26 +01:00
|
|
|
complete = null;
|
|
|
|
}
|
|
|
|
});
|
2020-06-01 16:53:51 +02:00
|
|
|
ps.on('error', (err) => {
|
2017-02-05 11:08:26 +01:00
|
|
|
logger.error('spawn error: ' + config.cmd + ', ' + logger.ts(ts), err);
|
|
|
|
if (complete) {
|
|
|
|
complete(err);
|
|
|
|
complete = null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (config.data) {
|
|
|
|
try {
|
2019-08-22 22:15:04 +02:00
|
|
|
ps.stdin.end(config.data);
|
2017-02-05 11:08:26 +01:00
|
|
|
} catch (e) {
|
|
|
|
logger.error('spawn write error', e);
|
|
|
|
}
|
|
|
|
}
|
2019-08-22 22:15:04 +02:00
|
|
|
process.nextTick(() => {
|
|
|
|
// it should work without destroy, but a process doesn't get launched
|
|
|
|
// xubuntu-desktop 19.04 / xfce
|
|
|
|
// see https://github.com/keeweb/keeweb/issues/1234
|
|
|
|
ps.stdin.destroy();
|
|
|
|
});
|
2017-02-05 11:08:26 +01:00
|
|
|
return ps;
|
2017-12-03 20:31:54 +01:00
|
|
|
},
|
|
|
|
checkOpenFiles() {
|
|
|
|
this.readyToOpenFiles = true;
|
|
|
|
if (this.pendingFileToOpen) {
|
|
|
|
this.openFile(this.pendingFileToOpen);
|
|
|
|
delete this.pendingFileToOpen;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
openFile(file) {
|
|
|
|
if (this.readyToOpenFiles) {
|
2019-09-16 22:57:56 +02:00
|
|
|
Events.emit('launcher-open-file', file);
|
2017-12-03 20:31:54 +01:00
|
|
|
} else {
|
|
|
|
this.pendingFileToOpen = file;
|
|
|
|
}
|
2019-09-14 16:36:30 +02:00
|
|
|
},
|
|
|
|
setGlobalShortcuts(appSettings) {
|
|
|
|
this.remoteApp().setGlobalShortcuts(appSettings);
|
2017-02-05 11:08:26 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-09-16 22:57:56 +02:00
|
|
|
Events.on('launcher-exit-request', () => {
|
2017-02-05 11:08:26 +01:00
|
|
|
setTimeout(() => Launcher.exit(), 0);
|
|
|
|
});
|
2019-09-16 22:57:56 +02:00
|
|
|
Events.on('launcher-minimize', () => setTimeout(() => Events.emit('app-minimized'), 0));
|
2019-10-06 13:14:12 +02:00
|
|
|
Events.on('launcher-started-minimized', () => setTimeout(() => Launcher.minimizeApp(), 0));
|
2020-06-01 16:53:51 +02:00
|
|
|
Events.on('start-profile', (data) => StartProfiler.reportAppProfile(data));
|
|
|
|
Events.on('log', (e) => new Logger(e.category || 'remote-app')[e.method || 'info'](e.message));
|
2019-10-06 13:14:12 +02:00
|
|
|
|
2020-06-01 16:53:51 +02:00
|
|
|
window.launcherOpen = (file) => Launcher.openFile(file);
|
2017-02-05 11:08:26 +01:00
|
|
|
if (window.launcherOpenedFile) {
|
|
|
|
logger.info('Open file request', window.launcherOpenedFile);
|
2017-12-03 20:31:54 +01:00
|
|
|
Launcher.openFile(window.launcherOpenedFile);
|
2017-02-05 11:08:26 +01:00
|
|
|
delete window.launcherOpenedFile;
|
2015-10-22 20:03:44 +02:00
|
|
|
}
|
2019-10-26 22:56:36 +02:00
|
|
|
Events.on('app-ready', () =>
|
|
|
|
setTimeout(() => {
|
|
|
|
Launcher.checkOpenFiles();
|
|
|
|
Launcher.remoteApp().setAboutPanelOptions({
|
|
|
|
applicationVersion: RuntimeInfo.version,
|
|
|
|
version: RuntimeInfo.commit
|
|
|
|
});
|
|
|
|
}, 0)
|
|
|
|
);
|
2015-10-22 20:03:44 +02:00
|
|
|
|
2020-06-01 16:59:32 +02:00
|
|
|
if (process.platform === 'darwin') {
|
|
|
|
Launcher.remoteApp().setHookBeforeQuitEvent(true);
|
|
|
|
}
|
|
|
|
|
2020-06-01 16:53:51 +02:00
|
|
|
Launcher.remoteApp().on('remote-app-event', (e) => {
|
2020-06-01 14:42:06 +02:00
|
|
|
if (window.debugRemoteAppEvents) {
|
|
|
|
logger.debug('remote-app-event', e.name);
|
|
|
|
}
|
2020-04-11 18:24:23 +02:00
|
|
|
Events.emit(e.name, e.data);
|
|
|
|
});
|
2019-09-21 07:39:03 +02:00
|
|
|
|
2019-09-15 14:16:32 +02:00
|
|
|
export { Launcher };
|