2019-12-06 19:17:34 +01:00
|
|
|
/*
|
|
|
|
Copyright 2016 Aviral Dasgupta
|
|
|
|
Copyright 2016 OpenMarket Ltd
|
|
|
|
Copyright 2017, 2019 Michael Telatynski <7t3chguy@gmail.com>
|
2021-01-13 16:21:00 +01:00
|
|
|
Copyright 2018 - 2021 New Vector Ltd
|
2019-12-06 19:17:34 +01:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Squirrel on windows starts the app with various flags
|
|
|
|
// as hooks to tell us when we've been installed/uninstalled
|
|
|
|
// etc.
|
|
|
|
const checkSquirrelHooks = require('./squirrelhooks');
|
|
|
|
if (checkSquirrelHooks()) return;
|
|
|
|
|
|
|
|
const argv = require('minimist')(process.argv, {
|
|
|
|
alias: {help: "h"},
|
|
|
|
});
|
|
|
|
|
2021-03-29 13:10:27 +02:00
|
|
|
const {
|
2021-04-01 10:30:15 +02:00
|
|
|
app, ipcMain, powerSaveBlocker, BrowserWindow, Menu, autoUpdater, protocol, dialog,
|
2021-03-29 13:10:27 +02:00
|
|
|
} = require('electron');
|
2019-12-06 19:17:34 +01:00
|
|
|
const AutoLaunch = require('auto-launch');
|
|
|
|
const path = require('path');
|
|
|
|
|
|
|
|
const tray = require('./tray');
|
|
|
|
const vectorMenu = require('./vectormenu');
|
|
|
|
const webContentsHandler = require('./webcontents-handler');
|
|
|
|
const updater = require('./updater');
|
2020-04-14 14:29:47 +02:00
|
|
|
const {getProfileFromDeeplink, protocolInit, recordSSOSession} = require('./protocol');
|
2019-12-06 19:17:34 +01:00
|
|
|
|
|
|
|
const windowStateKeeper = require('electron-window-state');
|
|
|
|
const Store = require('electron-store');
|
|
|
|
|
|
|
|
const fs = require('fs');
|
|
|
|
const afs = fs.promises;
|
|
|
|
|
2020-05-28 21:07:39 +02:00
|
|
|
const crypto = require('crypto');
|
|
|
|
let keytar;
|
|
|
|
try {
|
|
|
|
keytar = require('keytar');
|
|
|
|
} catch (e) {
|
|
|
|
if (e.code === "MODULE_NOT_FOUND") {
|
|
|
|
console.log("Keytar isn't installed; secure key storage is disabled.");
|
|
|
|
} else {
|
|
|
|
console.warn("Keytar unexpected error:", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-11 11:27:00 +01:00
|
|
|
let seshatSupported = false;
|
|
|
|
let Seshat;
|
|
|
|
let SeshatRecovery;
|
|
|
|
let ReindexError;
|
|
|
|
|
2020-10-13 15:59:21 +02:00
|
|
|
const seshatDefaultPassphrase = "DEFAULT_PASSPHRASE";
|
2019-12-06 19:17:34 +01:00
|
|
|
|
|
|
|
try {
|
2020-03-11 12:05:34 +01:00
|
|
|
const seshatModule = require('matrix-seshat');
|
2020-03-11 11:27:00 +01:00
|
|
|
Seshat = seshatModule.Seshat;
|
|
|
|
SeshatRecovery = seshatModule.SeshatRecovery;
|
|
|
|
ReindexError = seshatModule.ReindexError;
|
|
|
|
seshatSupported = true;
|
2019-12-06 19:17:34 +01:00
|
|
|
} catch (e) {
|
2020-02-03 17:19:16 +01:00
|
|
|
if (e.code === "MODULE_NOT_FOUND") {
|
|
|
|
console.log("Seshat isn't installed, event indexing is disabled.");
|
|
|
|
} else {
|
|
|
|
console.warn("Seshat unexpected error:", e);
|
|
|
|
}
|
2019-12-06 19:17:34 +01:00
|
|
|
}
|
|
|
|
|
2019-12-10 18:40:17 +01:00
|
|
|
// Things we need throughout the file but need to be created
|
|
|
|
// async to are initialised in setupGlobals()
|
|
|
|
let asarPath;
|
|
|
|
let resPath;
|
|
|
|
let vectorConfig;
|
|
|
|
let iconPath;
|
|
|
|
let trayConfig;
|
|
|
|
let launcher;
|
|
|
|
|
2019-12-06 19:17:34 +01:00
|
|
|
if (argv["help"]) {
|
|
|
|
console.log("Options:");
|
|
|
|
console.log(" --profile-dir {path}: Path to where to store the profile.");
|
|
|
|
console.log(" --profile {name}: Name of alternate profile to use, allows for running multiple accounts.");
|
|
|
|
console.log(" --devtools: Install and use react-devtools and react-perf.");
|
|
|
|
console.log(" --no-update: Disable automatic updating.");
|
|
|
|
console.log(" --hidden: Start the application hidden in the system tray.");
|
|
|
|
console.log(" --help: Displays this help message.");
|
|
|
|
console.log("And more such as --proxy, see:" +
|
2020-05-14 12:27:26 +02:00
|
|
|
"https://electronjs.org/docs/api/command-line-switches");
|
2019-12-06 19:17:34 +01:00
|
|
|
app.exit();
|
|
|
|
}
|
|
|
|
|
2020-07-15 12:41:30 +02:00
|
|
|
// Electron creates the user data directory (with just an empty 'Dictionaries' directory...)
|
|
|
|
// as soon as the app path is set, so pick a random path in it that must exist if it's a
|
|
|
|
// real user data directory.
|
|
|
|
function isRealUserDataDir(d) {
|
|
|
|
return fs.existsSync(path.join(d, 'IndexedDB'));
|
|
|
|
}
|
|
|
|
|
2020-04-14 14:29:47 +02:00
|
|
|
// check if we are passed a profile in the SSO callback url
|
2020-07-07 19:57:29 +02:00
|
|
|
let userDataPath;
|
|
|
|
|
2020-04-14 14:29:47 +02:00
|
|
|
const userDataPathInProtocol = getProfileFromDeeplink(argv["_"]);
|
|
|
|
if (userDataPathInProtocol) {
|
2020-07-07 19:57:29 +02:00
|
|
|
userDataPath = userDataPathInProtocol;
|
2020-04-14 14:29:47 +02:00
|
|
|
} else if (argv['profile-dir']) {
|
2020-07-07 19:57:29 +02:00
|
|
|
userDataPath = argv['profile-dir'];
|
2020-07-02 14:30:11 +02:00
|
|
|
} else {
|
2020-07-21 18:57:54 +02:00
|
|
|
let newUserDataPath = app.getPath('userData');
|
2020-07-02 14:30:11 +02:00
|
|
|
if (argv['profile']) {
|
|
|
|
newUserDataPath += '-' + argv['profile'];
|
|
|
|
}
|
|
|
|
const newUserDataPathExists = isRealUserDataDir(newUserDataPath);
|
2020-07-21 18:57:54 +02:00
|
|
|
let oldUserDataPath = path.join(app.getPath('appData'), app.getName().replace('Element', 'Riot'));
|
|
|
|
if (argv['profile']) {
|
|
|
|
oldUserDataPath += '-' + argv['profile'];
|
|
|
|
}
|
|
|
|
|
2020-07-02 14:30:11 +02:00
|
|
|
const oldUserDataPathExists = isRealUserDataDir(oldUserDataPath);
|
|
|
|
console.log(newUserDataPath + " exists: " + (newUserDataPathExists ? 'yes' : 'no'));
|
|
|
|
console.log(oldUserDataPath + " exists: " + (oldUserDataPathExists ? 'yes' : 'no'));
|
|
|
|
if (!newUserDataPathExists && oldUserDataPathExists) {
|
|
|
|
console.log("Using legacy user data path: " + oldUserDataPath);
|
2020-07-07 19:57:29 +02:00
|
|
|
userDataPath = oldUserDataPath;
|
|
|
|
} else {
|
|
|
|
userDataPath = newUserDataPath;
|
2020-07-02 14:30:11 +02:00
|
|
|
}
|
2019-12-06 19:17:34 +01:00
|
|
|
}
|
2020-07-07 19:57:29 +02:00
|
|
|
app.setPath('userData', userDataPath);
|
2019-12-06 19:17:34 +01:00
|
|
|
|
2020-04-16 14:08:16 +02:00
|
|
|
async function tryPaths(name, root, rawPaths) {
|
|
|
|
// Make everything relative to root
|
|
|
|
const paths = rawPaths.map(p => path.join(root, p));
|
2019-12-10 18:40:17 +01:00
|
|
|
|
|
|
|
for (const p of paths) {
|
|
|
|
try {
|
|
|
|
await afs.stat(p);
|
|
|
|
return p + '/';
|
|
|
|
} catch (e) {
|
|
|
|
}
|
|
|
|
}
|
2020-04-16 14:08:16 +02:00
|
|
|
console.log(`Couldn't find ${name} files in any of: `);
|
2019-12-10 18:40:17 +01:00
|
|
|
for (const p of paths) {
|
|
|
|
console.log("\t"+path.resolve(p));
|
|
|
|
}
|
2020-04-16 14:08:16 +02:00
|
|
|
throw new Error(`Failed to find ${name} files`);
|
2019-12-06 19:17:34 +01:00
|
|
|
}
|
|
|
|
|
2019-12-10 18:40:17 +01:00
|
|
|
// Find the webapp resources and set up things that require them
|
|
|
|
async function setupGlobals() {
|
|
|
|
// find the webapp asar.
|
2020-04-16 14:08:16 +02:00
|
|
|
asarPath = await tryPaths("webapp", __dirname, [
|
2019-12-10 18:40:17 +01:00
|
|
|
// If run from the source checkout, this will be in the directory above
|
|
|
|
'../webapp.asar',
|
|
|
|
// but if run from a packaged application, electron-main.js will be in
|
|
|
|
// a different asar file so it will be two levels above
|
|
|
|
'../../webapp.asar',
|
2020-04-16 14:08:16 +02:00
|
|
|
// also try without the 'asar' suffix to allow symlinking in a directory
|
2019-12-10 18:40:17 +01:00
|
|
|
'../webapp',
|
2020-04-16 14:08:16 +02:00
|
|
|
// from a packaged application
|
|
|
|
'../../webapp',
|
2019-12-10 18:40:17 +01:00
|
|
|
]);
|
2020-04-16 14:08:16 +02:00
|
|
|
|
2019-12-10 18:40:17 +01:00
|
|
|
// we assume the resources path is in the same place as the asar
|
2020-04-16 14:08:16 +02:00
|
|
|
resPath = await tryPaths("res", path.dirname(asarPath), [
|
|
|
|
// If run from the source checkout
|
|
|
|
'res',
|
|
|
|
// if run from packaged application
|
|
|
|
'',
|
|
|
|
]);
|
2019-12-10 18:40:17 +01:00
|
|
|
|
|
|
|
try {
|
|
|
|
vectorConfig = require(asarPath + 'config.json');
|
|
|
|
} catch (e) {
|
|
|
|
// it would be nice to check the error code here and bail if the config
|
2020-11-14 22:54:07 +01:00
|
|
|
// is unparsable, but we get MODULE_NOT_FOUND in the case of a missing
|
2019-12-10 18:40:17 +01:00
|
|
|
// file or invalid json, so node is just very unhelpful.
|
|
|
|
// Continue with the defaults (ie. an empty config)
|
|
|
|
vectorConfig = {};
|
2019-12-06 19:17:34 +01:00
|
|
|
}
|
|
|
|
|
2019-12-10 18:40:17 +01:00
|
|
|
try {
|
|
|
|
// Load local config and use it to override values from the one baked with the build
|
|
|
|
const localConfig = require(path.join(app.getPath('userData'), 'config.json'));
|
|
|
|
|
|
|
|
// If the local config has a homeserver defined, don't use the homeserver from the build
|
|
|
|
// config. This is to avoid a problem where Riot thinks there are multiple homeservers
|
|
|
|
// defined, and panics as a result.
|
|
|
|
const homeserverProps = ['default_is_url', 'default_hs_url', 'default_server_name', 'default_server_config'];
|
|
|
|
if (Object.keys(localConfig).find(k => homeserverProps.includes(k))) {
|
|
|
|
// Rip out all the homeserver options from the vector config
|
|
|
|
vectorConfig = Object.keys(vectorConfig)
|
|
|
|
.filter(k => !homeserverProps.includes(k))
|
|
|
|
.reduce((obj, key) => {obj[key] = vectorConfig[key]; return obj;}, {});
|
|
|
|
}
|
|
|
|
|
|
|
|
vectorConfig = Object.assign(vectorConfig, localConfig);
|
|
|
|
} catch (e) {
|
|
|
|
// Could not load local config, this is expected in most cases.
|
|
|
|
}
|
|
|
|
|
|
|
|
// The tray icon
|
|
|
|
// It's important to call `path.join` so we don't end up with the packaged asar in the final path.
|
2020-07-15 14:50:00 +02:00
|
|
|
const iconFile = `element.${process.platform === 'win32' ? 'ico' : 'png'}`;
|
2019-12-10 18:40:17 +01:00
|
|
|
iconPath = path.join(resPath, "img", iconFile);
|
|
|
|
trayConfig = {
|
|
|
|
icon_path: iconPath,
|
2020-07-01 16:40:23 +02:00
|
|
|
brand: vectorConfig.brand || 'Element',
|
2019-12-10 18:40:17 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
// launcher
|
|
|
|
launcher = new AutoLaunch({
|
2020-07-02 14:30:11 +02:00
|
|
|
name: vectorConfig.brand || 'Element',
|
2019-12-10 18:40:17 +01:00
|
|
|
isHidden: true,
|
|
|
|
mac: {
|
|
|
|
useLaunchAgent: true,
|
|
|
|
},
|
|
|
|
});
|
2019-12-06 19:17:34 +01:00
|
|
|
}
|
|
|
|
|
2020-07-02 14:30:11 +02:00
|
|
|
async function moveAutoLauncher() {
|
2020-07-02 15:42:45 +02:00
|
|
|
// Look for an auto-launcher under 'Riot' and if we find one, port it's
|
2020-07-02 14:30:11 +02:00
|
|
|
// enabled/disbaledp-ness over to the new 'Element' launcher
|
|
|
|
if (!vectorConfig.brand || vectorConfig.brand === 'Element') {
|
|
|
|
const oldLauncher = new AutoLaunch({
|
|
|
|
name: 'Riot',
|
|
|
|
isHidden: true,
|
|
|
|
mac: {
|
|
|
|
useLaunchAgent: true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const wasEnabled = await oldLauncher.isEnabled();
|
|
|
|
if (wasEnabled) {
|
|
|
|
await oldLauncher.disable();
|
|
|
|
await launcher.enable();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-06 19:17:34 +01:00
|
|
|
const eventStorePath = path.join(app.getPath('userData'), 'EventStore');
|
|
|
|
const store = new Store({ name: "electron-config" });
|
|
|
|
|
|
|
|
let eventIndex = null;
|
|
|
|
|
|
|
|
let mainWindow = null;
|
|
|
|
global.appQuitting = false;
|
|
|
|
|
2021-03-31 09:58:24 +02:00
|
|
|
const exitShortcuts = [
|
|
|
|
(input, platform) => platform !== 'darwin' && input.alt && input.code === 'F4',
|
|
|
|
(input, platform) => platform !== 'darwin' && input.control && input.code === 'KeyQ',
|
|
|
|
(input, platform) => platform === 'darwin' && input.meta && input.code === 'KeyQ',
|
|
|
|
];
|
|
|
|
|
|
|
|
const warnBeforeExit = (event, input) => {
|
|
|
|
const shouldWarnBeforeExit = store.get('warnBeforeExit', true);
|
2021-03-31 18:18:39 +02:00
|
|
|
const exitShortcutPressed = exitShortcuts.some(shortcutFn => shortcutFn(input, process.platform));
|
2021-03-31 09:58:24 +02:00
|
|
|
|
2021-03-31 18:18:39 +02:00
|
|
|
if (shouldWarnBeforeExit && exitShortcutPressed) {
|
2021-03-29 13:10:27 +02:00
|
|
|
const shouldCancelCloseRequest = dialog.showMessageBoxSync(mainWindow, {
|
|
|
|
type: "question",
|
|
|
|
buttons: ["Cancel", "Close Element"],
|
|
|
|
message: "Are you sure you want to quit?",
|
|
|
|
defaultId: 1,
|
|
|
|
cancelId: 0,
|
|
|
|
}) === 0;
|
|
|
|
|
|
|
|
if (shouldCancelCloseRequest) {
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2020-06-23 16:42:27 +02:00
|
|
|
|
2020-06-24 11:06:09 +02:00
|
|
|
const deleteContents = async (p) => {
|
2020-06-23 16:42:27 +02:00
|
|
|
for (const entry of await afs.readdir(p)) {
|
|
|
|
const curPath = path.join(p, entry);
|
|
|
|
await afs.unlink(curPath);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-13 15:58:35 +02:00
|
|
|
async function randomArray(size) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
crypto.randomBytes(size, (err, buf) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
} else {
|
|
|
|
resolve(buf.toString("base64").replace(/=+$/g, ''));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-12-06 19:17:34 +01:00
|
|
|
// handle uncaught errors otherwise it displays
|
|
|
|
// stack traces in popup dialogs, which is terrible (which
|
|
|
|
// it will do any time the auto update poke fails, and there's
|
|
|
|
// no other way to catch this error).
|
|
|
|
// Assuming we generally run from the console when developing,
|
|
|
|
// this is far preferable.
|
|
|
|
process.on('uncaughtException', function(error) {
|
|
|
|
console.log('Unhandled exception', error);
|
|
|
|
});
|
|
|
|
|
|
|
|
let focusHandlerAttached = false;
|
|
|
|
ipcMain.on('setBadgeCount', function(ev, count) {
|
2019-12-10 19:02:20 +01:00
|
|
|
app.badgeCount = count;
|
2019-12-06 19:17:34 +01:00
|
|
|
if (count === 0 && mainWindow) {
|
|
|
|
mainWindow.flashFrame(false);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
ipcMain.on('loudNotification', function() {
|
|
|
|
if (process.platform === 'win32' && mainWindow && !mainWindow.isFocused() && !focusHandlerAttached) {
|
|
|
|
mainWindow.flashFrame(true);
|
|
|
|
mainWindow.once('focus', () => {
|
|
|
|
mainWindow.flashFrame(false);
|
|
|
|
focusHandlerAttached = false;
|
|
|
|
});
|
|
|
|
focusHandlerAttached = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let powerSaveBlockerId = null;
|
|
|
|
ipcMain.on('app_onAction', function(ev, payload) {
|
|
|
|
switch (payload.action) {
|
|
|
|
case 'call_state':
|
|
|
|
if (powerSaveBlockerId !== null && powerSaveBlocker.isStarted(powerSaveBlockerId)) {
|
|
|
|
if (payload.state === 'ended') {
|
|
|
|
powerSaveBlocker.stop(powerSaveBlockerId);
|
|
|
|
powerSaveBlockerId = null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (powerSaveBlockerId === null && payload.state === 'connected') {
|
|
|
|
powerSaveBlockerId = powerSaveBlocker.start('prevent-display-sleep');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
ipcMain.on('ipcCall', async function(ev, payload) {
|
|
|
|
if (!mainWindow) return;
|
|
|
|
|
|
|
|
const args = payload.args || [];
|
|
|
|
let ret;
|
|
|
|
|
|
|
|
switch (payload.name) {
|
|
|
|
case 'getUpdateFeedUrl':
|
|
|
|
ret = autoUpdater.getFeedURL();
|
|
|
|
break;
|
|
|
|
case 'getAutoLaunchEnabled':
|
|
|
|
ret = await launcher.isEnabled();
|
|
|
|
break;
|
|
|
|
case 'setAutoLaunchEnabled':
|
|
|
|
if (args[0]) {
|
|
|
|
launcher.enable();
|
|
|
|
} else {
|
|
|
|
launcher.disable();
|
|
|
|
}
|
|
|
|
break;
|
2021-03-25 15:15:04 +01:00
|
|
|
case 'shouldWarnBeforeExit':
|
|
|
|
ret = store.get('warnBeforeExit', true);
|
|
|
|
break;
|
|
|
|
case 'setWarnBeforeExit':
|
|
|
|
store.set('warnBeforeExit', args[0]);
|
|
|
|
break;
|
2019-12-06 19:17:34 +01:00
|
|
|
case 'getMinimizeToTrayEnabled':
|
|
|
|
ret = tray.hasTray();
|
|
|
|
break;
|
|
|
|
case 'setMinimizeToTrayEnabled':
|
|
|
|
if (args[0]) {
|
|
|
|
// Create trayIcon icon
|
|
|
|
tray.create(trayConfig);
|
|
|
|
} else {
|
|
|
|
tray.destroy();
|
|
|
|
}
|
|
|
|
store.set('minimizeToTray', args[0]);
|
|
|
|
break;
|
|
|
|
case 'getAutoHideMenuBarEnabled':
|
2020-03-03 11:00:09 +01:00
|
|
|
ret = global.mainWindow.autoHideMenuBar;
|
2019-12-06 19:17:34 +01:00
|
|
|
break;
|
|
|
|
case 'setAutoHideMenuBarEnabled':
|
|
|
|
store.set('autoHideMenuBar', args[0]);
|
2020-03-05 12:12:01 +01:00
|
|
|
global.mainWindow.autoHideMenuBar = Boolean(args[0]);
|
2019-12-06 19:17:34 +01:00
|
|
|
global.mainWindow.setMenuBarVisibility(!args[0]);
|
|
|
|
break;
|
|
|
|
case 'getAppVersion':
|
|
|
|
ret = app.getVersion();
|
|
|
|
break;
|
|
|
|
case 'focusWindow':
|
|
|
|
if (mainWindow.isMinimized()) {
|
|
|
|
mainWindow.restore();
|
|
|
|
} else if (!mainWindow.isVisible()) {
|
|
|
|
mainWindow.show();
|
|
|
|
} else {
|
|
|
|
mainWindow.focus();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'getConfig':
|
|
|
|
ret = vectorConfig;
|
|
|
|
break;
|
2020-04-14 16:04:58 +02:00
|
|
|
case 'navigateBack':
|
|
|
|
if (mainWindow.webContents.canGoBack()) {
|
|
|
|
mainWindow.webContents.goBack();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'navigateForward':
|
|
|
|
if (mainWindow.webContents.canGoForward()) {
|
|
|
|
mainWindow.webContents.goForward();
|
|
|
|
}
|
|
|
|
break;
|
2020-12-01 20:19:12 +01:00
|
|
|
case 'setSpellCheckLanguages':
|
2021-02-18 18:05:37 +01:00
|
|
|
if (args[0] && args[0].length > 0) {
|
2021-02-18 18:39:51 +01:00
|
|
|
try {
|
|
|
|
mainWindow.webContents.session.setSpellCheckerLanguages(args[0]);
|
|
|
|
} catch (er) {
|
|
|
|
console.log("There were problems setting the spellcheck languages", er);
|
|
|
|
}
|
2020-12-01 20:27:09 +01:00
|
|
|
} else {
|
2021-03-03 14:49:07 +01:00
|
|
|
mainWindow.webContents.session.setSpellCheckerLanguages([]);
|
2020-02-24 18:16:35 +01:00
|
|
|
}
|
|
|
|
break;
|
2021-02-18 20:12:47 +01:00
|
|
|
case 'getSpellCheckLanguages':
|
|
|
|
ret = mainWindow.webContents.session.getSpellCheckerLanguages();
|
|
|
|
break;
|
2020-11-29 20:50:57 +01:00
|
|
|
case 'getAvailableSpellCheckLanguages':
|
|
|
|
ret = mainWindow.webContents.session.availableSpellCheckerLanguages;
|
|
|
|
break;
|
|
|
|
|
2020-04-14 14:29:47 +02:00
|
|
|
case 'startSSOFlow':
|
|
|
|
recordSSOSession(args[0]);
|
|
|
|
break;
|
2019-12-06 19:17:34 +01:00
|
|
|
|
2020-05-28 21:07:39 +02:00
|
|
|
case 'getPickleKey':
|
|
|
|
try {
|
2020-07-02 14:30:11 +02:00
|
|
|
ret = await keytar.getPassword("element.io", `${args[0]}|${args[1]}`);
|
|
|
|
// migrate from riot.im (remove once we think there will no longer be
|
|
|
|
// logins from the time of riot.im)
|
|
|
|
if (ret === null) {
|
|
|
|
ret = await keytar.getPassword("riot.im", `${args[0]}|${args[1]}`);
|
|
|
|
}
|
2020-05-28 21:07:39 +02:00
|
|
|
} catch (e) {
|
|
|
|
// if an error is thrown (e.g. keytar can't connect to the keychain),
|
|
|
|
// then return null, which means the default pickle key will be used
|
|
|
|
ret = null;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'createPickleKey':
|
|
|
|
try {
|
2020-10-13 15:58:35 +02:00
|
|
|
const pickleKey = await randomArray(32);
|
2020-07-02 14:30:11 +02:00
|
|
|
await keytar.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey);
|
2020-05-28 21:07:39 +02:00
|
|
|
ret = pickleKey;
|
|
|
|
} catch (e) {
|
|
|
|
ret = null;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'destroyPickleKey':
|
|
|
|
try {
|
2020-07-02 14:30:11 +02:00
|
|
|
await keytar.deletePassword("element.io", `${args[0]}|${args[1]}`);
|
|
|
|
// migrate from riot.im (remove once we think there will no longer be
|
|
|
|
// logins from the time of riot.im)
|
2020-05-28 21:07:39 +02:00
|
|
|
await keytar.deletePassword("riot.im", `${args[0]}|${args[1]}`);
|
|
|
|
} catch (e) {}
|
|
|
|
break;
|
|
|
|
|
2019-12-06 19:17:34 +01:00
|
|
|
default:
|
|
|
|
mainWindow.webContents.send('ipcReply', {
|
|
|
|
id: payload.id,
|
|
|
|
error: "Unknown IPC Call: " + payload.name,
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mainWindow.webContents.send('ipcReply', {
|
|
|
|
id: payload.id,
|
|
|
|
reply: ret,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
ipcMain.on('seshat', async function(ev, payload) {
|
|
|
|
if (!mainWindow) return;
|
|
|
|
|
|
|
|
const sendError = (id, e) => {
|
|
|
|
const error = {
|
2019-12-10 19:10:15 +01:00
|
|
|
message: e.message,
|
|
|
|
};
|
2019-12-06 19:17:34 +01:00
|
|
|
|
|
|
|
mainWindow.webContents.send('seshatReply', {
|
2019-12-10 19:10:15 +01:00
|
|
|
id: id,
|
|
|
|
error: error,
|
2019-12-06 19:17:34 +01:00
|
|
|
});
|
2019-12-10 19:10:15 +01:00
|
|
|
};
|
2019-12-06 19:17:34 +01:00
|
|
|
|
|
|
|
const args = payload.args || [];
|
|
|
|
let ret;
|
|
|
|
|
|
|
|
switch (payload.name) {
|
|
|
|
case 'supportsEventIndexing':
|
2020-03-11 11:27:00 +01:00
|
|
|
ret = seshatSupported;
|
2019-12-06 19:17:34 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'initEventIndex':
|
|
|
|
if (eventIndex === null) {
|
2020-10-13 17:07:39 +02:00
|
|
|
const userId = args[0];
|
|
|
|
const deviceId = args[1];
|
|
|
|
const passphraseKey = `seshat|${userId}|${deviceId}`;
|
|
|
|
|
2020-10-13 15:59:21 +02:00
|
|
|
let changePassphrase = false;
|
|
|
|
let passphrase = seshatDefaultPassphrase;
|
|
|
|
|
|
|
|
if (keytar) {
|
|
|
|
try {
|
|
|
|
// Try to get a passphrase for seshat.
|
|
|
|
const storedPassphrase = await keytar.getPassword("element.io", passphraseKey);
|
|
|
|
|
|
|
|
// If no passphrase was found mark that we should change
|
|
|
|
// it, if one is found, use that one.
|
|
|
|
if (storedPassphrase === null) {
|
|
|
|
changePassphrase = true;
|
|
|
|
} else {
|
|
|
|
passphrase = storedPassphrase;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.log("Error getting the event index passphrase out of the secret store", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const openSeshat = async () => {
|
|
|
|
try {
|
|
|
|
await afs.mkdir(eventStorePath, {recursive: true});
|
|
|
|
return new Seshat(eventStorePath, {passphrase});
|
|
|
|
} catch (e) {
|
|
|
|
if (e instanceof ReindexError) {
|
|
|
|
// If this is a reindex error, the index schema
|
|
|
|
// changed. Try to open the database in recovery mode,
|
|
|
|
// reindex the database and finally try to open the
|
|
|
|
// database again.
|
2020-03-11 12:05:34 +01:00
|
|
|
const recoveryIndex = new SeshatRecovery(eventStorePath, {
|
2020-10-13 15:59:21 +02:00
|
|
|
passphrase,
|
2020-03-11 11:27:00 +01:00
|
|
|
});
|
2020-06-23 16:42:27 +02:00
|
|
|
|
|
|
|
const userVersion = await recoveryIndex.getUserVersion();
|
|
|
|
|
|
|
|
// If our user version is 0 we'll delete the db
|
|
|
|
// anyways so reindexing it is a waste of time.
|
|
|
|
if (userVersion === 0) {
|
|
|
|
await recoveryIndex.shutdown();
|
|
|
|
|
|
|
|
try {
|
2020-06-24 11:06:09 +02:00
|
|
|
await deleteContents(eventStorePath);
|
2020-06-23 16:42:27 +02:00
|
|
|
} catch (e) {
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
await recoveryIndex.reindex();
|
|
|
|
}
|
|
|
|
|
2020-10-13 15:59:21 +02:00
|
|
|
return new Seshat(eventStorePath, {passphrase});
|
|
|
|
} else {
|
|
|
|
throw (e);
|
2020-03-11 11:27:00 +01:00
|
|
|
}
|
2020-10-13 15:59:21 +02:00
|
|
|
}
|
2020-10-13 17:07:39 +02:00
|
|
|
};
|
2020-10-13 15:59:21 +02:00
|
|
|
|
|
|
|
try {
|
|
|
|
eventIndex = await openSeshat();
|
2020-10-13 17:07:39 +02:00
|
|
|
} catch (e) {
|
2020-10-13 15:59:21 +02:00
|
|
|
sendError(payload.id, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (changePassphrase) {
|
|
|
|
try {
|
|
|
|
// Generate a new random passphrase.
|
|
|
|
const newPassphrase = await randomArray(32);
|
|
|
|
await keytar.setPassword("element.io", passphraseKey, newPassphrase);
|
|
|
|
|
|
|
|
// Set the new passphrase, this will close the event
|
|
|
|
// index.
|
|
|
|
await eventIndex.changePassphrase(newPassphrase);
|
|
|
|
|
|
|
|
// Re-open the event index with the new passphrase.
|
|
|
|
eventIndex = new Seshat(eventStorePath, {newPassphrase});
|
2020-10-13 17:07:39 +02:00
|
|
|
} catch (e) {
|
2020-03-11 11:27:00 +01:00
|
|
|
sendError(payload.id, e);
|
|
|
|
return;
|
|
|
|
}
|
2019-12-06 19:17:34 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'closeEventIndex':
|
2020-03-13 11:32:25 +01:00
|
|
|
if (eventIndex !== null) {
|
2020-03-24 14:23:49 +01:00
|
|
|
const index = eventIndex;
|
2020-03-24 14:16:54 +01:00
|
|
|
eventIndex = null;
|
|
|
|
|
2020-03-13 11:32:25 +01:00
|
|
|
try {
|
2020-03-24 14:16:54 +01:00
|
|
|
await index.shutdown();
|
2020-03-13 11:32:25 +01:00
|
|
|
} catch (e) {
|
|
|
|
sendError(payload.id, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2019-12-06 19:17:34 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'deleteEventIndex':
|
2019-12-10 19:10:15 +01:00
|
|
|
{
|
|
|
|
try {
|
2020-06-24 11:06:09 +02:00
|
|
|
await deleteContents(eventStorePath);
|
2019-12-10 19:10:15 +01:00
|
|
|
} catch (e) {
|
|
|
|
}
|
2019-12-06 19:17:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'isEventIndexEmpty':
|
|
|
|
if (eventIndex === null) ret = true;
|
|
|
|
else ret = await eventIndex.isEmpty();
|
|
|
|
break;
|
|
|
|
|
2020-06-08 16:41:15 +02:00
|
|
|
case 'isRoomIndexed':
|
|
|
|
if (eventIndex === null) ret = false;
|
|
|
|
else ret = await eventIndex.isRoomIndexed(args[0]);
|
|
|
|
break;
|
|
|
|
|
2019-12-06 19:17:34 +01:00
|
|
|
case 'addEventToIndex':
|
|
|
|
try {
|
|
|
|
eventIndex.addEvent(args[0], args[1]);
|
|
|
|
} catch (e) {
|
|
|
|
sendError(payload.id, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-03-12 11:46:14 +01:00
|
|
|
case 'deleteEvent':
|
|
|
|
try {
|
|
|
|
ret = await eventIndex.deleteEvent(args[0]);
|
|
|
|
} catch (e) {
|
|
|
|
sendError(payload.id, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2019-12-06 19:17:34 +01:00
|
|
|
case 'commitLiveEvents':
|
|
|
|
try {
|
|
|
|
ret = await eventIndex.commit();
|
|
|
|
} catch (e) {
|
|
|
|
sendError(payload.id, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'searchEventIndex':
|
|
|
|
try {
|
|
|
|
ret = await eventIndex.search(args[0]);
|
|
|
|
} catch (e) {
|
|
|
|
sendError(payload.id, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'addHistoricEvents':
|
|
|
|
if (eventIndex === null) ret = false;
|
|
|
|
else {
|
|
|
|
try {
|
|
|
|
ret = await eventIndex.addHistoricEvents(
|
|
|
|
args[0], args[1], args[2]);
|
|
|
|
} catch (e) {
|
|
|
|
sendError(payload.id, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-02-03 17:23:42 +01:00
|
|
|
case 'getStats':
|
|
|
|
if (eventIndex === null) ret = 0;
|
|
|
|
else {
|
|
|
|
try {
|
|
|
|
ret = await eventIndex.getStats();
|
|
|
|
} catch (e) {
|
|
|
|
sendError(payload.id, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2019-12-06 19:17:34 +01:00
|
|
|
case 'removeCrawlerCheckpoint':
|
|
|
|
if (eventIndex === null) ret = false;
|
|
|
|
else {
|
|
|
|
try {
|
|
|
|
ret = await eventIndex.removeCrawlerCheckpoint(args[0]);
|
|
|
|
} catch (e) {
|
|
|
|
sendError(payload.id, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'addCrawlerCheckpoint':
|
|
|
|
if (eventIndex === null) ret = false;
|
|
|
|
else {
|
|
|
|
try {
|
|
|
|
ret = await eventIndex.addCrawlerCheckpoint(args[0]);
|
|
|
|
} catch (e) {
|
|
|
|
sendError(payload.id, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-02-03 17:21:35 +01:00
|
|
|
case 'loadFileEvents':
|
|
|
|
if (eventIndex === null) ret = [];
|
|
|
|
else {
|
|
|
|
try {
|
|
|
|
ret = await eventIndex.loadFileEvents(args[0]);
|
|
|
|
} catch (e) {
|
|
|
|
sendError(payload.id, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2019-12-06 19:17:34 +01:00
|
|
|
case 'loadCheckpoints':
|
|
|
|
if (eventIndex === null) ret = [];
|
|
|
|
else {
|
|
|
|
try {
|
|
|
|
ret = await eventIndex.loadCheckpoints();
|
|
|
|
} catch (e) {
|
|
|
|
ret = [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-06-17 17:19:15 +02:00
|
|
|
case 'setUserVersion':
|
|
|
|
if (eventIndex === null) break;
|
|
|
|
else {
|
|
|
|
try {
|
|
|
|
await eventIndex.setUserVersion(args[0]);
|
|
|
|
} catch (e) {
|
|
|
|
sendError(payload.id, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'getUserVersion':
|
|
|
|
if (eventIndex === null) ret = 0;
|
|
|
|
else {
|
|
|
|
try {
|
|
|
|
ret = await eventIndex.getUserVersion();
|
|
|
|
} catch (e) {
|
|
|
|
sendError(payload.id, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2019-12-06 19:17:34 +01:00
|
|
|
default:
|
|
|
|
mainWindow.webContents.send('seshatReply', {
|
|
|
|
id: payload.id,
|
|
|
|
error: "Unknown IPC Call: " + payload.name,
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mainWindow.webContents.send('seshatReply', {
|
|
|
|
id: payload.id,
|
|
|
|
reply: ret,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
app.commandLine.appendSwitch('--enable-usermedia-screen-capturing');
|
|
|
|
|
|
|
|
const gotLock = app.requestSingleInstanceLock();
|
|
|
|
if (!gotLock) {
|
|
|
|
console.log('Other instance detected: exiting');
|
|
|
|
app.exit();
|
|
|
|
}
|
|
|
|
|
2020-03-02 16:04:51 +01:00
|
|
|
// do this after we know we are the primary instance of the app
|
|
|
|
protocolInit();
|
|
|
|
|
2019-12-06 19:17:34 +01:00
|
|
|
// Register the scheme the app is served from as 'standard'
|
|
|
|
// which allows things like relative URLs and IndexedDB to
|
|
|
|
// work.
|
|
|
|
// Also mark it as secure (ie. accessing resources from this
|
|
|
|
// protocol and HTTPS won't trigger mixed content warnings).
|
|
|
|
protocol.registerSchemesAsPrivileged([{
|
|
|
|
scheme: 'vector',
|
|
|
|
privileges: {
|
|
|
|
standard: true,
|
|
|
|
secure: true,
|
|
|
|
supportFetchAPI: true,
|
|
|
|
},
|
|
|
|
}]);
|
|
|
|
|
2020-05-21 00:16:57 +02:00
|
|
|
// Turn the sandbox on for *all* windows we might generate. Doing this means we don't
|
|
|
|
// have to specify a `sandbox: true` to each BrowserWindow.
|
|
|
|
//
|
|
|
|
// This also fixes an issue with window.open where if we only specified the sandbox
|
|
|
|
// on the main window we'd run into cryptic "ipc_renderer be broke" errors. Turns out
|
|
|
|
// it's trying to jump the sandbox and make some calls into electron, which it can't
|
|
|
|
// do when half of it is sandboxed. By turning on the sandbox for everything, the new
|
|
|
|
// window (no matter how temporary it may be) is also sandboxed, allowing for a clean
|
|
|
|
// transition into the user's browser.
|
|
|
|
app.enableSandbox();
|
|
|
|
|
2019-12-10 18:40:17 +01:00
|
|
|
app.on('ready', async () => {
|
|
|
|
try {
|
|
|
|
await setupGlobals();
|
2020-07-02 14:30:11 +02:00
|
|
|
await moveAutoLauncher();
|
2019-12-10 18:40:17 +01:00
|
|
|
} catch (e) {
|
|
|
|
console.log("App setup failed: exiting", e);
|
|
|
|
process.exit(1);
|
|
|
|
// process.exit doesn't cause node to stop running code immediately,
|
|
|
|
// so return (we could let the exception propagate but then we end up
|
|
|
|
// with node printing all sorts of stuff about unhandled exceptions
|
|
|
|
// when we want the actual error to be as obvious as possible).
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-12-06 19:17:34 +01:00
|
|
|
if (argv['devtools']) {
|
|
|
|
try {
|
|
|
|
const { default: installExt, REACT_DEVELOPER_TOOLS, REACT_PERF } = require('electron-devtools-installer');
|
|
|
|
installExt(REACT_DEVELOPER_TOOLS)
|
|
|
|
.then((name) => console.log(`Added Extension: ${name}`))
|
|
|
|
.catch((err) => console.log('An error occurred: ', err));
|
|
|
|
installExt(REACT_PERF)
|
|
|
|
.then((name) => console.log(`Added Extension: ${name}`))
|
|
|
|
.catch((err) => console.log('An error occurred: ', err));
|
|
|
|
} catch (e) {
|
|
|
|
console.log(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protocol.registerFileProtocol('vector', (request, callback) => {
|
|
|
|
if (request.method !== 'GET') {
|
|
|
|
callback({error: -322}); // METHOD_NOT_SUPPORTED from chromium/src/net/base/net_error_list.h
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const parsedUrl = new URL(request.url);
|
|
|
|
if (parsedUrl.protocol !== 'vector:') {
|
|
|
|
callback({error: -302}); // UNKNOWN_URL_SCHEME
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (parsedUrl.host !== 'vector') {
|
|
|
|
callback({error: -105}); // NAME_NOT_RESOLVED
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const target = parsedUrl.pathname.split('/');
|
|
|
|
|
|
|
|
// path starts with a '/'
|
|
|
|
if (target[0] !== '') {
|
|
|
|
callback({error: -6}); // FILE_NOT_FOUND
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (target[target.length - 1] == '') {
|
|
|
|
target[target.length - 1] = 'index.html';
|
|
|
|
}
|
|
|
|
|
|
|
|
let baseDir;
|
2019-12-09 13:49:41 +01:00
|
|
|
if (target[1] === 'webapp') {
|
2019-12-10 18:40:17 +01:00
|
|
|
baseDir = asarPath;
|
2019-12-06 19:17:34 +01:00
|
|
|
} else {
|
|
|
|
callback({error: -6}); // FILE_NOT_FOUND
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Normalise the base dir and the target path separately, then make sure
|
|
|
|
// the target path isn't trying to back out beyond its root
|
|
|
|
baseDir = path.normalize(baseDir);
|
|
|
|
|
|
|
|
const relTarget = path.normalize(path.join(...target.slice(2)));
|
|
|
|
if (relTarget.startsWith('..')) {
|
|
|
|
callback({error: -6}); // FILE_NOT_FOUND
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const absTarget = path.join(baseDir, relTarget);
|
|
|
|
|
|
|
|
callback({
|
|
|
|
path: absTarget,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
if (argv['no-update']) {
|
|
|
|
console.log('Auto update disabled via command line flag "--no-update"');
|
|
|
|
} else if (vectorConfig['update_base_url']) {
|
|
|
|
console.log(`Starting auto update with base URL: ${vectorConfig['update_base_url']}`);
|
|
|
|
updater.start(vectorConfig['update_base_url']);
|
|
|
|
} else {
|
|
|
|
console.log('No update_base_url is defined: auto update is disabled');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load the previous window state with fallback to defaults
|
|
|
|
const mainWindowState = windowStateKeeper({
|
|
|
|
defaultWidth: 1024,
|
|
|
|
defaultHeight: 768,
|
|
|
|
});
|
|
|
|
|
|
|
|
const preloadScript = path.normalize(`${__dirname}/preload.js`);
|
|
|
|
mainWindow = global.mainWindow = new BrowserWindow({
|
2020-02-21 12:17:18 +01:00
|
|
|
// https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
|
|
|
backgroundColor: '#fff',
|
|
|
|
|
2019-12-06 19:17:34 +01:00
|
|
|
icon: iconPath,
|
|
|
|
show: false,
|
|
|
|
autoHideMenuBar: store.get('autoHideMenuBar', true),
|
|
|
|
|
|
|
|
x: mainWindowState.x,
|
|
|
|
y: mainWindowState.y,
|
|
|
|
width: mainWindowState.width,
|
|
|
|
height: mainWindowState.height,
|
|
|
|
webPreferences: {
|
|
|
|
preload: preloadScript,
|
|
|
|
nodeIntegration: false,
|
2020-05-21 00:16:57 +02:00
|
|
|
//sandbox: true, // We enable sandboxing from app.enableSandbox() above
|
2019-12-06 19:17:34 +01:00
|
|
|
enableRemoteModule: false,
|
2021-01-13 16:21:00 +01:00
|
|
|
contextIsolation: true,
|
2019-12-06 19:17:34 +01:00
|
|
|
webgl: false,
|
2020-02-24 18:16:35 +01:00
|
|
|
spellcheck: true,
|
2019-12-06 19:17:34 +01:00
|
|
|
},
|
|
|
|
});
|
|
|
|
mainWindow.loadURL('vector://vector/webapp/');
|
|
|
|
Menu.setApplicationMenu(vectorMenu);
|
|
|
|
|
|
|
|
// Create trayIcon icon
|
|
|
|
if (store.get('minimizeToTray', true)) tray.create(trayConfig);
|
|
|
|
|
|
|
|
mainWindow.once('ready-to-show', () => {
|
|
|
|
mainWindowState.manage(mainWindow);
|
|
|
|
|
|
|
|
if (!argv['hidden']) {
|
|
|
|
mainWindow.show();
|
|
|
|
} else {
|
|
|
|
// hide here explicitly because window manage above sometimes shows it
|
|
|
|
mainWindow.hide();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-03-31 09:58:24 +02:00
|
|
|
mainWindow.webContents.on('before-input-event', warnBeforeExit);
|
2021-03-29 13:10:27 +02:00
|
|
|
|
2019-12-06 19:17:34 +01:00
|
|
|
mainWindow.on('closed', () => {
|
|
|
|
mainWindow = global.mainWindow = null;
|
|
|
|
});
|
2021-03-25 13:46:10 +01:00
|
|
|
mainWindow.on('close', async (e) => {
|
2021-03-25 15:50:33 +01:00
|
|
|
// If we are not quitting and have a tray icon then minimize to tray
|
|
|
|
if (!global.appQuitting && (tray.hasTray() || process.platform === 'darwin')) {
|
|
|
|
// On Mac, closing the window just hides it
|
|
|
|
// (this is generally how single-window Mac apps
|
|
|
|
// behave, eg. Mail.app)
|
|
|
|
e.preventDefault();
|
|
|
|
mainWindow.hide();
|
|
|
|
return false;
|
|
|
|
}
|
2019-12-06 19:17:34 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
if (process.platform === 'win32') {
|
|
|
|
// Handle forward/backward mouse buttons in Windows
|
|
|
|
mainWindow.on('app-command', (e, cmd) => {
|
|
|
|
if (cmd === 'browser-backward' && mainWindow.webContents.canGoBack()) {
|
|
|
|
mainWindow.webContents.goBack();
|
|
|
|
} else if (cmd === 'browser-forward' && mainWindow.webContents.canGoForward()) {
|
|
|
|
mainWindow.webContents.goForward();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
webContentsHandler(mainWindow.webContents);
|
|
|
|
});
|
|
|
|
|
|
|
|
app.on('window-all-closed', () => {
|
|
|
|
app.quit();
|
|
|
|
});
|
|
|
|
|
|
|
|
app.on('activate', () => {
|
|
|
|
mainWindow.show();
|
|
|
|
});
|
|
|
|
|
2020-05-23 12:52:49 +02:00
|
|
|
function beforeQuit() {
|
2019-12-06 19:17:34 +01:00
|
|
|
global.appQuitting = true;
|
|
|
|
if (mainWindow) {
|
|
|
|
mainWindow.webContents.send('before-quit');
|
|
|
|
}
|
2020-05-23 12:52:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
app.on('before-quit', beforeQuit);
|
|
|
|
app.on('before-quit-for-update', beforeQuit);
|
2019-12-06 19:17:34 +01:00
|
|
|
|
|
|
|
app.on('second-instance', (ev, commandLine, workingDirectory) => {
|
|
|
|
// If other instance launched with --hidden then skip showing window
|
|
|
|
if (commandLine.includes('--hidden')) return;
|
|
|
|
|
|
|
|
// Someone tried to run a second instance, we should focus our window.
|
|
|
|
if (mainWindow) {
|
|
|
|
if (!mainWindow.isVisible()) mainWindow.show();
|
|
|
|
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
|
|
mainWindow.focus();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Set the App User Model ID to match what the squirrel
|
|
|
|
// installer uses for the shortcut icon.
|
|
|
|
// This makes notifications work on windows 8.1 (and is
|
|
|
|
// a noop on other platforms).
|
2020-07-01 16:30:53 +02:00
|
|
|
app.setAppUserModelId('com.squirrel.element-desktop.Element');
|