Format all files with prettier

This commit is contained in:
Andy Balaam
2022-12-15 11:52:12 +00:00
parent 040344eeab
commit 0faac52dae
67 changed files with 1758 additions and 1791 deletions
+1 -1
View File
@@ -50,5 +50,5 @@ declare module "keytar" {
*
* @returns A promise for the array of found credentials.
*/
export function findCredentials(service: string): Promise<Array<{ account: string, password: string}>>;
export function findCredentials(service: string): Promise<Array<{ account: string; password: string }>>;
}
+124 -129
View File
@@ -19,18 +19,11 @@ 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.
import "./squirrelhooks";
import {
app,
BrowserWindow,
Menu,
autoUpdater,
protocol,
dialog,
} from "electron";
import { app, BrowserWindow, Menu, autoUpdater, protocol, dialog } from "electron";
import AutoLaunch from "auto-launch";
import path from "path";
import windowStateKeeper from 'electron-window-state';
import Store from 'electron-store';
import windowStateKeeper from "electron-window-state";
import Store from "electron-store";
import fs, { promises as afs } from "fs";
import { URL } from "url";
import minimist from "minimist";
@@ -40,11 +33,11 @@ import "./keytar";
import "./seshat";
import "./settings";
import * as tray from "./tray";
import { buildMenuTemplate } from './vectormenu';
import webContentsHandler from './webcontents-handler';
import * as updater from './updater';
import { getProfileFromDeeplink, protocolInit } from './protocol';
import { _t, AppLocalization } from './language-helper';
import { buildMenuTemplate } from "./vectormenu";
import webContentsHandler from "./webcontents-handler";
import * as updater from "./updater";
import { getProfileFromDeeplink, protocolInit } from "./protocol";
import { _t, AppLocalization } from "./language-helper";
import Input = Electron.Input;
const argv = minimist(process.argv, {
@@ -65,8 +58,7 @@ if (argv["help"]) {
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:" +
"https://electronjs.org/docs/api/command-line-switches");
console.log("And more such as --proxy, see:" + "https://electronjs.org/docs/api/command-line-switches");
app.exit();
}
@@ -74,7 +66,7 @@ if (argv["help"]) {
// 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: string): boolean {
return fs.existsSync(path.join(d, 'IndexedDB'));
return fs.existsSync(path.join(d, "IndexedDB"));
}
// check if we are passed a profile in the SSO callback url
@@ -83,22 +75,22 @@ let userDataPath: string;
const userDataPathInProtocol = getProfileFromDeeplink(argv["_"]);
if (userDataPathInProtocol) {
userDataPath = userDataPathInProtocol;
} else if (argv['profile-dir']) {
userDataPath = argv['profile-dir'];
} else if (argv["profile-dir"]) {
userDataPath = argv["profile-dir"];
} else {
let newUserDataPath = app.getPath('userData');
if (argv['profile']) {
newUserDataPath += '-' + argv['profile'];
let newUserDataPath = app.getPath("userData");
if (argv["profile"]) {
newUserDataPath += "-" + argv["profile"];
}
const newUserDataPathExists = isRealUserDataDir(newUserDataPath);
let oldUserDataPath = path.join(app.getPath('appData'), app.getName().replace('Element', 'Riot'));
if (argv['profile']) {
oldUserDataPath += '-' + argv['profile'];
let oldUserDataPath = path.join(app.getPath("appData"), app.getName().replace("Element", "Riot"));
if (argv["profile"]) {
oldUserDataPath += "-" + argv["profile"];
}
const oldUserDataPathExists = isRealUserDataDir(oldUserDataPath);
console.log(newUserDataPath + " exists: " + (newUserDataPathExists ? 'yes' : 'no'));
console.log(oldUserDataPath + " exists: " + (oldUserDataPathExists ? 'yes' : 'no'));
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);
userDataPath = oldUserDataPath;
@@ -106,54 +98,53 @@ if (userDataPathInProtocol) {
userDataPath = newUserDataPath;
}
}
app.setPath('userData', userDataPath);
app.setPath("userData", userDataPath);
async function tryPaths(name: string, root: string, rawPaths: string[]): Promise<string> {
// Make everything relative to root
const paths = rawPaths.map(p => path.join(root, p));
const paths = rawPaths.map((p) => path.join(root, p));
for (const p of paths) {
try {
await afs.stat(p);
return p + '/';
} catch (e) {
}
return p + "/";
} catch (e) {}
}
console.log(`Couldn't find ${name} files in any of: `);
for (const p of paths) {
console.log("\t"+path.resolve(p));
console.log("\t" + path.resolve(p));
}
throw new Error(`Failed to find ${name} files`);
}
const homeserverProps = ['default_is_url', 'default_hs_url', 'default_server_name', 'default_server_config'] as const;
const homeserverProps = ["default_is_url", "default_hs_url", "default_server_name", "default_server_config"] as const;
// Find the webapp resources and set up things that require them
async function setupGlobals(): Promise<void> {
// find the webapp asar.
asarPath = await tryPaths("webapp", __dirname, [
// If run from the source checkout, this will be in the directory above
'../webapp.asar',
"../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',
"../../webapp.asar",
// also try without the 'asar' suffix to allow symlinking in a directory
'../webapp',
"../webapp",
// from a packaged application
'../../webapp',
"../../webapp",
]);
// we assume the resources path is in the same place as the asar
resPath = await tryPaths("res", path.dirname(asarPath), [
// If run from the source checkout
'res',
"res",
// if run from packaged application
'',
"",
]);
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
global.vectorConfig = require(asarPath + 'config.json');
global.vectorConfig = require(asarPath + "config.json");
} catch (e) {
// it would be nice to check the error code here and bail if the config
// is unparsable, but we get MODULE_NOT_FOUND in the case of a missing
@@ -165,15 +156,15 @@ async function setupGlobals(): Promise<void> {
try {
// Load local config and use it to override values from the one baked with the build
// eslint-disable-next-line @typescript-eslint/no-var-requires
const localConfig = require(path.join(app.getPath('userData'), 'config.json'));
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.
if (Object.keys(localConfig).find(k => homeserverProps.includes(<any>k))) {
if (Object.keys(localConfig).find((k) => homeserverProps.includes(<any>k))) {
// Rip out all the homeserver options from the vector config
global.vectorConfig = Object.keys(global.vectorConfig)
.filter(k => !homeserverProps.includes(<any>k))
.filter((k) => !homeserverProps.includes(<any>k))
.reduce((obj, key) => {
obj[key] = global.vectorConfig[key];
return obj;
@@ -185,9 +176,10 @@ async function setupGlobals(): Promise<void> {
if (e instanceof SyntaxError) {
dialog.showMessageBox({
type: "error",
title: `Your ${global.vectorConfig.brand || 'Element'} is misconfigured`,
message: `Your custom ${global.vectorConfig.brand || 'Element'} configuration contains invalid JSON. ` +
`Please correct the problem and reopen ${global.vectorConfig.brand || 'Element'}.`,
title: `Your ${global.vectorConfig.brand || "Element"} is misconfigured`,
message:
`Your custom ${global.vectorConfig.brand || "Element"} configuration contains invalid JSON. ` +
`Please correct the problem and reopen ${global.vectorConfig.brand || "Element"}.`,
detail: e.message || "",
});
}
@@ -197,16 +189,16 @@ async function setupGlobals(): Promise<void> {
// The tray icon
// It's important to call `path.join` so we don't end up with the packaged asar in the final path.
const iconFile = `element.${process.platform === 'win32' ? 'ico' : 'png'}`;
const iconFile = `element.${process.platform === "win32" ? "ico" : "png"}`;
iconPath = path.join(resPath, "img", iconFile);
global.trayConfig = {
icon_path: iconPath,
brand: global.vectorConfig.brand || 'Element',
brand: global.vectorConfig.brand || "Element",
};
// launcher
global.launcher = new AutoLaunch({
name: global.vectorConfig.brand || 'Element',
name: global.vectorConfig.brand || "Element",
isHidden: true,
mac: {
useLaunchAgent: true,
@@ -217,9 +209,9 @@ async function setupGlobals(): Promise<void> {
async function moveAutoLauncher(): Promise<void> {
// Look for an auto-launcher under 'Riot' and if we find one, port it's
// enabled/disabled-ness over to the new 'Element' launcher
if (!global.vectorConfig.brand || global.vectorConfig.brand === 'Element') {
if (!global.vectorConfig.brand || global.vectorConfig.brand === "Element") {
const oldLauncher = new AutoLaunch({
name: 'Riot',
name: "Riot",
isHidden: true,
mac: {
useLaunchAgent: true,
@@ -238,26 +230,30 @@ global.store = new Store({ name: "electron-config" });
global.appQuitting = false;
const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
(input, platform): boolean => platform !== 'darwin' && input.alt && input.key.toUpperCase() === 'F4',
(input, platform): boolean => platform !== 'darwin' && input.control && input.key.toUpperCase() === 'Q',
(input, platform): boolean => platform === 'darwin' && input.meta && input.key.toUpperCase() === 'Q',
(input, platform): boolean => platform !== "darwin" && input.alt && input.key.toUpperCase() === "F4",
(input, platform): boolean => platform !== "darwin" && input.control && input.key.toUpperCase() === "Q",
(input, platform): boolean => platform === "darwin" && input.meta && input.key.toUpperCase() === "Q",
];
const warnBeforeExit = (event: Event, input: Input): void => {
const shouldWarnBeforeExit = global.store.get('warnBeforeExit', true);
const shouldWarnBeforeExit = global.store.get("warnBeforeExit", true);
const exitShortcutPressed =
input.type === 'keyDown' && exitShortcuts.some(shortcutFn => shortcutFn(input, process.platform));
input.type === "keyDown" && exitShortcuts.some((shortcutFn) => shortcutFn(input, process.platform));
if (shouldWarnBeforeExit && exitShortcutPressed && global.mainWindow) {
const shouldCancelCloseRequest = dialog.showMessageBoxSync(global.mainWindow, {
type: "question",
buttons: [_t("Cancel"), _t("Close %(brand)s", {
brand: global.vectorConfig.brand || 'Element',
})],
message: _t("Are you sure you want to quit?"),
defaultId: 1,
cancelId: 0,
}) === 0;
const shouldCancelCloseRequest =
dialog.showMessageBoxSync(global.mainWindow, {
type: "question",
buttons: [
_t("Cancel"),
_t("Close %(brand)s", {
brand: global.vectorConfig.brand || "Element",
}),
],
message: _t("Are you sure you want to quit?"),
defaultId: 1,
cancelId: 0,
}) === 0;
if (shouldCancelCloseRequest) {
event.preventDefault();
@@ -271,18 +267,18 @@ const warnBeforeExit = (event: Event, input: Input): void => {
// 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: Error): void {
console.log('Unhandled exception', error);
process.on("uncaughtException", function (error: Error): void {
console.log("Unhandled exception", error);
});
app.commandLine.appendSwitch('--enable-usermedia-screen-capturing');
if (!app.commandLine.hasSwitch('enable-features')) {
app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer');
app.commandLine.appendSwitch("--enable-usermedia-screen-capturing");
if (!app.commandLine.hasSwitch("enable-features")) {
app.commandLine.appendSwitch("enable-features", "WebRTCPipeWireCapturer");
}
const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
console.log('Other instance detected: exiting');
console.log("Other instance detected: exiting");
app.exit();
}
@@ -294,14 +290,16 @@ protocolInit();
// 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,
protocol.registerSchemesAsPrivileged([
{
scheme: "vector",
privileges: {
standard: true,
secure: true,
supportFetchAPI: true,
},
},
}]);
]);
// 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.
@@ -315,15 +313,15 @@ protocol.registerSchemesAsPrivileged([{
app.enableSandbox();
// We disable media controls here. We do this because calls use audio and video elements and they sometimes capture the media keys. See https://github.com/vector-im/element-web/issues/15704
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService');
app.commandLine.appendSwitch("disable-features", "HardwareMediaKeyHandling,MediaSessionService");
// Disable hardware acceleration if the setting has been set.
if (global.store.get('disableHardwareAcceleration', false) === true) {
if (global.store.get("disableHardwareAcceleration", false) === true) {
console.log("Disabling hardware acceleration.");
app.disableHardwareAcceleration();
}
app.on('ready', async () => {
app.on("ready", async () => {
try {
await setupGlobals();
await moveAutoLauncher();
@@ -337,51 +335,51 @@ app.on('ready', async () => {
return;
}
if (argv['devtools']) {
if (argv["devtools"]) {
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { default: installExt, REACT_DEVELOPER_TOOLS, REACT_PERF } = require('electron-devtools-installer');
const { default: installExt, REACT_DEVELOPER_TOOLS, REACT_PERF } = require("electron-devtools-installer");
installExt(REACT_DEVELOPER_TOOLS)
.then((name: string) => console.log(`Added Extension: ${name}`))
.catch((err: unknown) => console.log('An error occurred: ', err));
.catch((err: unknown) => console.log("An error occurred: ", err));
installExt(REACT_PERF)
.then((name: string) => console.log(`Added Extension: ${name}`))
.catch((err: unknown) => console.log('An error occurred: ', err));
.catch((err: unknown) => console.log("An error occurred: ", err));
} catch (e) {
console.log(e);
}
}
protocol.registerFileProtocol('vector', (request, callback) => {
if (request.method !== 'GET') {
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:') {
if (parsedUrl.protocol !== "vector:") {
callback({ error: -302 }); // UNKNOWN_URL_SCHEME
return;
}
if (parsedUrl.host !== 'vector') {
if (parsedUrl.host !== "vector") {
callback({ error: -105 }); // NAME_NOT_RESOLVED
return;
}
const target = parsedUrl.pathname.split('/');
const target = parsedUrl.pathname.split("/");
// path starts with a '/'
if (target[0] !== '') {
if (target[0] !== "") {
callback({ error: -6 }); // FILE_NOT_FOUND
return;
}
if (target[target.length - 1] == '') {
target[target.length - 1] = 'index.html';
if (target[target.length - 1] == "") {
target[target.length - 1] = "index.html";
}
let baseDir: string;
if (target[1] === 'webapp') {
if (target[1] === "webapp") {
baseDir = asarPath;
} else {
callback({ error: -6 }); // FILE_NOT_FOUND
@@ -393,7 +391,7 @@ app.on('ready', async () => {
baseDir = path.normalize(baseDir);
const relTarget = path.normalize(path.join(...target.slice(2)));
if (relTarget.startsWith('..')) {
if (relTarget.startsWith("..")) {
callback({ error: -6 }); // FILE_NOT_FOUND
return;
}
@@ -404,13 +402,13 @@ app.on('ready', async () => {
});
});
if (argv['no-update']) {
if (argv["no-update"]) {
console.log('Auto update disabled via command line flag "--no-update"');
} else if (global.vectorConfig['update_base_url']) {
console.log(`Starting auto update with base URL: ${global.vectorConfig['update_base_url']}`);
updater.start(global.vectorConfig['update_base_url']);
} else if (global.vectorConfig["update_base_url"]) {
console.log(`Starting auto update with base URL: ${global.vectorConfig["update_base_url"]}`);
updater.start(global.vectorConfig["update_base_url"]);
} else {
console.log('No update_base_url is defined: auto update is disabled');
console.log("No update_base_url is defined: auto update is disabled");
}
// Load the previous window state with fallback to defaults
@@ -422,11 +420,11 @@ app.on('ready', async () => {
const preloadScript = path.normalize(`${__dirname}/preload.js`);
global.mainWindow = new BrowserWindow({
// https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
backgroundColor: '#fff',
backgroundColor: "#fff",
icon: iconPath,
show: false,
autoHideMenuBar: global.store.get('autoHideMenuBar', true),
autoHideMenuBar: global.store.get("autoHideMenuBar", true),
x: mainWindowState.x,
y: mainWindowState.y,
@@ -440,20 +438,20 @@ app.on('ready', async () => {
webgl: true,
},
});
global.mainWindow.loadURL('vector://vector/webapp/');
global.mainWindow.loadURL("vector://vector/webapp/");
// Handle spellchecker
// For some reason spellCheckerEnabled isn't persisted, so we have to use the store here
global.mainWindow.webContents.session.setSpellCheckerEnabled(global.store.get("spellCheckerEnabled", true));
// Create trayIcon icon
if (global.store.get('minimizeToTray', true)) tray.create(global.trayConfig);
if (global.store.get("minimizeToTray", true)) tray.create(global.trayConfig);
global.mainWindow.once('ready-to-show', () => {
global.mainWindow.once("ready-to-show", () => {
if (!global.mainWindow) return;
mainWindowState.manage(global.mainWindow);
if (!argv['hidden']) {
if (!argv["hidden"]) {
global.mainWindow.show();
} else {
// hide here explicitly because window manage above sometimes shows it
@@ -461,21 +459,21 @@ app.on('ready', async () => {
}
});
global.mainWindow.webContents.on('before-input-event', warnBeforeExit);
global.mainWindow.webContents.on("before-input-event", warnBeforeExit);
global.mainWindow.on('closed', () => {
global.mainWindow.on("closed", () => {
global.mainWindow = null;
});
global.mainWindow.on('close', async (e) => {
global.mainWindow.on("close", async (e) => {
// If we are not quitting and have a tray icon then minimize to tray
if (!global.appQuitting && (tray.hasTray() || process.platform === 'darwin')) {
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();
if (global.mainWindow?.isFullScreen()) {
global.mainWindow.once('leave-full-screen', () => global.mainWindow?.hide());
global.mainWindow.once("leave-full-screen", () => global.mainWindow?.hide());
global.mainWindow.setFullScreen(false);
} else {
@@ -486,12 +484,12 @@ app.on('ready', async () => {
}
});
if (process.platform === 'win32') {
if (process.platform === "win32") {
// Handle forward/backward mouse buttons in Windows
global.mainWindow.on('app-command', (e, cmd) => {
if (cmd === 'browser-backward' && global.mainWindow?.webContents.canGoBack()) {
global.mainWindow.on("app-command", (e, cmd) => {
if (cmd === "browser-backward" && global.mainWindow?.webContents.canGoBack()) {
global.mainWindow.webContents.goBack();
} else if (cmd === 'browser-forward' && global.mainWindow?.webContents.canGoForward()) {
} else if (cmd === "browser-forward" && global.mainWindow?.webContents.canGoForward()) {
global.mainWindow.webContents.goForward();
}
});
@@ -501,32 +499,29 @@ app.on('ready', async () => {
global.appLocalization = new AppLocalization({
store: global.store,
components: [
(): void => tray.initApplicationMenu(),
(): void => Menu.setApplicationMenu(buildMenuTemplate()),
],
components: [(): void => tray.initApplicationMenu(), (): void => Menu.setApplicationMenu(buildMenuTemplate())],
});
});
app.on('window-all-closed', () => {
app.on("window-all-closed", () => {
app.quit();
});
app.on('activate', () => {
app.on("activate", () => {
global.mainWindow?.show();
});
function beforeQuit(): void {
global.appQuitting = true;
global.mainWindow?.webContents.send('before-quit');
global.mainWindow?.webContents.send("before-quit");
}
app.on('before-quit', beforeQuit);
autoUpdater.on('before-quit-for-update', beforeQuit);
app.on("before-quit", beforeQuit);
autoUpdater.on("before-quit-for-update", beforeQuit);
app.on('second-instance', (ev, commandLine, workingDirectory) => {
app.on("second-instance", (ev, commandLine, workingDirectory) => {
// If other instance launched with --hidden then skip showing window
if (commandLine.includes('--hidden')) return;
if (commandLine.includes("--hidden")) return;
// Someone tried to run a second instance, we should focus our window.
if (global.mainWindow) {
@@ -540,4 +535,4 @@ app.on('second-instance', (ev, commandLine, workingDirectory) => {
// installer uses for the shortcut icon.
// This makes notifications work on windows 8.1 (and is
// a noop on other platforms).
app.setAppUserModelId('com.squirrel.element-desktop.Element');
app.setAppUserModelId("com.squirrel.element-desktop.Element");
+33 -34
View File
@@ -22,8 +22,8 @@ import { randomArray } from "./utils";
import { Settings } from "./settings";
import { keytar } from "./keytar";
ipcMain.on('setBadgeCount', function(_ev: IpcMainEvent, count: number): void {
if (process.platform !== 'win32') {
ipcMain.on("setBadgeCount", function (_ev: IpcMainEvent, count: number): void {
if (process.platform !== "win32") {
// only set badgeCount on Mac/Linux, the docs say that only those platforms support it but turns out Electron
// has some Windows support too, and in some Windows environments this leads to two badges rendering atop
// each other. See https://github.com/vector-im/element-web/issues/16942
@@ -35,10 +35,10 @@ ipcMain.on('setBadgeCount', function(_ev: IpcMainEvent, count: number): void {
});
let focusHandlerAttached = false;
ipcMain.on('loudNotification', function(): void {
if (process.platform === 'win32' && global.mainWindow && !global.mainWindow.isFocused() && !focusHandlerAttached) {
ipcMain.on("loudNotification", function (): void {
if (process.platform === "win32" && global.mainWindow && !global.mainWindow.isFocused() && !focusHandlerAttached) {
global.mainWindow.flashFrame(true);
global.mainWindow.once('focus', () => {
global.mainWindow.once("focus", () => {
global.mainWindow?.flashFrame(false);
focusHandlerAttached = false;
});
@@ -47,17 +47,17 @@ ipcMain.on('loudNotification', function(): void {
});
let powerSaveBlockerId: number | null = null;
ipcMain.on('app_onAction', function(_ev: IpcMainEvent, payload) {
ipcMain.on("app_onAction", function (_ev: IpcMainEvent, payload) {
switch (payload.action) {
case 'call_state': {
case "call_state": {
if (powerSaveBlockerId !== null && powerSaveBlocker.isStarted(powerSaveBlockerId)) {
if (payload.state === 'ended') {
if (payload.state === "ended") {
powerSaveBlocker.stop(powerSaveBlockerId);
powerSaveBlockerId = null;
}
} else {
if (powerSaveBlockerId === null && payload.state === 'connected') {
powerSaveBlockerId = powerSaveBlocker.start('prevent-display-sleep');
if (powerSaveBlockerId === null && payload.state === "connected") {
powerSaveBlockerId = powerSaveBlocker.start("prevent-display-sleep");
}
}
break;
@@ -65,35 +65,35 @@ ipcMain.on('app_onAction', function(_ev: IpcMainEvent, payload) {
}
});
ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
if (!global.mainWindow) return;
const args = payload.args || [];
let ret: any;
switch (payload.name) {
case 'getUpdateFeedUrl':
case "getUpdateFeedUrl":
ret = autoUpdater.getFeedURL();
break;
case 'getSettingValue': {
case "getSettingValue": {
const [settingName] = args;
const setting = Settings[settingName];
ret = await setting.read();
break;
}
case 'setSettingValue': {
case "setSettingValue": {
const [settingName, value] = args;
const setting = Settings[settingName];
await setting.write(value);
break;
}
case 'setLanguage':
case "setLanguage":
global.appLocalization.setAppLocale(args[0]);
break;
case 'getAppVersion':
case "getAppVersion":
ret = app.getVersion();
break;
case 'focusWindow':
case "focusWindow":
if (global.mainWindow.isMinimized()) {
global.mainWindow.restore();
} else if (!global.mainWindow.isVisible()) {
@@ -102,31 +102,31 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
global.mainWindow.focus();
}
break;
case 'getConfig':
case "getConfig":
ret = global.vectorConfig;
break;
case 'navigateBack':
case "navigateBack":
if (global.mainWindow.webContents.canGoBack()) {
global.mainWindow.webContents.goBack();
}
break;
case 'navigateForward':
case "navigateForward":
if (global.mainWindow.webContents.canGoForward()) {
global.mainWindow.webContents.goForward();
}
break;
case 'setSpellCheckEnabled':
if (typeof args[0] !== 'boolean') return;
case "setSpellCheckEnabled":
if (typeof args[0] !== "boolean") return;
global.mainWindow.webContents.session.setSpellCheckerEnabled(args[0]);
global.store.set("spellCheckerEnabled", args[0]);
break;
case 'getSpellCheckEnabled':
case "getSpellCheckEnabled":
ret = global.store.get("spellCheckerEnabled", true);
break;
case 'setSpellCheckLanguages':
case "setSpellCheckLanguages":
try {
global.mainWindow.webContents.session.setSpellCheckerLanguages(args[0]);
} catch (er) {
@@ -134,18 +134,18 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
}
break;
case 'getSpellCheckLanguages':
case "getSpellCheckLanguages":
ret = global.mainWindow.webContents.session.getSpellCheckerLanguages();
break;
case 'getAvailableSpellCheckLanguages':
case "getAvailableSpellCheckLanguages":
ret = global.mainWindow.webContents.session.availableSpellCheckerLanguages;
break;
case 'startSSOFlow':
case "startSSOFlow":
recordSSOSession(args[0]);
break;
case 'getPickleKey':
case "getPickleKey":
try {
ret = await keytar?.getPassword("element.io", `${args[0]}|${args[1]}`);
// migrate from riot.im (remove once we think there will no longer be
@@ -160,7 +160,7 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
}
break;
case 'createPickleKey':
case "createPickleKey":
try {
const pickleKey = await randomArray(32);
await keytar?.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey);
@@ -170,7 +170,7 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
}
break;
case 'destroyPickleKey':
case "destroyPickleKey":
try {
await keytar?.deletePassword("element.io", `${args[0]}|${args[1]}`);
// migrate from riot.im (remove once we think there will no longer be
@@ -178,7 +178,7 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
await keytar?.deletePassword("riot.im", `${args[0]}|${args[1]}`);
} catch (e) {}
break;
case 'getDesktopCapturerSources':
case "getDesktopCapturerSources":
ret = (await desktopCapturer.getSources(args[0])).map((source) => ({
id: source.id,
name: source.name,
@@ -187,16 +187,15 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
break;
default:
global.mainWindow.webContents.send('ipcReply', {
global.mainWindow.webContents.send("ipcReply", {
id: payload.id,
error: "Unknown IPC Call: " + payload.name,
});
return;
}
global.mainWindow.webContents.send('ipcReply', {
global.mainWindow.webContents.send("ipcReply", {
id: payload.id,
reply: ret,
});
});
+1 -1
View File
@@ -19,7 +19,7 @@ import type * as Keytar from "keytar"; // Hak dependency type
let keytar: typeof Keytar | undefined;
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
keytar = require('keytar');
keytar = require("keytar");
} catch (e) {
if ((<NodeJS.ErrnoException>e).code === "MODULE_NOT_FOUND") {
console.log("Keytar isn't installed; secure key storage is disabled.");
+8 -8
View File
@@ -16,9 +16,9 @@ limitations under the License.
import counterpart from "counterpart";
import type Store from 'electron-store';
import type Store from "electron-store";
const FALLBACK_LOCALE = 'en';
const FALLBACK_LOCALE = "en";
export function _td(text: string): string {
return text;
@@ -44,11 +44,11 @@ export function _t(text: string, variables: IVariables = {}): string {
Object.keys(variables).forEach((key) => {
if (variables[key] === undefined) {
console.warn("safeCounterpartTranslate called with undefined interpolation name: " + key);
variables[key] = 'undefined';
variables[key] = "undefined";
}
if (variables[key] === null) {
console.warn("safeCounterpartTranslate called with null interpolation name: " + key);
variables[key] = 'null';
variables[key] = "null";
}
});
let translated = counterpart.translate(text, variables);
@@ -71,10 +71,10 @@ export class AppLocalization {
private readonly store: TypedStore;
private readonly localizedComponents?: Set<Component>;
public constructor({ store, components = [] }: { store: TypedStore, components: Component[] }) {
public constructor({ store, components = [] }: { store: TypedStore; components: Component[] }) {
counterpart.registerTranslations(FALLBACK_LOCALE, this.fetchTranslationJson("en_EN"));
counterpart.setFallbackLocale(FALLBACK_LOCALE);
counterpart.setSeparator('|');
counterpart.setSeparator("|");
if (Array.isArray(components)) {
this.localizedComponents = new Set(components);
@@ -119,7 +119,7 @@ export class AppLocalization {
locales = [locales];
}
const loadedLocales = locales.filter(locale => {
const loadedLocales = locales.filter((locale) => {
const translations = this.fetchTranslationJson(locale);
if (translations !== null) {
counterpart.registerTranslations(locale, translations);
@@ -135,7 +135,7 @@ export class AppLocalization {
public resetLocalizedUI(): void {
console.log("Resetting the UI components after locale change");
this.localizedComponents?.forEach(componentSetup => {
this.localizedComponents?.forEach((componentSetup) => {
if (typeof componentSetup === "function") {
componentSetup();
}
+16 -19
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { ipcRenderer, contextBridge, IpcRendererEvent } from 'electron';
import { ipcRenderer, contextBridge, IpcRendererEvent } from "electron";
// Expose only expected IPC wrapper APIs to the renderer process to avoid
// handing out generalised messaging access.
@@ -36,22 +36,19 @@ const CHANNELS = [
"userDownloadAction",
];
contextBridge.exposeInMainWorld(
"electron",
{
on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): void {
if (!CHANNELS.includes(channel)) {
console.error(`Unknown IPC channel ${channel} ignored`);
return;
}
ipcRenderer.on(channel, listener);
},
send(channel: string, ...args: any[]): void {
if (!CHANNELS.includes(channel)) {
console.error(`Unknown IPC channel ${channel} ignored`);
return;
}
ipcRenderer.send(channel, ...args);
},
contextBridge.exposeInMainWorld("electron", {
on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): void {
if (!CHANNELS.includes(channel)) {
console.error(`Unknown IPC channel ${channel} ignored`);
return;
}
ipcRenderer.on(channel, listener);
},
);
send(channel: string, ...args: any[]): void {
if (!CHANNELS.includes(channel)) {
console.error(`Unknown IPC channel ${channel} ignored`);
return;
}
ipcRenderer.send(channel, ...args);
},
});
+11 -10
View File
@@ -67,7 +67,7 @@ function writeStore(data: Record<string, string>): void {
}
export function recordSSOSession(sessionID: string): void {
const userDataPath = app.getPath('userData');
const userDataPath = app.getPath("userData");
const store = readStore();
for (const key in store) {
// ensure each instance only has one (the latest) session ID to prevent the file growing unbounded
@@ -82,7 +82,7 @@ export function recordSSOSession(sessionID: string): void {
export function getProfileFromDeeplink(args: string[]): string | undefined {
// check if we are passed a profile in the SSO callback url
const deeplinkUrl = args.find(arg => arg.startsWith(PROTOCOL + '//'));
const deeplinkUrl = args.find((arg) => arg.startsWith(PROTOCOL + "//"));
if (deeplinkUrl?.includes(SEARCH_PARAM)) {
const parsedUrl = new URL(deeplinkUrl);
if (parsedUrl.protocol === PROTOCOL) {
@@ -98,25 +98,26 @@ export function protocolInit(): void {
// get all args except `hidden` as it'd mean the app would not get focused
// XXX: passing args to protocol handlers only works on Windows, so unpackaged deep-linking
// --profile/--profile-dir are passed via the SEARCH_PARAM var in the callback url
const args = process.argv.slice(1).filter(arg => arg !== "--hidden" && arg !== "-hidden");
const args = process.argv.slice(1).filter((arg) => arg !== "--hidden" && arg !== "-hidden");
if (app.isPackaged) {
app.setAsDefaultProtocolClient('element', process.execPath, args);
} else if (process.platform === 'win32') { // on Mac/Linux this would just cause the electron binary to open
app.setAsDefaultProtocolClient("element", process.execPath, args);
} else if (process.platform === "win32") {
// on Mac/Linux this would just cause the electron binary to open
// special handler for running without being packaged, e.g `electron .` by passing our app path to electron
app.setAsDefaultProtocolClient('element', process.execPath, [app.getAppPath(), ...args]);
app.setAsDefaultProtocolClient("element", process.execPath, [app.getAppPath(), ...args]);
}
if (process.platform === 'darwin') {
if (process.platform === "darwin") {
// Protocol handler for macos
app.on('open-url', function(ev, url) {
app.on("open-url", function (ev, url) {
ev.preventDefault();
processUrl(url);
});
} else {
// Protocol handler for win32/Linux
app.on('second-instance', (ev, commandLine) => {
app.on("second-instance", (ev, commandLine) => {
const url = commandLine[commandLine.length - 1];
if (!url.startsWith(PROTOCOL + '//')) return;
if (!url.startsWith(PROTOCOL + "//")) return;
processUrl(url);
});
}
+27 -31
View File
@@ -34,7 +34,7 @@ let ReindexError: typeof ReindexErrorType;
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const seshatModule = require('matrix-seshat');
const seshatModule = require("matrix-seshat");
Seshat = seshatModule.Seshat;
SeshatRecovery = seshatModule.SeshatRecovery;
ReindexError = seshatModule.ReindexError;
@@ -75,29 +75,29 @@ const deleteContents = async (p: string): Promise<void> => {
}
};
ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
ipcMain.on("seshat", async function (_ev: IpcMainEvent, payload): Promise<void> {
if (!global.mainWindow) return;
// We do this here to ensure we get the path after --profile has been resolved
const eventStorePath = path.join(app.getPath('userData'), 'EventStore');
const eventStorePath = path.join(app.getPath("userData"), "EventStore");
const sendError = (id: string, e: Error): void => {
const error = {
message: e.message,
};
global.mainWindow?.webContents.send('seshatReply', { id, error });
global.mainWindow?.webContents.send("seshatReply", { id, error });
};
const args = payload.args || [];
let ret: any;
switch (payload.name) {
case 'supportsEventIndexing':
case "supportsEventIndexing":
ret = seshatSupported;
break;
case 'initEventIndex':
case "initEventIndex":
if (eventIndex === null) {
const userId = args[0];
const deviceId = args[1];
@@ -127,8 +127,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
try {
await deleteContents(eventStorePath);
} catch (e) {
}
} catch (e) {}
} else {
await recoveryIndex.reindex();
}
@@ -142,7 +141,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
}
break;
case 'closeEventIndex':
case "closeEventIndex":
if (eventIndex !== null) {
const index = eventIndex;
eventIndex = null;
@@ -156,26 +155,24 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
}
break;
case 'deleteEventIndex': {
case "deleteEventIndex": {
try {
await deleteContents(eventStorePath);
} catch (e) {
}
} catch (e) {}
break;
}
case 'isEventIndexEmpty':
case "isEventIndexEmpty":
if (eventIndex === null) ret = true;
else ret = await eventIndex.isEmpty();
break;
case 'isRoomIndexed':
case "isRoomIndexed":
if (eventIndex === null) ret = false;
else ret = await eventIndex.isRoomIndexed(args[0]);
break;
case 'addEventToIndex':
case "addEventToIndex":
try {
eventIndex?.addEvent(args[0], args[1]);
} catch (e) {
@@ -184,7 +181,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
}
break;
case 'deleteEvent':
case "deleteEvent":
try {
ret = await eventIndex?.deleteEvent(args[0]);
} catch (e) {
@@ -193,7 +190,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
}
break;
case 'commitLiveEvents':
case "commitLiveEvents":
try {
ret = await eventIndex?.commit();
} catch (e) {
@@ -202,7 +199,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
}
break;
case 'searchEventIndex':
case "searchEventIndex":
try {
ret = await eventIndex?.search(args[0]);
} catch (e) {
@@ -211,12 +208,11 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
}
break;
case 'addHistoricEvents':
case "addHistoricEvents":
if (eventIndex === null) ret = false;
else {
try {
ret = await eventIndex.addHistoricEvents(
args[0], args[1], args[2]);
ret = await eventIndex.addHistoricEvents(args[0], args[1], args[2]);
} catch (e) {
sendError(payload.id, <Error>e);
return;
@@ -224,7 +220,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
}
break;
case 'getStats':
case "getStats":
if (eventIndex === null) ret = 0;
else {
try {
@@ -236,7 +232,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
}
break;
case 'removeCrawlerCheckpoint':
case "removeCrawlerCheckpoint":
if (eventIndex === null) ret = false;
else {
try {
@@ -248,7 +244,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
}
break;
case 'addCrawlerCheckpoint':
case "addCrawlerCheckpoint":
if (eventIndex === null) ret = false;
else {
try {
@@ -260,7 +256,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
}
break;
case 'loadFileEvents':
case "loadFileEvents":
if (eventIndex === null) ret = [];
else {
try {
@@ -272,7 +268,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
}
break;
case 'loadCheckpoints':
case "loadCheckpoints":
if (eventIndex === null) ret = [];
else {
try {
@@ -283,7 +279,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
}
break;
case 'setUserVersion':
case "setUserVersion":
if (eventIndex === null) break;
else {
try {
@@ -295,7 +291,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
}
break;
case 'getUserVersion':
case "getUserVersion":
if (eventIndex === null) ret = 0;
else {
try {
@@ -308,14 +304,14 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
break;
default:
global.mainWindow.webContents.send('seshatReply', {
global.mainWindow.webContents.send("seshatReply", {
id: payload.id,
error: "Unknown IPC Call: " + payload.name,
});
return;
}
global.mainWindow.webContents.send('seshatReply', {
global.mainWindow.webContents.send("seshatReply", {
id: payload.id,
reply: ret,
});
+8 -6
View File
@@ -42,17 +42,19 @@ export const Settings: Record<string, Setting> = {
global.store.set("warnBeforeExit", value);
},
},
"Electron.alwaysShowMenuBar": { // not supported on macOS
"Electron.alwaysShowMenuBar": {
// not supported on macOS
async read(): Promise<any> {
return !global.mainWindow!.autoHideMenuBar;
},
async write(value: any): Promise<void> {
global.store.set('autoHideMenuBar', !value);
global.store.set("autoHideMenuBar", !value);
global.mainWindow!.autoHideMenuBar = !value;
global.mainWindow!.setMenuBarVisibility(value);
},
},
"Electron.showTrayIcon": { // not supported on macOS
"Electron.showTrayIcon": {
// not supported on macOS
async read(): Promise<any> {
return tray.hasTray();
},
@@ -63,15 +65,15 @@ export const Settings: Record<string, Setting> = {
} else {
tray.destroy();
}
global.store.set('minimizeToTray', value);
global.store.set("minimizeToTray", value);
},
},
"Electron.enableHardwareAcceleration": {
async read(): Promise<any> {
return !global.store.get('disableHardwareAcceleration', false);
return !global.store.get("disableHardwareAcceleration", false);
},
async write(value: any): Promise<void> {
global.store.set('disableHardwareAcceleration', !value);
global.store.set("disableHardwareAcceleration", !value);
},
},
};
+10 -10
View File
@@ -23,29 +23,29 @@ function runUpdateExe(args: string[]): Promise<void> {
// Note that there's an Update.exe in the app-x.x.x directory and one in the parent
// directory: we need to run the one in the parent directory, because it discovers
// information about the app by inspecting the directory it's run from.
const updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe');
const updateExe = path.resolve(path.dirname(process.execPath), "..", "Update.exe");
console.log(`Spawning '${updateExe}' with args '${args}'`);
return new Promise(resolve => {
return new Promise((resolve) => {
spawn(updateExe, args, {
detached: true,
}).on('close', resolve);
}).on("close", resolve);
});
}
function checkSquirrelHooks(): boolean {
if (process.platform !== 'win32') return false;
if (process.platform !== "win32") return false;
const cmd = process.argv[1];
const target = path.basename(process.execPath);
if (cmd === '--squirrel-install') {
runUpdateExe(['--createShortcut=' + target]).then(() => app.quit());
if (cmd === "--squirrel-install") {
runUpdateExe(["--createShortcut=" + target]).then(() => app.quit());
return true;
} else if (cmd === '--squirrel-updated') {
} else if (cmd === "--squirrel-updated") {
app.quit();
return true;
} else if (cmd === '--squirrel-uninstall') {
runUpdateExe(['--removeShortcut=' + target]).then(() => app.quit());
} else if (cmd === "--squirrel-uninstall") {
runUpdateExe(["--removeShortcut=" + target]).then(() => app.quit());
return true;
} else if (cmd === '--squirrel-obsolete') {
} else if (cmd === "--squirrel-obsolete") {
app.quit();
return true;
}
+12 -12
View File
@@ -25,7 +25,7 @@ import { _t } from "./language-helper";
let trayIcon: Tray | null = null;
export function hasTray(): boolean {
return (trayIcon !== null);
return trayIcon !== null;
}
export function destroy(): void {
@@ -52,17 +52,17 @@ interface IConfig {
export function create(config: IConfig): void {
// no trays on darwin
if (process.platform === 'darwin' || trayIcon) return;
if (process.platform === "darwin" || trayIcon) return;
const defaultIcon = nativeImage.createFromPath(config.icon_path);
trayIcon = new Tray(defaultIcon);
trayIcon.setToolTip(config.brand);
initApplicationMenu();
trayIcon.on('click', toggleWin);
trayIcon.on("click", toggleWin);
let lastFavicon: string | null = null;
global.mainWindow?.webContents.on('page-favicon-updated', async function(ev, favicons) {
if (!favicons || favicons.length <= 0 || !favicons[0].startsWith('data:')) {
global.mainWindow?.webContents.on("page-favicon-updated", async function (ev, favicons) {
if (!favicons || favicons.length <= 0 || !favicons[0].startsWith("data:")) {
if (lastFavicon !== null) {
global.mainWindow?.setIcon(defaultIcon);
trayIcon?.setImage(defaultIcon);
@@ -78,9 +78,9 @@ export function create(config: IConfig): void {
let newFavicon = nativeImage.createFromDataURL(favicons[0]);
// Windows likes ico's too much.
if (process.platform === 'win32') {
if (process.platform === "win32") {
try {
const icoPath = path.join(app.getPath('temp'), 'win32_element_icon.ico');
const icoPath = path.join(app.getPath("temp"), "win32_element_icon.ico");
fs.writeFileSync(icoPath, await pngToIco(newFavicon.toPNG()));
newFavicon = nativeImage.createFromPath(icoPath);
} catch (e) {
@@ -92,7 +92,7 @@ export function create(config: IConfig): void {
global.mainWindow?.setIcon(newFavicon);
});
global.mainWindow?.webContents.on('page-title-updated', function(ev, title) {
global.mainWindow?.webContents.on("page-title-updated", function (ev, title) {
trayIcon?.setToolTip(title);
});
}
@@ -104,13 +104,13 @@ export function initApplicationMenu(): void {
const contextMenu = Menu.buildFromTemplate([
{
label: _t('Show/Hide'),
label: _t("Show/Hide"),
click: toggleWin,
},
{ type: 'separator' },
{ type: "separator" },
{
label: _t('Quit'),
click: function(): void {
label: _t("Quit"),
click: function (): void {
app.quit();
},
},
+30 -27
View File
@@ -37,33 +37,33 @@ function pollForUpdates(): void {
autoUpdater.checkForUpdates();
} else {
console.log("Skipping update check as download already present");
global.mainWindow?.webContents.send('update-downloaded', latestUpdateDownloaded);
global.mainWindow?.webContents.send("update-downloaded", latestUpdateDownloaded);
}
} catch (e) {
console.log('Couldn\'t check for update', e);
console.log("Couldn't check for update", e);
}
}
export function start(updateBaseUrl: string): void {
if (updateBaseUrl.slice(-1) !== '/') {
updateBaseUrl = updateBaseUrl + '/';
if (updateBaseUrl.slice(-1) !== "/") {
updateBaseUrl = updateBaseUrl + "/";
}
try {
let url: string;
let serverType: "json" | undefined;
if (process.platform === 'darwin') {
if (process.platform === "darwin") {
// On macOS it takes a JSON file with a map between versions and their URLs
url = `${updateBaseUrl}macos/releases.json`;
serverType = "json";
} else if (process.platform === 'win32') {
} else if (process.platform === "win32") {
// On windows it takes a base path and looks for files under that path.
url = `${updateBaseUrl}win32/${process.arch}/`;
} else {
// Squirrel / electron only supports auto-update on these two platforms.
// I'm not even going to try to guess which feed style they'd use if they
// implemented it on Linux, or if it would be different again.
console.log('Auto update not supported on this platform');
console.log("Auto update not supported on this platform");
return;
}
@@ -82,15 +82,15 @@ export function start(updateBaseUrl: string): void {
}
} catch (err) {
// will fail if running in debug mode
console.log('Couldn\'t enable update checking', err);
console.log("Couldn't enable update checking", err);
}
}
ipcMain.on('install_update', installUpdate);
ipcMain.on('check_updates', pollForUpdates);
ipcMain.on("install_update", installUpdate);
ipcMain.on("check_updates", pollForUpdates);
function ipcChannelSendUpdateStatus(status: boolean | string): void {
global.mainWindow?.webContents.send('check_updates', status);
global.mainWindow?.webContents.send("check_updates", status);
}
interface ICachedUpdate {
@@ -102,23 +102,26 @@ interface ICachedUpdate {
// cache the latest update which has been downloaded as electron offers no api to read it
let latestUpdateDownloaded: ICachedUpdate;
autoUpdater.on('update-available', function() {
ipcChannelSendUpdateStatus(true);
}).on('update-not-available', function() {
if (latestUpdateDownloaded) {
// the only time we will get `update-not-available` if `latestUpdateDownloaded` is already set
// is if the user used the Manual Update check and there is no update newer than the one we
// have downloaded, so show it to them as the latest again.
global.mainWindow?.webContents.send('update-downloaded', latestUpdateDownloaded);
} else {
ipcChannelSendUpdateStatus(false);
}
}).on('error', function(error) {
ipcChannelSendUpdateStatus(error.message);
});
autoUpdater
.on("update-available", function () {
ipcChannelSendUpdateStatus(true);
})
.on("update-not-available", function () {
if (latestUpdateDownloaded) {
// the only time we will get `update-not-available` if `latestUpdateDownloaded` is already set
// is if the user used the Manual Update check and there is no update newer than the one we
// have downloaded, so show it to them as the latest again.
global.mainWindow?.webContents.send("update-downloaded", latestUpdateDownloaded);
} else {
ipcChannelSendUpdateStatus(false);
}
})
.on("error", function (error) {
ipcChannelSendUpdateStatus(error.message);
});
autoUpdater.on('update-downloaded', (ev, releaseNotes, releaseName, releaseDate, updateURL) => {
autoUpdater.on("update-downloaded", (ev, releaseNotes, releaseName, releaseDate, updateURL) => {
// forward to renderer
latestUpdateDownloaded = { releaseNotes, releaseName, releaseDate, updateURL };
global.mainWindow?.webContents.send('update-downloaded', latestUpdateDownloaded);
global.mainWindow?.webContents.send("update-downloaded", latestUpdateDownloaded);
});
+1 -1
View File
@@ -22,7 +22,7 @@ export async function randomArray(size: number): Promise<string> {
if (err) {
reject(err);
} else {
resolve(buf.toString("base64").replace(/=+$/g, ''));
resolve(buf.toString("base64").replace(/=+$/g, ""));
}
});
});
+112 -101
View File
@@ -14,125 +14,133 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { app, shell, Menu, MenuItem, MenuItemConstructorOptions } from 'electron';
import { app, shell, Menu, MenuItem, MenuItemConstructorOptions } from "electron";
import { _t } from './language-helper';
import { _t } from "./language-helper";
const isMac = process.platform === 'darwin';
const isMac = process.platform === "darwin";
export function buildMenuTemplate(): Menu {
// Menu template from http://electron.atom.io/docs/api/menu/, edited
const template: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
const template: Array<MenuItemConstructorOptions | MenuItem> = [
{
label: _t('Edit'),
accelerator: 'e',
label: _t("Edit"),
accelerator: "e",
submenu: [
{
role: 'undo',
label: _t('Undo'),
role: "undo",
label: _t("Undo"),
},
{
role: 'redo',
label: _t('Redo'),
role: "redo",
label: _t("Redo"),
},
{ type: 'separator' },
{ type: "separator" },
{
role: 'cut',
label: _t('Cut'),
role: "cut",
label: _t("Cut"),
},
{
role: 'copy',
label: _t('Copy'),
role: "copy",
label: _t("Copy"),
},
{
role: 'paste',
label: _t('Paste'),
role: "paste",
label: _t("Paste"),
},
{
role: 'pasteAndMatchStyle',
label: _t('Paste and Match Style'),
role: "pasteAndMatchStyle",
label: _t("Paste and Match Style"),
},
{
role: 'delete',
label: _t('Delete'),
role: "delete",
label: _t("Delete"),
},
{
role: 'selectAll',
label: _t('Select All'),
role: "selectAll",
label: _t("Select All"),
},
],
},
{
label: _t('View'),
accelerator: 'V',
label: _t("View"),
accelerator: "V",
submenu: [
{ type: 'separator' },
{ type: "separator" },
{
role: 'resetZoom',
accelerator: 'CmdOrCtrl+Num0',
role: "resetZoom",
accelerator: "CmdOrCtrl+Num0",
visible: false,
},
{
role: 'zoomIn',
accelerator: 'CmdOrCtrl+NumAdd',
role: "zoomIn",
accelerator: "CmdOrCtrl+NumAdd",
visible: false,
},
{
role: 'zoomOut',
accelerator: 'CmdOrCtrl+NumSub',
role: "zoomOut",
accelerator: "CmdOrCtrl+NumSub",
visible: false,
},
{
role: 'resetZoom',
label: _t('Actual Size'),
role: "resetZoom",
label: _t("Actual Size"),
},
{
role: 'zoomIn',
label: _t('Zoom In'),
role: "zoomIn",
label: _t("Zoom In"),
},
{
role: 'zoomOut',
label: _t('Zoom Out'),
role: "zoomOut",
label: _t("Zoom Out"),
},
{ type: 'separator' },
{ type: "separator" },
// in macOS the Preferences menu item goes in the first menu
...(!isMac ? [{
label: _t('Preferences'),
click(): void { global.mainWindow?.webContents.send('preferences'); },
}] : []),
...(!isMac
? [
{
label: _t("Preferences"),
click(): void {
global.mainWindow?.webContents.send("preferences");
},
},
]
: []),
{
role: 'togglefullscreen',
label: _t('Toggle Full Screen'),
role: "togglefullscreen",
label: _t("Toggle Full Screen"),
},
{
role: 'toggleDevTools',
label: _t('Toggle Developer Tools'),
role: "toggleDevTools",
label: _t("Toggle Developer Tools"),
},
],
},
{
label: _t('Window'),
accelerator: 'w',
role: 'window',
label: _t("Window"),
accelerator: "w",
role: "window",
submenu: [
{
role: 'minimize',
label: _t('Minimize'),
role: "minimize",
label: _t("Minimize"),
},
{
role: 'close',
label: _t('Close'),
role: "close",
label: _t("Close"),
},
],
},
{
label: _t('Help'),
accelerator: 'h',
role: 'help',
label: _t("Help"),
accelerator: "h",
role: "help",
submenu: [
{
label: _t('Element Help'),
click(): void { shell.openExternal('https://element.io/help'); },
label: _t("Element Help"),
click(): void {
shell.openExternal("https://element.io/help");
},
},
],
},
@@ -142,92 +150,95 @@ export function buildMenuTemplate(): Menu {
if (isMac) {
template.unshift({
// first macOS menu is the name of the app
role: 'appMenu',
role: "appMenu",
label: app.name,
submenu: [
{
role: 'about',
label: _t('About') + ' ' + app.name,
role: "about",
label: _t("About") + " " + app.name,
},
{ type: 'separator' },
{ type: "separator" },
{
label: _t('Preferences') + '…',
accelerator: 'Command+,', // Mac-only accelerator
click(): void { global.mainWindow?.webContents.send('preferences'); },
label: _t("Preferences") + "…",
accelerator: "Command+,", // Mac-only accelerator
click(): void {
global.mainWindow?.webContents.send("preferences");
},
},
{ type: 'separator' },
{ type: "separator" },
{
role: 'services',
label: _t('Services'),
role: "services",
label: _t("Services"),
submenu: [],
},
{ type: 'separator' },
{ type: "separator" },
{
role: 'hide',
label: _t('Hide'),
role: "hide",
label: _t("Hide"),
},
{
role: 'hideOthers',
label: _t('Hide Others'),
role: "hideOthers",
label: _t("Hide Others"),
},
{
role: 'unhide',
label: _t('Unhide'),
role: "unhide",
label: _t("Unhide"),
},
{ type: 'separator' },
{ type: "separator" },
{
role: 'quit',
label: _t('Quit'),
role: "quit",
label: _t("Quit"),
},
],
});
// Edit menu.
// This has a 'speech' section on macOS
(template[1].submenu as MenuItemConstructorOptions[]).push(
{ type: 'separator' },
{ type: "separator" },
{
label: _t('Speech'),
label: _t("Speech"),
submenu: [
{
role: 'startSpeaking',
label: _t('Start Speaking'),
role: "startSpeaking",
label: _t("Start Speaking"),
},
{
role: 'stopSpeaking',
label: _t('Stop Speaking'),
role: "stopSpeaking",
label: _t("Stop Speaking"),
},
],
});
},
);
// Window menu.
// This also has specific functionality on macOS
template[3].submenu = [
{
label: _t('Close'),
accelerator: 'CmdOrCtrl+W',
role: 'close',
label: _t("Close"),
accelerator: "CmdOrCtrl+W",
role: "close",
},
{
label: _t('Minimize'),
accelerator: 'CmdOrCtrl+M',
role: 'minimize',
label: _t("Minimize"),
accelerator: "CmdOrCtrl+M",
role: "minimize",
},
{
label: _t('Zoom'),
role: 'zoom',
label: _t("Zoom"),
role: "zoom",
},
{
type: 'separator',
type: "separator",
},
{
label: _t('Bring All to Front'),
role: 'front',
label: _t("Bring All to Front"),
role: "front",
},
];
} else {
template.unshift({
label: _t('File'),
accelerator: 'f',
label: _t("File"),
accelerator: "f",
submenu: [
// For some reason, 'about' does not seem to work on windows.
/*{
@@ -235,8 +246,8 @@ export function buildMenuTemplate(): Menu {
label: _t('About'),
},*/
{
role: 'quit',
label: _t('Quit'),
role: "quit",
label: _t("Quit"),
},
],
});
+133 -119
View File
@@ -28,22 +28,18 @@ import {
DownloadItem,
MenuItemConstructorOptions,
IpcMainEvent,
} from 'electron';
import url from 'url';
import fs from 'fs';
import fetch from 'node-fetch';
import { pipeline } from 'stream';
import path from 'path';
} from "electron";
import url from "url";
import fs from "fs";
import fetch from "node-fetch";
import { pipeline } from "stream";
import path from "path";
import { _t } from './language-helper';
import { _t } from "./language-helper";
const MAILTO_PREFIX = "mailto:";
const PERMITTED_URL_SCHEMES: string[] = [
'http:',
'https:',
MAILTO_PREFIX,
];
const PERMITTED_URL_SCHEMES: string[] = ["http:", "https:", MAILTO_PREFIX];
function safeOpenURL(target: string): void {
// openExternal passes the target to open/start/xdg-open,
@@ -70,7 +66,7 @@ function onWindowOrNavigate(ev: Event, target: string): void {
}
function writeNativeImage(filePath: string, img: NativeImage): Promise<void> {
switch (filePath.split('.').pop()?.toLowerCase()) {
switch (filePath.split(".").pop()?.toLowerCase()) {
case "jpg":
case "jpeg":
return fs.promises.writeFile(filePath, img.toJPEG(100));
@@ -85,7 +81,7 @@ function writeNativeImage(filePath: string, img: NativeImage): Promise<void> {
function onLinkContextMenu(ev: Event, params: ContextMenuParams, webContents: WebContents): void {
let url = params.linkURL || params.srcURL;
if (url.startsWith('vector://vector/webapp')) {
if (url.startsWith("vector://vector/webapp")) {
// Avoid showing a context menu for app icons
if (params.hasImageContents) return;
// Rewrite URL so that it can be used outside of the app
@@ -94,82 +90,90 @@ function onLinkContextMenu(ev: Event, params: ContextMenuParams, webContents: We
const popupMenu = new Menu();
// No point trying to open blob: URLs in an external browser: it ain't gonna work.
if (!url.startsWith('blob:')) {
popupMenu.append(new MenuItem({
label: url,
click(): void {
safeOpenURL(url);
},
}));
if (!url.startsWith("blob:")) {
popupMenu.append(
new MenuItem({
label: url,
click(): void {
safeOpenURL(url);
},
}),
);
}
if (params.hasImageContents) {
popupMenu.append(new MenuItem({
label: _t('Copy image'),
accelerator: 'c',
click(): void {
webContents.copyImageAt(params.x, params.y);
},
}));
popupMenu.append(
new MenuItem({
label: _t("Copy image"),
accelerator: "c",
click(): void {
webContents.copyImageAt(params.x, params.y);
},
}),
);
}
// No point offering to copy a blob: URL either
if (!url.startsWith('blob:')) {
if (!url.startsWith("blob:")) {
// Special-case e-mail URLs to strip the `mailto:` like modern browsers do
if (url.startsWith(MAILTO_PREFIX)) {
popupMenu.append(new MenuItem({
label: _t('Copy email address'),
accelerator: 'a',
click(): void {
clipboard.writeText(url.substr(MAILTO_PREFIX.length));
},
}));
popupMenu.append(
new MenuItem({
label: _t("Copy email address"),
accelerator: "a",
click(): void {
clipboard.writeText(url.substr(MAILTO_PREFIX.length));
},
}),
);
} else {
popupMenu.append(new MenuItem({
label: params.hasImageContents
? _t('Copy image address')
: _t('Copy link address'),
accelerator: 'a',
click(): void {
clipboard.writeText(url);
},
}));
popupMenu.append(
new MenuItem({
label: params.hasImageContents ? _t("Copy image address") : _t("Copy link address"),
accelerator: "a",
click(): void {
clipboard.writeText(url);
},
}),
);
}
}
// XXX: We cannot easily save a blob from the main process as
// only the renderer can resolve them so don't give the user an option to.
if (params.hasImageContents && !url.startsWith('blob:')) {
popupMenu.append(new MenuItem({
label: _t('Save image as...'),
accelerator: 's',
async click(): Promise<void> {
const targetFileName = params.suggestedFilename || params.altText || "image.png";
const { filePath } = await dialog.showSaveDialog({
defaultPath: targetFileName,
});
if (!filePath) return; // user cancelled dialog
try {
if (url.startsWith("data:")) {
await writeNativeImage(filePath, nativeImage.createFromDataURL(url));
} else {
const resp = await fetch(url);
if (!resp.ok) throw new Error(`unexpected response ${resp.statusText}`);
if (!resp.body) throw new Error(`unexpected response has no body ${resp.statusText}`);
pipeline(resp.body, fs.createWriteStream(filePath));
}
} catch (err) {
console.error(err);
dialog.showMessageBox({
type: "error",
title: _t("Failed to save image"),
message: _t("The image failed to save"),
if (params.hasImageContents && !url.startsWith("blob:")) {
popupMenu.append(
new MenuItem({
label: _t("Save image as..."),
accelerator: "s",
async click(): Promise<void> {
const targetFileName = params.suggestedFilename || params.altText || "image.png";
const { filePath } = await dialog.showSaveDialog({
defaultPath: targetFileName,
});
}
},
}));
if (!filePath) return; // user cancelled dialog
try {
if (url.startsWith("data:")) {
await writeNativeImage(filePath, nativeImage.createFromDataURL(url));
} else {
const resp = await fetch(url);
if (!resp.ok) throw new Error(`unexpected response ${resp.statusText}`);
if (!resp.body) throw new Error(`unexpected response has no body ${resp.statusText}`);
pipeline(resp.body, fs.createWriteStream(filePath));
}
} catch (err) {
console.error(err);
dialog.showMessageBox({
type: "error",
title: _t("Failed to save image"),
message: _t("The image failed to save"),
});
}
},
}),
);
}
// popup() requires an options object even for no options
@@ -181,7 +185,7 @@ function cutCopyPasteSelectContextMenus(params: ContextMenuParams): MenuItemCons
const options: MenuItemConstructorOptions[] = [];
if (params.misspelledWord) {
params.dictionarySuggestions.forEach(word => {
params.dictionarySuggestions.forEach((word) => {
options.push({
label: word,
click: (menuItem, browserWindow) => {
@@ -189,42 +193,52 @@ function cutCopyPasteSelectContextMenus(params: ContextMenuParams): MenuItemCons
},
});
});
options.push({
type: 'separator',
}, {
label: _t('Add to dictionary'),
click: (menuItem, browserWindow) => {
browserWindow?.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord);
options.push(
{
type: "separator",
},
}, {
type: 'separator',
});
{
label: _t("Add to dictionary"),
click: (menuItem, browserWindow) => {
browserWindow?.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord);
},
},
{
type: "separator",
},
);
}
options.push({
role: 'cut',
label: _t('Cut'),
accelerator: 't',
enabled: params.editFlags.canCut,
}, {
role: 'copy',
label: _t('Copy'),
accelerator: 'c',
enabled: params.editFlags.canCopy,
}, {
role: 'paste',
label: _t('Paste'),
accelerator: 'p',
enabled: params.editFlags.canPaste,
}, {
role: 'pasteAndMatchStyle',
enabled: params.editFlags.canPaste,
}, {
role: 'selectAll',
label: _t("Select All"),
accelerator: 'a',
enabled: params.editFlags.canSelectAll,
});
options.push(
{
role: "cut",
label: _t("Cut"),
accelerator: "t",
enabled: params.editFlags.canCut,
},
{
role: "copy",
label: _t("Copy"),
accelerator: "c",
enabled: params.editFlags.canCopy,
},
{
role: "paste",
label: _t("Paste"),
accelerator: "p",
enabled: params.editFlags.canPaste,
},
{
role: "pasteAndMatchStyle",
enabled: params.editFlags.canPaste,
},
{
role: "selectAll",
label: _t("Select All"),
accelerator: "a",
enabled: params.editFlags.canSelectAll,
},
);
return options;
}
@@ -239,9 +253,9 @@ function onSelectedContextMenu(ev: Event, params: ContextMenuParams): void {
function onEditableContextMenu(ev: Event, params: ContextMenuParams): void {
const items: MenuItemConstructorOptions[] = [
{ role: 'undo' },
{ role: 'redo', enabled: params.editFlags.canRedo },
{ type: 'separator' },
{ role: "undo" },
{ role: "redo", enabled: params.editFlags.canRedo },
{ type: "separator" },
...cutCopyPasteSelectContextMenus(params),
];
@@ -254,7 +268,7 @@ function onEditableContextMenu(ev: Event, params: ContextMenuParams): void {
let userDownloadIndex = 0;
const userDownloadMap = new Map<number, string>(); // Map from id to path
ipcMain.on('userDownloadAction', function(ev: IpcMainEvent, { id, open = false }) {
ipcMain.on("userDownloadAction", function (ev: IpcMainEvent, { id, open = false }) {
const path = userDownloadMap.get(id);
if (open && path) {
shell.openPath(path);
@@ -268,12 +282,12 @@ export default (webContents: WebContents): void => {
return { action: "deny" };
});
webContents.on('will-navigate', (ev: Event, target: string): void => {
webContents.on("will-navigate", (ev: Event, target: string): void => {
if (target.startsWith("vector://")) return;
return onWindowOrNavigate(ev, target);
});
webContents.on('context-menu', function(ev: Event, params: ContextMenuParams): void {
webContents.on("context-menu", function (ev: Event, params: ContextMenuParams): void {
if (params.linkURL || params.srcURL) {
onLinkContextMenu(ev, params, webContents);
} else if (params.selectionText) {
@@ -283,13 +297,13 @@ export default (webContents: WebContents): void => {
}
});
webContents.session.on('will-download', (event: Event, item: DownloadItem): void => {
item.once('done', (event, state) => {
if (state === 'completed') {
webContents.session.on("will-download", (event: Event, item: DownloadItem): void => {
item.once("done", (event, state) => {
if (state === "completed") {
const savePath = item.getSavePath();
const id = userDownloadIndex++;
userDownloadMap.set(id, savePath);
webContents.send('userDownloadCompleted', {
webContents.send("userDownloadCompleted", {
id,
name: path.basename(savePath),
});