Merge branch 'develop' of github.com:vector-im/element-desktop into langleyd/fix_seshat_delete_contents
This commit is contained in:
5
src/@types/global.d.ts
vendored
5
src/@types/global.d.ts
vendored
@@ -20,8 +20,8 @@ declare global {
|
||||
var launcher: AutoLaunch;
|
||||
var vectorConfig: Record<string, any>;
|
||||
var trayConfig: {
|
||||
// eslint-disable-next-line camelcase
|
||||
icon_path: string;
|
||||
color_icon_path: string; // eslint-disable-line camelcase
|
||||
monochrome_icon_path: string; // eslint-disable-line camelcase
|
||||
brand: string;
|
||||
};
|
||||
var store: Store<{
|
||||
@@ -31,6 +31,7 @@ declare global {
|
||||
autoHideMenuBar?: boolean;
|
||||
locale?: string | string[];
|
||||
disableHardwareAcceleration?: boolean;
|
||||
monochromeIcon?: boolean;
|
||||
}>;
|
||||
}
|
||||
/* eslint-enable no-var */
|
||||
|
||||
@@ -97,7 +97,7 @@ async function tryPaths(name: string, root: string, rawPaths: string[]): Promise
|
||||
try {
|
||||
await afs.stat(p);
|
||||
return p + "/";
|
||||
} catch (e) {}
|
||||
} catch {}
|
||||
}
|
||||
console.log(`Couldn't find ${name} files in any of: `);
|
||||
for (const p of paths) {
|
||||
@@ -137,7 +137,7 @@ async function loadConfig(): Promise<void> {
|
||||
|
||||
try {
|
||||
global.vectorConfig = loadJsonFile(asarPath, "config.json");
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// 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
|
||||
// file or invalid json, so node is just very unhelpful.
|
||||
@@ -212,9 +212,11 @@ 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 colorIconFile = `element.${process.platform === "win32" ? "ico" : "png"}`;
|
||||
const monochromeIconFile = `monochrome.${process.platform === "win32" ? "ico" : "png"}`;
|
||||
global.trayConfig = {
|
||||
icon_path: path.join(resPath, "img", iconFile),
|
||||
monochrome_icon_path: path.join(resPath, "img", monochromeIconFile),
|
||||
color_icon_path: path.join(resPath, "img", colorIconFile),
|
||||
brand: global.vectorConfig.brand || "Element",
|
||||
};
|
||||
|
||||
@@ -365,7 +367,7 @@ app.on("ready", async () => {
|
||||
|
||||
if (argv["devtools"]) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { default: installExt, REACT_DEVELOPER_TOOLS, REACT_PERF } = require("electron-devtools-installer");
|
||||
installExt(REACT_DEVELOPER_TOOLS)
|
||||
.then((name: string) => console.log(`Added Extension: ${name}`))
|
||||
@@ -453,7 +455,7 @@ app.on("ready", async () => {
|
||||
titleBarStyle: process.platform === "darwin" ? "hidden" : "default",
|
||||
trafficLightPosition: { x: 9, y: 8 },
|
||||
|
||||
icon: global.trayConfig.icon_path,
|
||||
icon: global.trayConfig.color_icon_path,
|
||||
show: false,
|
||||
autoHideMenuBar: global.store.get("autoHideMenuBar", true),
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
if (ret === null) {
|
||||
ret = await keytar?.getPassword("riot.im", `${args[0]}|${args[1]}`);
|
||||
}
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// 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;
|
||||
@@ -159,7 +159,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
const pickleKey = await randomArray(32);
|
||||
await keytar?.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey);
|
||||
ret = pickleKey;
|
||||
} catch (e) {
|
||||
} catch {
|
||||
ret = null;
|
||||
}
|
||||
break;
|
||||
@@ -170,7 +170,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) {
|
||||
// migrate from riot.im (remove once we think there will no longer be
|
||||
// logins from the time of riot.im)
|
||||
await keytar?.deletePassword("riot.im", `${args[0]}|${args[1]}`);
|
||||
} catch (e) {}
|
||||
} catch {}
|
||||
break;
|
||||
case "getDesktopCapturerSources":
|
||||
ret = (await desktopCapturer.getSources(args[0])).map((source) => ({
|
||||
|
||||
@@ -9,7 +9,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
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
keytar = require("keytar");
|
||||
} catch (e) {
|
||||
if ((<NodeJS.ErrnoException>e).code === "MODULE_NOT_FOUND") {
|
||||
|
||||
@@ -33,39 +33,74 @@ async function getAccessToken(window: BrowserWindow): Promise<string | undefined
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the homeserver url
|
||||
* This requires asking the renderer process for the homeserver url.
|
||||
*/
|
||||
async function getHomeserverUrl(window: BrowserWindow): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
ipcMain.once("homeserverUrl", (_, homeserver) => {
|
||||
resolve(homeserver);
|
||||
});
|
||||
window.webContents.send("homeserverUrl"); // ping now that the listener exists
|
||||
});
|
||||
}
|
||||
|
||||
export function setupMediaAuth(window: BrowserWindow): void {
|
||||
session.defaultSession.webRequest.onBeforeRequest(async (req, callback) => {
|
||||
// This handler emulates the element-web service worker, where URLs are rewritten late in the request
|
||||
// for backwards compatibility. As authenticated media becomes more prevalent, this should be replaced
|
||||
// by the app using authenticated URLs from the outset.
|
||||
let url = req.url;
|
||||
if (!url.includes("/_matrix/media/v3/download") && !url.includes("/_matrix/media/v3/thumbnail")) {
|
||||
return callback({}); // not a URL we care about
|
||||
}
|
||||
try {
|
||||
const url = new URL(req.url);
|
||||
if (
|
||||
!url.pathname.startsWith("/_matrix/media/v3/download") &&
|
||||
!url.pathname.startsWith("/_matrix/media/v3/thumbnail")
|
||||
) {
|
||||
return callback({}); // not a URL we care about
|
||||
}
|
||||
|
||||
const supportedVersions = await getSupportedVersions(window);
|
||||
// We have to check that the access token is truthy otherwise we'd be intercepting pre-login media request too,
|
||||
// e.g. those required for SSO button icons.
|
||||
const accessToken = await getAccessToken(window);
|
||||
if (supportedVersions.includes("v1.11") && accessToken) {
|
||||
url = url.replace(/\/media\/v3\/(.*)\//, "/client/v1/media/$1/");
|
||||
return callback({ redirectURL: url });
|
||||
} else {
|
||||
return callback({}); // no support == no modification
|
||||
const supportedVersions = await getSupportedVersions(window);
|
||||
// We have to check that the access token is truthy otherwise we'd be intercepting pre-login media request too,
|
||||
// e.g. those required for SSO button icons.
|
||||
const accessToken = await getAccessToken(window);
|
||||
if (supportedVersions.includes("v1.11") && accessToken) {
|
||||
url.href = url.href.replace(/\/media\/v3\/(.*)\//, "/client/v1/media/$1/");
|
||||
return callback({ redirectURL: url.toString() });
|
||||
} else {
|
||||
return callback({}); // no support == no modification
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
session.defaultSession.webRequest.onBeforeSendHeaders(async (req, callback) => {
|
||||
if (!req.url.includes("/_matrix/client/v1/media")) {
|
||||
return callback({}); // invoke unmodified
|
||||
}
|
||||
try {
|
||||
const url = new URL(req.url);
|
||||
if (!url.pathname.startsWith("/_matrix/client/v1/media")) {
|
||||
return callback({}); // invoke unmodified
|
||||
}
|
||||
|
||||
// Only add authorization header to authenticated media URLs. This emulates the service worker
|
||||
// behaviour in element-web.
|
||||
const accessToken = await getAccessToken(window);
|
||||
// `accessToken` can be falsy, but if we're trying to download media without authentication
|
||||
// then we should expect failure anyway.
|
||||
const headers = { ...req.requestHeaders, Authorization: `Bearer ${accessToken}` };
|
||||
return callback({ requestHeaders: headers });
|
||||
// Is this request actually going to the homeserver?
|
||||
// We don't combine this check with the one above on purpose.
|
||||
// We're fetching the homeserver url through IPC and should do so
|
||||
// as sparingly as possible.
|
||||
const homeserver = await getHomeserverUrl(window);
|
||||
const isRequestToHomeServer = homeserver && url.origin === new URL(homeserver).origin;
|
||||
if (!isRequestToHomeServer) {
|
||||
return callback({}); // invoke unmodified
|
||||
}
|
||||
|
||||
// Only add authorization header to authenticated media URLs. This emulates the service worker
|
||||
// behaviour in element-web.
|
||||
const accessToken = await getAccessToken(window);
|
||||
// `accessToken` can be falsy, but if we're trying to download media without authentication
|
||||
// then we should expect failure anyway.
|
||||
const headers = { ...req.requestHeaders, Authorization: `Bearer ${accessToken}` };
|
||||
return callback({ requestHeaders: headers });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ const CHANNELS = [
|
||||
"userDownloadAction",
|
||||
"openDesktopCapturerSourcePicker",
|
||||
"userAccessToken",
|
||||
"homeserverUrl",
|
||||
"serverSupportedVersions",
|
||||
];
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ function readStore(): Record<string, string> {
|
||||
const s = fs.readFileSync(storePath, { encoding: "utf8" });
|
||||
const o = JSON.parse(s);
|
||||
return typeof o === "object" ? o : {};
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ let SeshatRecovery: typeof SeshatRecoveryType;
|
||||
let ReindexError: typeof ReindexErrorType;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const seshatModule = require("matrix-seshat");
|
||||
Seshat = seshatModule.Seshat;
|
||||
SeshatRecovery = seshatModule.SeshatRecovery;
|
||||
@@ -267,7 +267,7 @@ ipcMain.on("seshat", async function (_ev: IpcMainEvent, payload): Promise<void>
|
||||
else {
|
||||
try {
|
||||
ret = await eventIndex.loadCheckpoints();
|
||||
} catch (e) {
|
||||
} catch {
|
||||
ret = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,4 +67,13 @@ export const Settings: Record<string, Setting> = {
|
||||
global.store.set("disableHardwareAcceleration", !value);
|
||||
},
|
||||
},
|
||||
"Electron.monochromeIcon": {
|
||||
async read(): Promise<any> {
|
||||
return tray.isMonochrome();
|
||||
},
|
||||
async write(value: any): Promise<void> {
|
||||
global.store.set("monochromeIcon", value);
|
||||
tray.refreshIcon();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
20
src/tray.ts
20
src/tray.ts
@@ -28,6 +28,19 @@ export function destroy(): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function isMonochrome(): boolean {
|
||||
return global.store.get("monochromeIcon", process.platform === "linux");
|
||||
}
|
||||
|
||||
export function refreshIcon(): void {
|
||||
const monochrome = isMonochrome();
|
||||
if (monochrome) {
|
||||
trayIcon?.setImage(nativeImage.createFromPath(global.trayConfig.monochrome_icon_path));
|
||||
} else {
|
||||
trayIcon?.setImage(nativeImage.createFromPath(global.trayConfig.color_icon_path));
|
||||
}
|
||||
}
|
||||
|
||||
function toggleWin(): void {
|
||||
if (global.mainWindow?.isVisible() && !global.mainWindow.isMinimized() && global.mainWindow.isFocused()) {
|
||||
global.mainWindow.hide();
|
||||
@@ -39,7 +52,8 @@ function toggleWin(): void {
|
||||
}
|
||||
|
||||
interface IConfig {
|
||||
icon_path: string; // eslint-disable-line camelcase
|
||||
color_icon_path: string; // eslint-disable-line camelcase
|
||||
monochrome_icon_path: string; // eslint-disable-line camelcase
|
||||
brand: string;
|
||||
}
|
||||
|
||||
@@ -52,7 +66,9 @@ function getUuid(): string {
|
||||
export function create(config: IConfig): void {
|
||||
// no trays on darwin
|
||||
if (process.platform === "darwin" || trayIcon) return;
|
||||
const defaultIcon = nativeImage.createFromPath(config.icon_path);
|
||||
const defaultIcon = nativeImage.createFromPath(
|
||||
isMonochrome() ? config.monochrome_icon_path : config.color_icon_path,
|
||||
);
|
||||
|
||||
let guid: string | undefined;
|
||||
if (process.platform === "win32" && app.isPackaged) {
|
||||
|
||||
@@ -176,15 +176,18 @@ function onLinkContextMenu(ev: Event, params: ContextMenuParams, webContents: We
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
function cutCopyPasteSelectContextMenus(params: ContextMenuParams): MenuItemConstructorOptions[] {
|
||||
function cutCopyPasteSelectContextMenus(
|
||||
params: ContextMenuParams,
|
||||
webContents: WebContents,
|
||||
): MenuItemConstructorOptions[] {
|
||||
const options: MenuItemConstructorOptions[] = [];
|
||||
|
||||
if (params.misspelledWord) {
|
||||
params.dictionarySuggestions.forEach((word) => {
|
||||
options.push({
|
||||
label: word,
|
||||
click: (menuItem, browserWindow) => {
|
||||
browserWindow?.webContents.replaceMisspelling(word);
|
||||
click: () => {
|
||||
webContents.replaceMisspelling(word);
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -194,8 +197,8 @@ function cutCopyPasteSelectContextMenus(params: ContextMenuParams): MenuItemCons
|
||||
},
|
||||
{
|
||||
label: _t("right_click_menu|add_to_dictionary"),
|
||||
click: (menuItem, browserWindow) => {
|
||||
browserWindow?.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord);
|
||||
click: () => {
|
||||
webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -237,8 +240,8 @@ function cutCopyPasteSelectContextMenus(params: ContextMenuParams): MenuItemCons
|
||||
return options;
|
||||
}
|
||||
|
||||
function onSelectedContextMenu(ev: Event, params: ContextMenuParams): void {
|
||||
const items = cutCopyPasteSelectContextMenus(params);
|
||||
function onSelectedContextMenu(ev: Event, params: ContextMenuParams, webContents: WebContents): void {
|
||||
const items = cutCopyPasteSelectContextMenus(params, webContents);
|
||||
const popupMenu = Menu.buildFromTemplate(items);
|
||||
|
||||
// popup() requires an options object even for no options
|
||||
@@ -246,12 +249,12 @@ function onSelectedContextMenu(ev: Event, params: ContextMenuParams): void {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
function onEditableContextMenu(ev: Event, params: ContextMenuParams): void {
|
||||
function onEditableContextMenu(ev: Event, params: ContextMenuParams, webContents: WebContents): void {
|
||||
const items: MenuItemConstructorOptions[] = [
|
||||
{ role: "undo" },
|
||||
{ role: "redo", enabled: params.editFlags.canRedo },
|
||||
{ type: "separator" },
|
||||
...cutCopyPasteSelectContextMenus(params),
|
||||
...cutCopyPasteSelectContextMenus(params, webContents),
|
||||
];
|
||||
|
||||
const popupMenu = Menu.buildFromTemplate(items);
|
||||
@@ -286,9 +289,9 @@ export default (webContents: WebContents): void => {
|
||||
if (params.linkURL || params.srcURL) {
|
||||
onLinkContextMenu(ev, params, webContents);
|
||||
} else if (params.selectionText) {
|
||||
onSelectedContextMenu(ev, params);
|
||||
onSelectedContextMenu(ev, params, webContents);
|
||||
} else if (params.isEditable) {
|
||||
onEditableContextMenu(ev, params);
|
||||
onEditableContextMenu(ev, params, webContents);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user