mirror of
https://github.com/CringeStudios/element-desktop.git
synced 2025-01-18 15:34:59 +01:00
Split electron-main into smaller chunks (#377)
* Split electron-main into smaller chunks * Affix @types/node version and upgrade electron-store * Iterate PR * tidy up * Actually run the split out code
This commit is contained in:
parent
b7a0402de5
commit
389f6f4334
@ -42,7 +42,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"auto-launch": "^5.0.5",
|
"auto-launch": "^5.0.5",
|
||||||
"counterpart": "^0.18.6",
|
"counterpart": "^0.18.6",
|
||||||
"electron-store": "^6.0.1",
|
"electron-store": "^8.0.2",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
"minimist": "^1.2.6",
|
"minimist": "^1.2.6",
|
||||||
"png-to-ico": "^2.1.1",
|
"png-to-ico": "^2.1.1",
|
||||||
@ -88,6 +88,9 @@
|
|||||||
"matrix-seshat": "^2.3.3",
|
"matrix-seshat": "^2.3.3",
|
||||||
"keytar": "^7.9.0"
|
"keytar": "^7.9.0"
|
||||||
},
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"@types/node": "16.11.38"
|
||||||
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "im.riot.app",
|
"appId": "im.riot.app",
|
||||||
"asarUnpack": "**/*.node",
|
"asarUnpack": "**/*.node",
|
||||||
|
22
src/@types/global.d.ts
vendored
22
src/@types/global.d.ts
vendored
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2021 New Vector Ltd
|
Copyright 2021 - 2022 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -15,12 +15,32 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { BrowserWindow } from "electron";
|
import { BrowserWindow } from "electron";
|
||||||
|
import Store from "electron-store";
|
||||||
|
import AutoLaunch from "auto-launch";
|
||||||
|
|
||||||
|
import { AppLocalization } from "../language-helper";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace NodeJS {
|
namespace NodeJS {
|
||||||
interface Global {
|
interface Global {
|
||||||
mainWindow: BrowserWindow;
|
mainWindow: BrowserWindow;
|
||||||
appQuitting: boolean;
|
appQuitting: boolean;
|
||||||
|
appLocalization: AppLocalization;
|
||||||
|
launcher: AutoLaunch;
|
||||||
|
vectorConfig: Record<string, any>;
|
||||||
|
trayConfig: {
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
icon_path: string;
|
||||||
|
brand: string;
|
||||||
|
};
|
||||||
|
store: Store<{
|
||||||
|
warnBeforeExit?: boolean;
|
||||||
|
minimizeToTray?: boolean;
|
||||||
|
spellCheckerEnabled?: boolean;
|
||||||
|
autoHideMenuBar?: boolean;
|
||||||
|
locale?: string | string[];
|
||||||
|
disableHardwareAcceleration?: boolean;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,90 +21,42 @@ limitations under the License.
|
|||||||
import "./squirrelhooks";
|
import "./squirrelhooks";
|
||||||
import {
|
import {
|
||||||
app,
|
app,
|
||||||
ipcMain,
|
|
||||||
powerSaveBlocker,
|
|
||||||
BrowserWindow,
|
BrowserWindow,
|
||||||
Menu,
|
Menu,
|
||||||
autoUpdater,
|
autoUpdater,
|
||||||
protocol,
|
protocol,
|
||||||
dialog,
|
dialog,
|
||||||
desktopCapturer,
|
|
||||||
} from "electron";
|
} from "electron";
|
||||||
import AutoLaunch from "auto-launch";
|
import AutoLaunch from "auto-launch";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import windowStateKeeper from 'electron-window-state';
|
import windowStateKeeper from 'electron-window-state';
|
||||||
import Store from 'electron-store';
|
import Store from 'electron-store';
|
||||||
import fs, { promises as afs } from "fs";
|
import fs, { promises as afs } from "fs";
|
||||||
import crypto from "crypto";
|
|
||||||
import { URL } from "url";
|
import { URL } from "url";
|
||||||
import minimist from "minimist";
|
import minimist from "minimist";
|
||||||
|
|
||||||
import type * as Keytar from "keytar"; // Hak dependency type
|
import "./ipc";
|
||||||
import type {
|
import "./keytar";
|
||||||
Seshat as SeshatType,
|
import "./seshat";
|
||||||
SeshatRecovery as SeshatRecoveryType,
|
import "./settings";
|
||||||
ReindexError as ReindexErrorType,
|
|
||||||
} from "matrix-seshat"; // Hak dependency type
|
|
||||||
import * as tray from "./tray";
|
import * as tray from "./tray";
|
||||||
import { buildMenuTemplate } from './vectormenu';
|
import { buildMenuTemplate } from './vectormenu';
|
||||||
import webContentsHandler from './webcontents-handler';
|
import webContentsHandler from './webcontents-handler';
|
||||||
import * as updater from './updater';
|
import * as updater from './updater';
|
||||||
import { getProfileFromDeeplink, protocolInit, recordSSOSession } from './protocol';
|
import { getProfileFromDeeplink, protocolInit } from './protocol';
|
||||||
import { _t, AppLocalization } from './language-helper';
|
import { _t, AppLocalization } from './language-helper';
|
||||||
import Input = Electron.Input;
|
import Input = Electron.Input;
|
||||||
import IpcMainEvent = Electron.IpcMainEvent;
|
|
||||||
|
|
||||||
const argv = minimist(process.argv, {
|
const argv = minimist(process.argv, {
|
||||||
alias: { help: "h" },
|
alias: { help: "h" },
|
||||||
});
|
});
|
||||||
|
|
||||||
let keytar: typeof Keytar;
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let seshatSupported = false;
|
|
||||||
let Seshat: typeof SeshatType;
|
|
||||||
let SeshatRecovery: typeof SeshatRecoveryType;
|
|
||||||
let ReindexError: typeof ReindexErrorType;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const seshatModule = require('matrix-seshat');
|
|
||||||
Seshat = seshatModule.Seshat;
|
|
||||||
SeshatRecovery = seshatModule.SeshatRecovery;
|
|
||||||
ReindexError = seshatModule.ReindexError;
|
|
||||||
seshatSupported = true;
|
|
||||||
} catch (e) {
|
|
||||||
if (e.code === "MODULE_NOT_FOUND") {
|
|
||||||
console.log("Seshat isn't installed, event indexing is disabled.");
|
|
||||||
} else {
|
|
||||||
console.warn("Seshat unexpected error:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Things we need throughout the file but need to be created
|
// Things we need throughout the file but need to be created
|
||||||
// async to are initialised in setupGlobals()
|
// async to are initialised in setupGlobals()
|
||||||
let asarPath: string;
|
let asarPath: string;
|
||||||
let resPath: string;
|
let resPath: string;
|
||||||
let iconPath: string;
|
let iconPath: string;
|
||||||
|
|
||||||
let vectorConfig: Record<string, any>;
|
|
||||||
let trayConfig: {
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
icon_path: string;
|
|
||||||
brand: string;
|
|
||||||
};
|
|
||||||
let launcher: AutoLaunch;
|
|
||||||
let appLocalization: AppLocalization;
|
|
||||||
|
|
||||||
if (argv["help"]) {
|
if (argv["help"]) {
|
||||||
console.log("Options:");
|
console.log("Options:");
|
||||||
console.log(" --profile-dir {path}: Path to where to store the profile.");
|
console.log(" --profile-dir {path}: Path to where to store the profile.");
|
||||||
@ -199,13 +151,13 @@ async function setupGlobals(): Promise<void> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
vectorConfig = require(asarPath + 'config.json');
|
global.vectorConfig = require(asarPath + 'config.json');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// it would be nice to check the error code here and bail if the config
|
// 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
|
// is unparsable, but we get MODULE_NOT_FOUND in the case of a missing
|
||||||
// file or invalid json, so node is just very unhelpful.
|
// file or invalid json, so node is just very unhelpful.
|
||||||
// Continue with the defaults (ie. an empty config)
|
// Continue with the defaults (ie. an empty config)
|
||||||
vectorConfig = {};
|
global.vectorConfig = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -219,19 +171,19 @@ async function setupGlobals(): Promise<void> {
|
|||||||
const homeserverProps = ['default_is_url', 'default_hs_url', 'default_server_name', 'default_server_config'];
|
const homeserverProps = ['default_is_url', 'default_hs_url', 'default_server_name', 'default_server_config'];
|
||||||
if (Object.keys(localConfig).find(k => homeserverProps.includes(k))) {
|
if (Object.keys(localConfig).find(k => homeserverProps.includes(k))) {
|
||||||
// Rip out all the homeserver options from the vector config
|
// Rip out all the homeserver options from the vector config
|
||||||
vectorConfig = Object.keys(vectorConfig)
|
global.vectorConfig = Object.keys(global.vectorConfig)
|
||||||
.filter(k => !homeserverProps.includes(k))
|
.filter(k => !homeserverProps.includes(k))
|
||||||
.reduce((obj, key) => {obj[key] = vectorConfig[key]; return obj;}, {});
|
.reduce((obj, key) => {obj[key] = global.vectorConfig[key]; return obj;}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
vectorConfig = Object.assign(vectorConfig, localConfig);
|
global.vectorConfig = Object.assign(global.vectorConfig, localConfig);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof SyntaxError) {
|
if (e instanceof SyntaxError) {
|
||||||
dialog.showMessageBox({
|
dialog.showMessageBox({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: `Your ${vectorConfig.brand || 'Element'} is misconfigured`,
|
title: `Your ${global.vectorConfig.brand || 'Element'} is misconfigured`,
|
||||||
message: `Your custom ${vectorConfig.brand || 'Element'} configuration contains invalid JSON. ` +
|
message: `Your custom ${global.vectorConfig.brand || 'Element'} configuration contains invalid JSON. ` +
|
||||||
`Please correct the problem and reopen ${vectorConfig.brand || 'Element'}.`,
|
`Please correct the problem and reopen ${global.vectorConfig.brand || 'Element'}.`,
|
||||||
detail: e.message || "",
|
detail: e.message || "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -243,14 +195,14 @@ async function setupGlobals(): Promise<void> {
|
|||||||
// It's important to call `path.join` so we don't end up with the packaged asar in the final path.
|
// 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);
|
iconPath = path.join(resPath, "img", iconFile);
|
||||||
trayConfig = {
|
global.trayConfig = {
|
||||||
icon_path: iconPath,
|
icon_path: iconPath,
|
||||||
brand: vectorConfig.brand || 'Element',
|
brand: global.vectorConfig.brand || 'Element',
|
||||||
};
|
};
|
||||||
|
|
||||||
// launcher
|
// launcher
|
||||||
launcher = new AutoLaunch({
|
global.launcher = new AutoLaunch({
|
||||||
name: vectorConfig.brand || 'Element',
|
name: global.vectorConfig.brand || 'Element',
|
||||||
isHidden: true,
|
isHidden: true,
|
||||||
mac: {
|
mac: {
|
||||||
useLaunchAgent: true,
|
useLaunchAgent: true,
|
||||||
@ -261,7 +213,7 @@ async function setupGlobals(): Promise<void> {
|
|||||||
async function moveAutoLauncher(): Promise<void> {
|
async function moveAutoLauncher(): Promise<void> {
|
||||||
// Look for an auto-launcher under 'Riot' and if we find one, port it's
|
// Look for an auto-launcher under 'Riot' and if we find one, port it's
|
||||||
// enabled/disabled-ness over to the new 'Element' launcher
|
// enabled/disabled-ness over to the new 'Element' launcher
|
||||||
if (!vectorConfig.brand || vectorConfig.brand === 'Element') {
|
if (!global.vectorConfig.brand || global.vectorConfig.brand === 'Element') {
|
||||||
const oldLauncher = new AutoLaunch({
|
const oldLauncher = new AutoLaunch({
|
||||||
name: 'Riot',
|
name: 'Riot',
|
||||||
isHidden: true,
|
isHidden: true,
|
||||||
@ -272,24 +224,13 @@ async function moveAutoLauncher(): Promise<void> {
|
|||||||
const wasEnabled = await oldLauncher.isEnabled();
|
const wasEnabled = await oldLauncher.isEnabled();
|
||||||
if (wasEnabled) {
|
if (wasEnabled) {
|
||||||
await oldLauncher.disable();
|
await oldLauncher.disable();
|
||||||
await launcher.enable();
|
await global.launcher.enable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventStorePath = path.join(app.getPath('userData'), 'EventStore');
|
global.store = new Store({ name: "electron-config" });
|
||||||
const store = new Store<{
|
|
||||||
warnBeforeExit?: boolean;
|
|
||||||
minimizeToTray?: boolean;
|
|
||||||
spellCheckerEnabled?: boolean;
|
|
||||||
autoHideMenuBar?: boolean;
|
|
||||||
locale?: string | string[];
|
|
||||||
disableHardwareAcceleration?: boolean;
|
|
||||||
}>({ name: "electron-config" });
|
|
||||||
|
|
||||||
let eventIndex: SeshatType = null;
|
|
||||||
|
|
||||||
let mainWindow: BrowserWindow = null;
|
|
||||||
global.appQuitting = false;
|
global.appQuitting = false;
|
||||||
|
|
||||||
const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
|
const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
|
||||||
@ -299,12 +240,12 @@ const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const warnBeforeExit = (event: Event, input: Input): void => {
|
const warnBeforeExit = (event: Event, input: Input): void => {
|
||||||
const shouldWarnBeforeExit = store.get('warnBeforeExit', true);
|
const shouldWarnBeforeExit = global.store.get('warnBeforeExit', true);
|
||||||
const exitShortcutPressed =
|
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) {
|
if (shouldWarnBeforeExit && exitShortcutPressed) {
|
||||||
const shouldCancelCloseRequest = dialog.showMessageBoxSync(mainWindow, {
|
const shouldCancelCloseRequest = dialog.showMessageBoxSync(global.mainWindow, {
|
||||||
type: "question",
|
type: "question",
|
||||||
buttons: [_t("Cancel"), _t("Close Element")],
|
buttons: [_t("Cancel"), _t("Close Element")],
|
||||||
message: _t("Are you sure you want to quit?"),
|
message: _t("Are you sure you want to quit?"),
|
||||||
@ -318,25 +259,6 @@ const warnBeforeExit = (event: Event, input: Input): void => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteContents = async (p: string): Promise<void> => {
|
|
||||||
for (const entry of await afs.readdir(p)) {
|
|
||||||
const curPath = path.join(p, entry);
|
|
||||||
await afs.unlink(curPath);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function randomArray(size: number): Promise<string> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
crypto.randomBytes(size, (err, buf) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(buf.toString("base64").replace(/=+$/g, ''));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle uncaught errors otherwise it displays
|
// handle uncaught errors otherwise it displays
|
||||||
// stack traces in popup dialogs, which is terrible (which
|
// stack traces in popup dialogs, which is terrible (which
|
||||||
// it will do any time the auto update poke fails, and there's
|
// it will do any time the auto update poke fails, and there's
|
||||||
@ -347,510 +269,6 @@ process.on('uncaughtException', function(error: Error): void {
|
|||||||
console.log('Unhandled exception', error);
|
console.log('Unhandled exception', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
let focusHandlerAttached = false;
|
|
||||||
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
|
|
||||||
app.badgeCount = count;
|
|
||||||
}
|
|
||||||
if (count === 0 && mainWindow) {
|
|
||||||
mainWindow.flashFrame(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('loudNotification', function(): void {
|
|
||||||
if (process.platform === 'win32' && mainWindow && !mainWindow.isFocused() && !focusHandlerAttached) {
|
|
||||||
mainWindow.flashFrame(true);
|
|
||||||
mainWindow.once('focus', () => {
|
|
||||||
mainWindow.flashFrame(false);
|
|
||||||
focusHandlerAttached = false;
|
|
||||||
});
|
|
||||||
focusHandlerAttached = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let powerSaveBlockerId: number = null;
|
|
||||||
ipcMain.on('app_onAction', function(_ev: IpcMainEvent, 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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
interface Setting {
|
|
||||||
read(): Promise<any>;
|
|
||||||
write(value: any): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings: Record<string, Setting> = {
|
|
||||||
"Electron.autoLaunch": {
|
|
||||||
async read(): Promise<any> {
|
|
||||||
return launcher.isEnabled();
|
|
||||||
},
|
|
||||||
async write(value: any): Promise<void> {
|
|
||||||
if (value) {
|
|
||||||
return launcher.enable();
|
|
||||||
} else {
|
|
||||||
return launcher.disable();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Electron.warnBeforeExit": {
|
|
||||||
async read(): Promise<any> {
|
|
||||||
return store.get("warnBeforeExit", true);
|
|
||||||
},
|
|
||||||
async write(value: any): Promise<void> {
|
|
||||||
store.set("warnBeforeExit", value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Electron.alwaysShowMenuBar": { // not supported on macOS
|
|
||||||
async read(): Promise<any> {
|
|
||||||
return !global.mainWindow.autoHideMenuBar;
|
|
||||||
},
|
|
||||||
async write(value: any): Promise<void> {
|
|
||||||
store.set('autoHideMenuBar', !value);
|
|
||||||
global.mainWindow.autoHideMenuBar = !value;
|
|
||||||
global.mainWindow.setMenuBarVisibility(value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Electron.showTrayIcon": { // not supported on macOS
|
|
||||||
async read(): Promise<any> {
|
|
||||||
return tray.hasTray();
|
|
||||||
},
|
|
||||||
async write(value: any): Promise<void> {
|
|
||||||
if (value) {
|
|
||||||
// Create trayIcon icon
|
|
||||||
tray.create(trayConfig);
|
|
||||||
} else {
|
|
||||||
tray.destroy();
|
|
||||||
}
|
|
||||||
store.set('minimizeToTray', value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Electron.enableHardwareAcceleration": {
|
|
||||||
async read(): Promise<any> {
|
|
||||||
return !store.get('disableHardwareAcceleration', false);
|
|
||||||
},
|
|
||||||
async write(value: any): Promise<void> {
|
|
||||||
store.set('disableHardwareAcceleration', !value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) {
|
|
||||||
if (!mainWindow) return;
|
|
||||||
|
|
||||||
const args = payload.args || [];
|
|
||||||
let ret: any;
|
|
||||||
|
|
||||||
switch (payload.name) {
|
|
||||||
case 'getUpdateFeedUrl':
|
|
||||||
ret = autoUpdater.getFeedURL();
|
|
||||||
break;
|
|
||||||
case 'getSettingValue': {
|
|
||||||
const [settingName] = args;
|
|
||||||
const setting = settings[settingName];
|
|
||||||
ret = await setting.read();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'setSettingValue': {
|
|
||||||
const [settingName, value] = args;
|
|
||||||
const setting = settings[settingName];
|
|
||||||
await setting.write(value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'setLanguage':
|
|
||||||
appLocalization.setAppLocale(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;
|
|
||||||
case 'navigateBack':
|
|
||||||
if (mainWindow.webContents.canGoBack()) {
|
|
||||||
mainWindow.webContents.goBack();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'navigateForward':
|
|
||||||
if (mainWindow.webContents.canGoForward()) {
|
|
||||||
mainWindow.webContents.goForward();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'setSpellCheckLanguages':
|
|
||||||
if (args[0] && args[0].length > 0) {
|
|
||||||
mainWindow.webContents.session.setSpellCheckerEnabled(true);
|
|
||||||
store.set("spellCheckerEnabled", true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
mainWindow.webContents.session.setSpellCheckerLanguages(args[0]);
|
|
||||||
} catch (er) {
|
|
||||||
console.log("There were problems setting the spellcheck languages", er);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mainWindow.webContents.session.setSpellCheckerEnabled(false);
|
|
||||||
store.set("spellCheckerEnabled", false);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'getSpellCheckLanguages':
|
|
||||||
if (store.get("spellCheckerEnabled", true)) {
|
|
||||||
ret = mainWindow.webContents.session.getSpellCheckerLanguages();
|
|
||||||
} else {
|
|
||||||
ret = [];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'getAvailableSpellCheckLanguages':
|
|
||||||
ret = mainWindow.webContents.session.availableSpellCheckerLanguages;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'startSSOFlow':
|
|
||||||
recordSSOSession(args[0]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
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
|
|
||||||
// logins from the time of riot.im)
|
|
||||||
if (ret === null) {
|
|
||||||
ret = await keytar.getPassword("riot.im", `${args[0]}|${args[1]}`);
|
|
||||||
}
|
|
||||||
} 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 {
|
|
||||||
const pickleKey = await randomArray(32);
|
|
||||||
await keytar.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey);
|
|
||||||
ret = pickleKey;
|
|
||||||
} catch (e) {
|
|
||||||
ret = null;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
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
|
|
||||||
// logins from the time of riot.im)
|
|
||||||
await keytar.deletePassword("riot.im", `${args[0]}|${args[1]}`);
|
|
||||||
} catch (e) {}
|
|
||||||
break;
|
|
||||||
case 'getDesktopCapturerSources':
|
|
||||||
ret = (await desktopCapturer.getSources(args[0])).map((source) => ({
|
|
||||||
id: source.id,
|
|
||||||
name: source.name,
|
|
||||||
thumbnailURL: source.thumbnail.toDataURL(),
|
|
||||||
}));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
mainWindow.webContents.send('ipcReply', {
|
|
||||||
id: payload.id,
|
|
||||||
error: "Unknown IPC Call: " + payload.name,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mainWindow.webContents.send('ipcReply', {
|
|
||||||
id: payload.id,
|
|
||||||
reply: ret,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const seshatDefaultPassphrase = "DEFAULT_PASSPHRASE";
|
|
||||||
async function getOrCreatePassphrase(key: string): Promise<string> {
|
|
||||||
if (keytar) {
|
|
||||||
try {
|
|
||||||
const storedPassphrase = await keytar.getPassword("element.io", key);
|
|
||||||
if (storedPassphrase !== null) {
|
|
||||||
return storedPassphrase;
|
|
||||||
} else {
|
|
||||||
const newPassphrase = await randomArray(32);
|
|
||||||
await keytar.setPassword("element.io", key, newPassphrase);
|
|
||||||
return newPassphrase;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Error getting the event index passphrase out of the secret store", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return seshatDefaultPassphrase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
|
||||||
if (!mainWindow) return;
|
|
||||||
|
|
||||||
const sendError = (id, e) => {
|
|
||||||
const error = {
|
|
||||||
message: e.message,
|
|
||||||
};
|
|
||||||
|
|
||||||
mainWindow.webContents.send('seshatReply', {
|
|
||||||
id: id,
|
|
||||||
error: error,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const args = payload.args || [];
|
|
||||||
let ret: any;
|
|
||||||
|
|
||||||
switch (payload.name) {
|
|
||||||
case 'supportsEventIndexing':
|
|
||||||
ret = seshatSupported;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'initEventIndex':
|
|
||||||
if (eventIndex === null) {
|
|
||||||
const userId = args[0];
|
|
||||||
const deviceId = args[1];
|
|
||||||
const passphraseKey = `seshat|${userId}|${deviceId}`;
|
|
||||||
|
|
||||||
const passphrase = await getOrCreatePassphrase(passphraseKey);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await afs.mkdir(eventStorePath, { recursive: true });
|
|
||||||
eventIndex = 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.
|
|
||||||
const recoveryIndex = new SeshatRecovery(eventStorePath, {
|
|
||||||
passphrase,
|
|
||||||
});
|
|
||||||
|
|
||||||
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 {
|
|
||||||
await deleteContents(eventStorePath);
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await recoveryIndex.reindex();
|
|
||||||
}
|
|
||||||
|
|
||||||
eventIndex = new Seshat(eventStorePath, { passphrase });
|
|
||||||
} else {
|
|
||||||
sendError(payload.id, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'closeEventIndex':
|
|
||||||
if (eventIndex !== null) {
|
|
||||||
const index = eventIndex;
|
|
||||||
eventIndex = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await index.shutdown();
|
|
||||||
} catch (e) {
|
|
||||||
sendError(payload.id, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'deleteEventIndex':
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
await deleteContents(eventStorePath);
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'isEventIndexEmpty':
|
|
||||||
if (eventIndex === null) ret = true;
|
|
||||||
else ret = await eventIndex.isEmpty();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'isRoomIndexed':
|
|
||||||
if (eventIndex === null) ret = false;
|
|
||||||
else ret = await eventIndex.isRoomIndexed(args[0]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'addEventToIndex':
|
|
||||||
try {
|
|
||||||
eventIndex.addEvent(args[0], args[1]);
|
|
||||||
} catch (e) {
|
|
||||||
sendError(payload.id, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'deleteEvent':
|
|
||||||
try {
|
|
||||||
ret = await eventIndex.deleteEvent(args[0]);
|
|
||||||
} catch (e) {
|
|
||||||
sendError(payload.id, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
case 'getStats':
|
|
||||||
if (eventIndex === null) ret = 0;
|
|
||||||
else {
|
|
||||||
try {
|
|
||||||
ret = await eventIndex.getStats();
|
|
||||||
} catch (e) {
|
|
||||||
sendError(payload.id, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
case 'loadFileEvents':
|
|
||||||
if (eventIndex === null) ret = [];
|
|
||||||
else {
|
|
||||||
try {
|
|
||||||
ret = await eventIndex.loadFileEvents(args[0]);
|
|
||||||
} catch (e) {
|
|
||||||
sendError(payload.id, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'loadCheckpoints':
|
|
||||||
if (eventIndex === null) ret = [];
|
|
||||||
else {
|
|
||||||
try {
|
|
||||||
ret = await eventIndex.loadCheckpoints();
|
|
||||||
} catch (e) {
|
|
||||||
ret = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
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');
|
app.commandLine.appendSwitch('--enable-usermedia-screen-capturing');
|
||||||
if (!app.commandLine.hasSwitch('enable-features')) {
|
if (!app.commandLine.hasSwitch('enable-features')) {
|
||||||
app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer');
|
app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer');
|
||||||
@ -894,7 +312,7 @@ app.enableSandbox();
|
|||||||
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService');
|
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService');
|
||||||
|
|
||||||
// Disable hardware acceleration if the setting has been set.
|
// Disable hardware acceleration if the setting has been set.
|
||||||
if (store.get('disableHardwareAcceleration', false) === true) {
|
if (global.store.get('disableHardwareAcceleration', false) === true) {
|
||||||
console.log("Disabling hardware acceleration.");
|
console.log("Disabling hardware acceleration.");
|
||||||
app.disableHardwareAcceleration();
|
app.disableHardwareAcceleration();
|
||||||
}
|
}
|
||||||
@ -982,9 +400,9 @@ app.on('ready', async () => {
|
|||||||
|
|
||||||
if (argv['no-update']) {
|
if (argv['no-update']) {
|
||||||
console.log('Auto update disabled via command line flag "--no-update"');
|
console.log('Auto update disabled via command line flag "--no-update"');
|
||||||
} else if (vectorConfig['update_base_url']) {
|
} else if (global.vectorConfig['update_base_url']) {
|
||||||
console.log(`Starting auto update with base URL: ${vectorConfig['update_base_url']}`);
|
console.log(`Starting auto update with base URL: ${global.vectorConfig['update_base_url']}`);
|
||||||
updater.start(vectorConfig['update_base_url']);
|
updater.start(global.vectorConfig['update_base_url']);
|
||||||
} else {
|
} 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');
|
||||||
}
|
}
|
||||||
@ -996,13 +414,13 @@ app.on('ready', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const preloadScript = path.normalize(`${__dirname}/preload.js`);
|
const preloadScript = path.normalize(`${__dirname}/preload.js`);
|
||||||
mainWindow = global.mainWindow = new BrowserWindow({
|
global.mainWindow = new BrowserWindow({
|
||||||
// https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
// https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
|
|
||||||
icon: iconPath,
|
icon: iconPath,
|
||||||
show: false,
|
show: false,
|
||||||
autoHideMenuBar: store.get('autoHideMenuBar', true),
|
autoHideMenuBar: global.store.get('autoHideMenuBar', true),
|
||||||
|
|
||||||
x: mainWindowState.x,
|
x: mainWindowState.x,
|
||||||
y: mainWindowState.y,
|
y: mainWindowState.y,
|
||||||
@ -1016,32 +434,32 @@ app.on('ready', async () => {
|
|||||||
webgl: true,
|
webgl: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
mainWindow.loadURL('vector://vector/webapp/');
|
global.mainWindow.loadURL('vector://vector/webapp/');
|
||||||
|
|
||||||
// Handle spellchecker
|
// Handle spellchecker
|
||||||
// For some reason spellCheckerEnabled isn't persisted so we have to use the store here
|
// For some reason spellCheckerEnabled isn't persisted, so we have to use the store here
|
||||||
mainWindow.webContents.session.setSpellCheckerEnabled(store.get("spellCheckerEnabled", true));
|
global.mainWindow.webContents.session.setSpellCheckerEnabled(global.store.get("spellCheckerEnabled", true));
|
||||||
|
|
||||||
// Create trayIcon icon
|
// Create trayIcon icon
|
||||||
if (store.get('minimizeToTray', true)) tray.create(trayConfig);
|
if (global.store.get('minimizeToTray', true)) tray.create(global.trayConfig);
|
||||||
|
|
||||||
mainWindow.once('ready-to-show', () => {
|
global.mainWindow.once('ready-to-show', () => {
|
||||||
mainWindowState.manage(mainWindow);
|
mainWindowState.manage(global.mainWindow);
|
||||||
|
|
||||||
if (!argv['hidden']) {
|
if (!argv['hidden']) {
|
||||||
mainWindow.show();
|
global.mainWindow.show();
|
||||||
} else {
|
} else {
|
||||||
// hide here explicitly because window manage above sometimes shows it
|
// hide here explicitly because window manage above sometimes shows it
|
||||||
mainWindow.hide();
|
global.mainWindow.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mainWindow.webContents.on('before-input-event', warnBeforeExit);
|
global.mainWindow.webContents.on('before-input-event', warnBeforeExit);
|
||||||
|
|
||||||
mainWindow.on('closed', () => {
|
global.mainWindow.on('closed', () => {
|
||||||
mainWindow = global.mainWindow = null;
|
global.mainWindow = null;
|
||||||
});
|
});
|
||||||
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 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
|
// On Mac, closing the window just hides it
|
||||||
@ -1049,12 +467,12 @@ app.on('ready', async () => {
|
|||||||
// behave, eg. Mail.app)
|
// behave, eg. Mail.app)
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (mainWindow.isFullScreen()) {
|
if (global.mainWindow.isFullScreen()) {
|
||||||
mainWindow.once('leave-full-screen', () => mainWindow.hide());
|
global.mainWindow.once('leave-full-screen', () => global.mainWindow.hide());
|
||||||
|
|
||||||
mainWindow.setFullScreen(false);
|
global.mainWindow.setFullScreen(false);
|
||||||
} else {
|
} else {
|
||||||
mainWindow.hide();
|
global.mainWindow.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -1063,19 +481,19 @@ app.on('ready', async () => {
|
|||||||
|
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
// Handle forward/backward mouse buttons in Windows
|
// Handle forward/backward mouse buttons in Windows
|
||||||
mainWindow.on('app-command', (e, cmd) => {
|
global.mainWindow.on('app-command', (e, cmd) => {
|
||||||
if (cmd === 'browser-backward' && mainWindow.webContents.canGoBack()) {
|
if (cmd === 'browser-backward' && global.mainWindow.webContents.canGoBack()) {
|
||||||
mainWindow.webContents.goBack();
|
global.mainWindow.webContents.goBack();
|
||||||
} else if (cmd === 'browser-forward' && mainWindow.webContents.canGoForward()) {
|
} else if (cmd === 'browser-forward' && global.mainWindow.webContents.canGoForward()) {
|
||||||
mainWindow.webContents.goForward();
|
global.mainWindow.webContents.goForward();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
webContentsHandler(mainWindow.webContents);
|
webContentsHandler(global.mainWindow.webContents);
|
||||||
|
|
||||||
appLocalization = new AppLocalization({
|
global.appLocalization = new AppLocalization({
|
||||||
store,
|
store: global.store,
|
||||||
components: [
|
components: [
|
||||||
() => tray.initApplicationMenu(),
|
() => tray.initApplicationMenu(),
|
||||||
() => Menu.setApplicationMenu(buildMenuTemplate()),
|
() => Menu.setApplicationMenu(buildMenuTemplate()),
|
||||||
@ -1088,14 +506,12 @@ app.on('window-all-closed', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
mainWindow.show();
|
global.mainWindow.show();
|
||||||
});
|
});
|
||||||
|
|
||||||
function beforeQuit(): void {
|
function beforeQuit(): void {
|
||||||
global.appQuitting = true;
|
global.appQuitting = true;
|
||||||
if (mainWindow) {
|
global.mainWindow?.webContents.send('before-quit');
|
||||||
mainWindow.webContents.send('before-quit');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.on('before-quit', beforeQuit);
|
app.on('before-quit', beforeQuit);
|
||||||
@ -1106,10 +522,10 @@ app.on('second-instance', (ev, commandLine, workingDirectory) => {
|
|||||||
if (commandLine.includes('--hidden')) return;
|
if (commandLine.includes('--hidden')) return;
|
||||||
|
|
||||||
// Someone tried to run a second instance, we should focus our window.
|
// Someone tried to run a second instance, we should focus our window.
|
||||||
if (mainWindow) {
|
if (global.mainWindow) {
|
||||||
if (!mainWindow.isVisible()) mainWindow.show();
|
if (!global.mainWindow.isVisible()) global.mainWindow.show();
|
||||||
if (mainWindow.isMinimized()) mainWindow.restore();
|
if (global.mainWindow.isMinimized()) global.mainWindow.restore();
|
||||||
mainWindow.focus();
|
global.mainWindow.focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
202
src/ipc.ts
Normal file
202
src/ipc.ts
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 New Vector Ltd
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { app, autoUpdater, desktopCapturer, ipcMain, powerSaveBlocker } from "electron";
|
||||||
|
|
||||||
|
import IpcMainEvent = Electron.IpcMainEvent;
|
||||||
|
import { recordSSOSession } from "./protocol";
|
||||||
|
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') {
|
||||||
|
// 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
|
||||||
|
app.badgeCount = count;
|
||||||
|
}
|
||||||
|
if (count === 0) {
|
||||||
|
global.mainWindow?.flashFrame(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let focusHandlerAttached = false;
|
||||||
|
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.flashFrame(false);
|
||||||
|
focusHandlerAttached = false;
|
||||||
|
});
|
||||||
|
focusHandlerAttached = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let powerSaveBlockerId: number = null;
|
||||||
|
ipcMain.on('app_onAction', function(_ev: IpcMainEvent, 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: IpcMainEvent, payload) {
|
||||||
|
if (!global.mainWindow) return;
|
||||||
|
|
||||||
|
const args = payload.args || [];
|
||||||
|
let ret: any;
|
||||||
|
|
||||||
|
switch (payload.name) {
|
||||||
|
case 'getUpdateFeedUrl':
|
||||||
|
ret = autoUpdater.getFeedURL();
|
||||||
|
break;
|
||||||
|
case 'getSettingValue': {
|
||||||
|
const [settingName] = args;
|
||||||
|
const setting = Settings[settingName];
|
||||||
|
ret = await setting.read();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'setSettingValue': {
|
||||||
|
const [settingName, value] = args;
|
||||||
|
const setting = Settings[settingName];
|
||||||
|
await setting.write(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'setLanguage':
|
||||||
|
global.appLocalization.setAppLocale(args[0]);
|
||||||
|
break;
|
||||||
|
case 'getAppVersion':
|
||||||
|
ret = app.getVersion();
|
||||||
|
break;
|
||||||
|
case 'focusWindow':
|
||||||
|
if (global.mainWindow.isMinimized()) {
|
||||||
|
global.mainWindow.restore();
|
||||||
|
} else if (!global.mainWindow.isVisible()) {
|
||||||
|
global.mainWindow.show();
|
||||||
|
} else {
|
||||||
|
global.mainWindow.focus();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'getConfig':
|
||||||
|
ret = global.vectorConfig;
|
||||||
|
break;
|
||||||
|
case 'navigateBack':
|
||||||
|
if (global.mainWindow.webContents.canGoBack()) {
|
||||||
|
global.mainWindow.webContents.goBack();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'navigateForward':
|
||||||
|
if (global.mainWindow.webContents.canGoForward()) {
|
||||||
|
global.mainWindow.webContents.goForward();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'setSpellCheckLanguages':
|
||||||
|
if (args[0] && args[0].length > 0) {
|
||||||
|
global.mainWindow.webContents.session.setSpellCheckerEnabled(true);
|
||||||
|
global.store.set("spellCheckerEnabled", true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
global.mainWindow.webContents.session.setSpellCheckerLanguages(args[0]);
|
||||||
|
} catch (er) {
|
||||||
|
console.log("There were problems setting the spellcheck languages", er);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
global.mainWindow.webContents.session.setSpellCheckerEnabled(false);
|
||||||
|
global.store.set("spellCheckerEnabled", false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'getSpellCheckLanguages':
|
||||||
|
if (global.store.get("spellCheckerEnabled", true)) {
|
||||||
|
ret = global.mainWindow.webContents.session.getSpellCheckerLanguages();
|
||||||
|
} else {
|
||||||
|
ret = [];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'getAvailableSpellCheckLanguages':
|
||||||
|
ret = global.mainWindow.webContents.session.availableSpellCheckerLanguages;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'startSSOFlow':
|
||||||
|
recordSSOSession(args[0]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
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
|
||||||
|
// logins from the time of riot.im)
|
||||||
|
if (ret === null) {
|
||||||
|
ret = await keytar.getPassword("riot.im", `${args[0]}|${args[1]}`);
|
||||||
|
}
|
||||||
|
} 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 {
|
||||||
|
const pickleKey = await randomArray(32);
|
||||||
|
await keytar.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey);
|
||||||
|
ret = pickleKey;
|
||||||
|
} catch (e) {
|
||||||
|
ret = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
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
|
||||||
|
// logins from the time of riot.im)
|
||||||
|
await keytar.deletePassword("riot.im", `${args[0]}|${args[1]}`);
|
||||||
|
} catch (e) {}
|
||||||
|
break;
|
||||||
|
case 'getDesktopCapturerSources':
|
||||||
|
ret = (await desktopCapturer.getSources(args[0])).map((source) => ({
|
||||||
|
id: source.id,
|
||||||
|
name: source.name,
|
||||||
|
thumbnailURL: source.thumbnail.toDataURL(),
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
global.mainWindow.webContents.send('ipcReply', {
|
||||||
|
id: payload.id,
|
||||||
|
error: "Unknown IPC Call: " + payload.name,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global.mainWindow.webContents.send('ipcReply', {
|
||||||
|
id: payload.id,
|
||||||
|
reply: ret,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
31
src/keytar.ts
Normal file
31
src/keytar.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 New Vector Ltd
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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');
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { keytar };
|
325
src/seshat.ts
Normal file
325
src/seshat.ts
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 New Vector Ltd
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { app, ipcMain } from "electron";
|
||||||
|
import { promises as afs } from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
Seshat as SeshatType,
|
||||||
|
SeshatRecovery as SeshatRecoveryType,
|
||||||
|
ReindexError as ReindexErrorType,
|
||||||
|
} from "matrix-seshat"; // Hak dependency type
|
||||||
|
import IpcMainEvent = Electron.IpcMainEvent;
|
||||||
|
import { randomArray } from "./utils";
|
||||||
|
import { keytar } from "./keytar";
|
||||||
|
|
||||||
|
let seshatSupported = false;
|
||||||
|
let Seshat: typeof SeshatType;
|
||||||
|
let SeshatRecovery: typeof SeshatRecoveryType;
|
||||||
|
let ReindexError: typeof ReindexErrorType;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const seshatModule = require('matrix-seshat');
|
||||||
|
Seshat = seshatModule.Seshat;
|
||||||
|
SeshatRecovery = seshatModule.SeshatRecovery;
|
||||||
|
ReindexError = seshatModule.ReindexError;
|
||||||
|
seshatSupported = true;
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code === "MODULE_NOT_FOUND") {
|
||||||
|
console.log("Seshat isn't installed, event indexing is disabled.");
|
||||||
|
} else {
|
||||||
|
console.warn("Seshat unexpected error:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventStorePath = path.join(app.getPath('userData'), 'EventStore');
|
||||||
|
|
||||||
|
let eventIndex: SeshatType = null;
|
||||||
|
|
||||||
|
const seshatDefaultPassphrase = "DEFAULT_PASSPHRASE";
|
||||||
|
async function getOrCreatePassphrase(key: string): Promise<string> {
|
||||||
|
if (keytar) {
|
||||||
|
try {
|
||||||
|
const storedPassphrase = await keytar.getPassword("element.io", key);
|
||||||
|
if (storedPassphrase !== null) {
|
||||||
|
return storedPassphrase;
|
||||||
|
} else {
|
||||||
|
const newPassphrase = await randomArray(32);
|
||||||
|
await keytar.setPassword("element.io", key, newPassphrase);
|
||||||
|
return newPassphrase;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error getting the event index passphrase out of the secret store", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return seshatDefaultPassphrase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteContents = async (p: string): Promise<void> => {
|
||||||
|
for (const entry of await afs.readdir(p)) {
|
||||||
|
const curPath = path.join(p, entry);
|
||||||
|
await afs.unlink(curPath);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise<void> {
|
||||||
|
if (!global.mainWindow) return;
|
||||||
|
|
||||||
|
const sendError = (id, e) => {
|
||||||
|
const error = {
|
||||||
|
message: e.message,
|
||||||
|
};
|
||||||
|
|
||||||
|
global.mainWindow.webContents.send('seshatReply', {
|
||||||
|
id: id,
|
||||||
|
error: error,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const args = payload.args || [];
|
||||||
|
let ret: any;
|
||||||
|
|
||||||
|
switch (payload.name) {
|
||||||
|
case 'supportsEventIndexing':
|
||||||
|
ret = seshatSupported;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'initEventIndex':
|
||||||
|
if (eventIndex === null) {
|
||||||
|
const userId = args[0];
|
||||||
|
const deviceId = args[1];
|
||||||
|
const passphraseKey = `seshat|${userId}|${deviceId}`;
|
||||||
|
|
||||||
|
const passphrase = await getOrCreatePassphrase(passphraseKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await afs.mkdir(eventStorePath, { recursive: true });
|
||||||
|
eventIndex = 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.
|
||||||
|
const recoveryIndex = new SeshatRecovery(eventStorePath, {
|
||||||
|
passphrase,
|
||||||
|
});
|
||||||
|
|
||||||
|
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 {
|
||||||
|
await deleteContents(eventStorePath);
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await recoveryIndex.reindex();
|
||||||
|
}
|
||||||
|
|
||||||
|
eventIndex = new Seshat(eventStorePath, { passphrase });
|
||||||
|
} else {
|
||||||
|
sendError(payload.id, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'closeEventIndex':
|
||||||
|
if (eventIndex !== null) {
|
||||||
|
const index = eventIndex;
|
||||||
|
eventIndex = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await index.shutdown();
|
||||||
|
} catch (e) {
|
||||||
|
sendError(payload.id, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'deleteEventIndex': {
|
||||||
|
try {
|
||||||
|
await deleteContents(eventStorePath);
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'isEventIndexEmpty':
|
||||||
|
if (eventIndex === null) ret = true;
|
||||||
|
else ret = await eventIndex.isEmpty();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'isRoomIndexed':
|
||||||
|
if (eventIndex === null) ret = false;
|
||||||
|
else ret = await eventIndex.isRoomIndexed(args[0]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'addEventToIndex':
|
||||||
|
try {
|
||||||
|
eventIndex.addEvent(args[0], args[1]);
|
||||||
|
} catch (e) {
|
||||||
|
sendError(payload.id, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'deleteEvent':
|
||||||
|
try {
|
||||||
|
ret = await eventIndex.deleteEvent(args[0]);
|
||||||
|
} catch (e) {
|
||||||
|
sendError(payload.id, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
case 'getStats':
|
||||||
|
if (eventIndex === null) ret = 0;
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
ret = await eventIndex.getStats();
|
||||||
|
} catch (e) {
|
||||||
|
sendError(payload.id, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
case 'loadFileEvents':
|
||||||
|
if (eventIndex === null) ret = [];
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
ret = await eventIndex.loadFileEvents(args[0]);
|
||||||
|
} catch (e) {
|
||||||
|
sendError(payload.id, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'loadCheckpoints':
|
||||||
|
if (eventIndex === null) ret = [];
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
ret = await eventIndex.loadCheckpoints();
|
||||||
|
} catch (e) {
|
||||||
|
ret = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
default:
|
||||||
|
global.mainWindow.webContents.send('seshatReply', {
|
||||||
|
id: payload.id,
|
||||||
|
error: "Unknown IPC Call: " + payload.name,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global.mainWindow.webContents.send('seshatReply', {
|
||||||
|
id: payload.id,
|
||||||
|
reply: ret,
|
||||||
|
});
|
||||||
|
});
|
77
src/settings.ts
Normal file
77
src/settings.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 New Vector Ltd
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as tray from "./tray";
|
||||||
|
|
||||||
|
interface Setting {
|
||||||
|
read(): Promise<any>;
|
||||||
|
write(value: any): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Settings: Record<string, Setting> = {
|
||||||
|
"Electron.autoLaunch": {
|
||||||
|
async read(): Promise<any> {
|
||||||
|
return global.launcher.isEnabled();
|
||||||
|
},
|
||||||
|
async write(value: any): Promise<void> {
|
||||||
|
if (value) {
|
||||||
|
return global.launcher.enable();
|
||||||
|
} else {
|
||||||
|
return global.launcher.disable();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Electron.warnBeforeExit": {
|
||||||
|
async read(): Promise<any> {
|
||||||
|
return global.store.get("warnBeforeExit", true);
|
||||||
|
},
|
||||||
|
async write(value: any): Promise<void> {
|
||||||
|
global.store.set("warnBeforeExit", value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"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.mainWindow.autoHideMenuBar = !value;
|
||||||
|
global.mainWindow.setMenuBarVisibility(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Electron.showTrayIcon": { // not supported on macOS
|
||||||
|
async read(): Promise<any> {
|
||||||
|
return tray.hasTray();
|
||||||
|
},
|
||||||
|
async write(value: any): Promise<void> {
|
||||||
|
if (value) {
|
||||||
|
// Create trayIcon icon
|
||||||
|
tray.create(global.trayConfig);
|
||||||
|
} else {
|
||||||
|
tray.destroy();
|
||||||
|
}
|
||||||
|
global.store.set('minimizeToTray', value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Electron.enableHardwareAcceleration": {
|
||||||
|
async read(): Promise<any> {
|
||||||
|
return !global.store.get('disableHardwareAcceleration', false);
|
||||||
|
},
|
||||||
|
async write(value: any): Promise<void> {
|
||||||
|
global.store.set('disableHardwareAcceleration', !value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
29
src/utils.ts
Normal file
29
src/utils.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 New Vector Ltd
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
export async function randomArray(size: number): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
crypto.randomBytes(size, (err, buf) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(buf.toString("base64").replace(/=+$/g, ''));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -10,6 +10,7 @@
|
|||||||
"outDir": "./lib",
|
"outDir": "./lib",
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
"typeRoots": ["src/@types"],
|
||||||
"lib": [
|
"lib": [
|
||||||
"es2019",
|
"es2019",
|
||||||
"dom"
|
"dom"
|
||||||
|
93
yarn.lock
93
yarn.lock
@ -839,25 +839,10 @@
|
|||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
form-data "^3.0.0"
|
form-data "^3.0.0"
|
||||||
|
|
||||||
"@types/node@*":
|
"@types/node@*", "@types/node@16.11.38", "@types/node@16.9.1", "@types/node@^16.11.26", "@types/node@^17.0.12":
|
||||||
version "18.0.0"
|
version "16.11.38"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.38.tgz#be0edd097b23eace6c471c525a74b3f98803017f"
|
||||||
integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==
|
integrity sha512-hjO/0K140An3GWDw2HJfq7gko3wWeznbjXgg+rzPdVzhe198hp4x2i1dgveAOEiFKd8sOilAxzoSJiVv5P/CUg==
|
||||||
|
|
||||||
"@types/node@16.9.1":
|
|
||||||
version "16.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.1.tgz#0611b37db4246c937feef529ddcc018cf8e35708"
|
|
||||||
integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==
|
|
||||||
|
|
||||||
"@types/node@^16.11.26":
|
|
||||||
version "16.11.41"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.41.tgz#88eb485b1bfdb4c224d878b7832239536aa2f813"
|
|
||||||
integrity sha512-mqoYK2TnVjdkGk8qXAVGc/x9nSaTpSrFaGFm43BUH3IdoBV0nta6hYaGmdOvIMlbHJbUEVen3gvwpwovAZKNdQ==
|
|
||||||
|
|
||||||
"@types/node@^17.0.12":
|
|
||||||
version "17.0.45"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190"
|
|
||||||
integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==
|
|
||||||
|
|
||||||
"@types/npm-package-arg@*":
|
"@types/npm-package-arg@*":
|
||||||
version "6.1.1"
|
version "6.1.1"
|
||||||
@ -1059,12 +1044,19 @@ aggregate-error@^3.0.0:
|
|||||||
clean-stack "^2.0.0"
|
clean-stack "^2.0.0"
|
||||||
indent-string "^4.0.0"
|
indent-string "^4.0.0"
|
||||||
|
|
||||||
|
ajv-formats@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
|
||||||
|
integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==
|
||||||
|
dependencies:
|
||||||
|
ajv "^8.0.0"
|
||||||
|
|
||||||
ajv-keywords@^3.4.1:
|
ajv-keywords@^3.4.1:
|
||||||
version "3.5.2"
|
version "3.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
|
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
|
||||||
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
|
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
|
||||||
|
|
||||||
ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4:
|
ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.3, ajv@^6.12.4:
|
||||||
version "6.12.6"
|
version "6.12.6"
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||||
@ -1074,7 +1066,7 @@ ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4:
|
|||||||
json-schema-traverse "^0.4.1"
|
json-schema-traverse "^0.4.1"
|
||||||
uri-js "^4.2.2"
|
uri-js "^4.2.2"
|
||||||
|
|
||||||
ajv@^8.0.1:
|
ajv@^8.0.0, ajv@^8.0.1, ajv@^8.6.3:
|
||||||
version "8.11.0"
|
version "8.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
|
||||||
integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
|
integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
|
||||||
@ -1371,7 +1363,7 @@ at-least-node@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
||||||
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
||||||
|
|
||||||
atomically@^1.3.1:
|
atomically@^1.7.0:
|
||||||
version "1.7.0"
|
version "1.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/atomically/-/atomically-1.7.0.tgz#c07a0458432ea6dbc9a3506fffa424b48bccaafe"
|
resolved "https://registry.yarnpkg.com/atomically/-/atomically-1.7.0.tgz#c07a0458432ea6dbc9a3506fffa424b48bccaafe"
|
||||||
integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==
|
integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==
|
||||||
@ -1851,21 +1843,21 @@ concat-stream@^1.6.2:
|
|||||||
readable-stream "^2.2.2"
|
readable-stream "^2.2.2"
|
||||||
typedarray "^0.0.6"
|
typedarray "^0.0.6"
|
||||||
|
|
||||||
conf@^7.1.2:
|
conf@^10.1.2:
|
||||||
version "7.1.2"
|
version "10.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/conf/-/conf-7.1.2.tgz#d9678a9d8f04de8bf5cd475105da8fdae49c2ec4"
|
resolved "https://registry.yarnpkg.com/conf/-/conf-10.1.2.tgz#50132158f388756fa9dea3048f6b47935315c14e"
|
||||||
integrity sha512-r8/HEoWPFn4CztjhMJaWNAe5n+gPUCSaJ0oufbqDLFKsA1V8JjAG7G+p0pgoDFAws9Bpk2VtVLLXqOBA7WxLeg==
|
integrity sha512-o9Fv1Mv+6A0JpoayQ8JleNp3hhkbOJP/Re/Q+QqxMPHPkABVsRjQGWZn9A5GcqLiTNC6d89p2PB5ZhHVDSMwyg==
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv "^6.12.2"
|
ajv "^8.6.3"
|
||||||
atomically "^1.3.1"
|
ajv-formats "^2.1.1"
|
||||||
|
atomically "^1.7.0"
|
||||||
debounce-fn "^4.0.0"
|
debounce-fn "^4.0.0"
|
||||||
dot-prop "^5.2.0"
|
dot-prop "^6.0.1"
|
||||||
env-paths "^2.2.0"
|
env-paths "^2.2.1"
|
||||||
json-schema-typed "^7.0.3"
|
json-schema-typed "^7.0.3"
|
||||||
make-dir "^3.1.0"
|
onetime "^5.1.2"
|
||||||
onetime "^5.1.0"
|
|
||||||
pkg-up "^3.1.0"
|
pkg-up "^3.1.0"
|
||||||
semver "^7.3.2"
|
semver "^7.3.5"
|
||||||
|
|
||||||
config-chain@^1.1.11:
|
config-chain@^1.1.11:
|
||||||
version "1.1.13"
|
version "1.1.13"
|
||||||
@ -2141,6 +2133,13 @@ dot-prop@^5.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-obj "^2.0.0"
|
is-obj "^2.0.0"
|
||||||
|
|
||||||
|
dot-prop@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083"
|
||||||
|
integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==
|
||||||
|
dependencies:
|
||||||
|
is-obj "^2.0.0"
|
||||||
|
|
||||||
dotenv-expand@^5.1.0:
|
dotenv-expand@^5.1.0:
|
||||||
version "5.1.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
|
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
|
||||||
@ -2257,13 +2256,13 @@ electron-publish@22.14.13:
|
|||||||
lazy-val "^1.0.5"
|
lazy-val "^1.0.5"
|
||||||
mime "^2.5.2"
|
mime "^2.5.2"
|
||||||
|
|
||||||
electron-store@^6.0.1:
|
electron-store@^8.0.2:
|
||||||
version "6.0.1"
|
version "8.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/electron-store/-/electron-store-6.0.1.tgz#2178b9dc37aeb749d99cf9d1d1bc090890b922dc"
|
resolved "https://registry.yarnpkg.com/electron-store/-/electron-store-8.0.2.tgz#95c8cf81c1e1cf48b24f3ceeea24b921c1ff62d7"
|
||||||
integrity sha512-8rdM0XEmDGsLuZM2oRABzsLX+XmD5x3rwxPMEPv0MrN9/BWanyy3ilb2v+tCrKtIZVF3MxUiZ9Bfqe8e0popKQ==
|
integrity sha512-9GwUMv51w8ydbkaG7X0HrPlElXLApg63zYy1/VZ/a08ndl0gfm4iCoD3f0E1JvP3V16a+7KxqriCI0c122stiA==
|
||||||
dependencies:
|
dependencies:
|
||||||
conf "^7.1.2"
|
conf "^10.1.2"
|
||||||
type-fest "^0.16.0"
|
type-fest "^2.12.2"
|
||||||
|
|
||||||
electron-window-state@^5.0.3:
|
electron-window-state@^5.0.3:
|
||||||
version "5.0.3"
|
version "5.0.3"
|
||||||
@ -2313,7 +2312,7 @@ enquirer@^2.3.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ansi-colors "^4.1.1"
|
ansi-colors "^4.1.1"
|
||||||
|
|
||||||
env-paths@^2.2.0:
|
env-paths@^2.2.0, env-paths@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
|
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
|
||||||
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
|
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
|
||||||
@ -3785,7 +3784,7 @@ lru-queue@^0.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
es5-ext "~0.10.2"
|
es5-ext "~0.10.2"
|
||||||
|
|
||||||
make-dir@^3.0.0, make-dir@^3.1.0:
|
make-dir@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
||||||
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
|
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
|
||||||
@ -4272,7 +4271,7 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
wrappy "1"
|
wrappy "1"
|
||||||
|
|
||||||
onetime@^5.1.0:
|
onetime@^5.1.2:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
|
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
|
||||||
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
|
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
|
||||||
@ -5314,11 +5313,6 @@ type-fest@^0.13.1:
|
|||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
|
||||||
integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
|
integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
|
||||||
|
|
||||||
type-fest@^0.16.0:
|
|
||||||
version "0.16.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860"
|
|
||||||
integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==
|
|
||||||
|
|
||||||
type-fest@^0.20.2:
|
type-fest@^0.20.2:
|
||||||
version "0.20.2"
|
version "0.20.2"
|
||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
|
||||||
@ -5329,6 +5323,11 @@ type-fest@^0.8.1:
|
|||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||||
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||||
|
|
||||||
|
type-fest@^2.12.2:
|
||||||
|
version "2.13.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.13.0.tgz#d1ecee38af29eb2e863b22299a3d68ef30d2abfb"
|
||||||
|
integrity sha512-lPfAm42MxE4/456+QyIaaVBAwgpJb6xZ8PRu09utnhPdWwcyj9vgy6Sq0Z5yNbJ21EdxB5dRU/Qg8bsyAMtlcw==
|
||||||
|
|
||||||
type@^1.0.1:
|
type@^1.0.1:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
|
resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
|
||||||
|
Loading…
Reference in New Issue
Block a user