keeweb/app/scripts/comp/launcher-electron.js

305 lines
9.8 KiB
JavaScript

const Backbone = require('backbone');
const Locale = require('../util/locale');
const Logger = require('../util/logger');
const logger = new Logger('launcher');
const Launcher = {
name: 'electron',
version: window.process.versions.electron,
autoTypeSupported: true,
thirdPartyStoragesSupported: true,
clipboardSupported: true,
req: window.require,
platform: function() {
return process.platform;
},
electron: function() {
return this.req('electron');
},
remoteApp: function() {
return this.electron().remote.app;
},
remReq: function(mod) {
return this.electron().remote.require(mod);
},
openLink: function(href) {
this.electron().shell.openExternal(href);
},
devTools: true,
openDevTools: function() {
this.electron()
.remote.getCurrentWindow()
.openDevTools({ mode: 'bottom' });
},
getSaveFileName: function(defaultPath, callback) {
if (defaultPath) {
const homePath = this.remReq('electron').app.getPath('userDesktop');
defaultPath = this.joinPath(homePath, defaultPath);
}
this.remReq('electron').dialog.showSaveDialog(
{
title: Locale.launcherSave,
defaultPath: defaultPath,
filters: [{ name: Locale.launcherFileFilter, extensions: ['kdbx'] }]
},
callback
);
},
getUserDataPath: function(fileName) {
if (!this.userDataPath) {
const realUserDataPath = this.remoteApp().getPath('userData');
const suffixReplacementRegex = /[\\/]temp[\\/]\d+\.\d+[\\/]?$/;
this.userDataPath = realUserDataPath.replace(suffixReplacementRegex, '');
}
return this.joinPath(this.userDataPath, fileName || '');
},
getTempPath: function(fileName) {
return this.joinPath(this.remoteApp().getPath('temp'), fileName || '');
},
getDocumentsPath: function(fileName) {
return this.joinPath(this.remoteApp().getPath('documents'), fileName || '');
},
getAppPath: function(fileName) {
const dirname = this.req('path').dirname;
const appPath = __dirname.endsWith('app.asar') ? __dirname : this.remoteApp().getAppPath();
return this.joinPath(dirname(appPath), fileName || '');
},
getWorkDirPath: function(fileName) {
return this.joinPath(process.cwd(), fileName || '');
},
joinPath: function(...parts) {
return this.req('path').join(...parts);
},
writeFile: function(path, data, callback) {
this.req('fs').writeFile(path, window.Buffer.from(data), callback);
},
readFile: function(path, encoding, callback) {
this.req('fs').readFile(path, encoding, (err, contents) => {
const data = typeof contents === 'string' ? contents : new Uint8Array(contents);
callback(data, err);
});
},
fileExists: function(path, callback) {
this.req('fs').exists(path, callback);
},
deleteFile: function(path, callback) {
this.req('fs').unlink(path, callback || _.noop);
},
statFile: function(path, callback) {
this.req('fs').stat(path, (err, stats) => callback(stats, err));
},
mkdir: function(dir, callback) {
const fs = this.req('fs');
const path = this.req('path');
const stack = [];
const collect = function(dir, stack, callback) {
fs.exists(dir, exists => {
if (exists) {
return callback();
}
stack.unshift(dir);
const newDir = path.dirname(dir);
if (newDir === dir || !newDir || newDir === '.' || newDir === '/') {
return callback();
}
collect(newDir, stack, callback);
});
};
const create = function(stack, callback) {
if (!stack.length) {
return callback();
}
fs.mkdir(stack.shift(), err => (err ? callback(err) : create(stack, callback)));
};
collect(dir, stack, () => create(stack, callback));
},
parsePath: function(fileName) {
const path = this.req('path');
return {
path: fileName,
dir: path.dirname(fileName),
file: path.basename(fileName)
};
},
createFsWatcher: function(path) {
return this.req('fs').watch(path, { persistent: false });
},
ensureRunnable: function(path) {
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);
}
}
},
preventExit: function(e) {
e.returnValue = false;
return false;
},
exit: function() {
this.exitRequested = true;
this.requestExit();
},
requestExit: function() {
const app = this.remoteApp();
if (this.restartPending) {
app.restartApp();
} else {
app.quit();
}
},
requestRestart: function() {
this.restartPending = true;
this.requestExit();
},
cancelRestart: function() {
this.restartPending = false;
},
setClipboardText: function(text) {
return this.electron().clipboard.writeText(text);
},
getClipboardText: function() {
return this.electron().clipboard.readText();
},
clearClipboardText: function() {
return this.electron().clipboard.clear();
},
minimizeApp: function() {
this.remoteApp().minimizeApp();
},
canMinimize: function() {
return process.platform !== 'darwin';
},
canDetectOsSleep: function() {
return process.platform !== 'linux';
},
updaterEnabled: function() {
return this.electron().remote.process.argv.indexOf('--disable-updater') === -1;
},
getMainWindow: function() {
return this.remoteApp().getMainWindow();
},
resolveProxy: function(url, callback) {
const window = this.getMainWindow();
const session = window.webContents.session;
session.resolveProxy(url, proxy => {
const match = /^proxy\s+([\w\.]+):(\d+)+\s*/i.exec(proxy);
proxy = match && match[1] ? { host: match[1], port: +match[2] } : null;
callback(proxy);
});
},
openWindow: function(opts) {
return this.remoteApp().openWindow(opts);
},
hideApp: function() {
const app = this.remoteApp();
if (this.canMinimize()) {
app.minimizeThenHideIfInTray();
} else {
app.hide();
}
},
isAppFocused: function() {
return !!this.electron().remote.BrowserWindow.getFocusedWindow();
},
showMainWindow: function() {
const win = this.getMainWindow();
win.show();
win.focus();
win.restore();
},
spawn: function(config) {
const ts = logger.ts();
let complete = config.complete;
const ps = this.req('child_process').spawn(config.cmd, config.args);
[ps.stdin, ps.stdout, ps.stderr].forEach(s => s.setEncoding('utf-8'));
let stderr = '';
let stdout = '';
ps.stderr.on('data', d => {
stderr += d.toString('utf-8');
});
ps.stdout.on('data', d => {
stdout += d.toString('utf-8');
});
ps.on('close', code => {
stdout = stdout.trim();
stderr = stderr.trim();
const msg = 'spawn ' + config.cmd + ': ' + code + ', ' + logger.ts(ts);
if (code) {
logger.error(msg + '\n' + stdout + '\n' + stderr);
} else {
logger.info(msg + (stdout ? '\n' + stdout : ''));
}
if (complete) {
complete(code ? 'Exit code ' + code : null, stdout, code);
complete = null;
}
});
ps.on('error', err => {
logger.error('spawn error: ' + config.cmd + ', ' + logger.ts(ts), err);
if (complete) {
complete(err);
complete = null;
}
});
if (config.data) {
try {
ps.stdin.write(config.data);
ps.stdin.end();
} catch (e) {
logger.error('spawn write error', e);
}
}
return ps;
},
getCookies(callback) {
this.electron().remote.session.defaultSession.cookies.get({}, callback);
},
setCookies(cookies) {
if (cookies && cookies.length) {
const session = this.electron().remote.session.defaultSession;
for (const cookie of cookies) {
session.cookies.set(cookie, () => {});
}
}
},
checkOpenFiles() {
this.readyToOpenFiles = true;
if (this.pendingFileToOpen) {
this.openFile(this.pendingFileToOpen);
delete this.pendingFileToOpen;
}
},
openFile(file) {
if (this.readyToOpenFiles) {
Backbone.trigger('launcher-open-file', file);
} else {
this.pendingFileToOpen = file;
}
}
};
Backbone.on('launcher-exit-request', () => {
setTimeout(() => Launcher.exit(), 0);
});
Backbone.on('launcher-minimize', () => setTimeout(() => Backbone.trigger('app-minimized'), 0));
window.launcherOpen = file => Launcher.openFile(file);
if (window.launcherOpenedFile) {
logger.info('Open file request', window.launcherOpenedFile);
Launcher.openFile(window.launcherOpenedFile);
delete window.launcherOpenedFile;
}
Backbone.on('app-ready', () => setTimeout(() => Launcher.checkOpenFiles(), 0));
module.exports = Launcher;