mirror of
https://github.com/element-hq/element-desktop
synced 2025-04-21 09:03:56 +02:00
Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
bec721cf57
commit
4889634f0b
@ -364,6 +364,8 @@ app.enableSandbox();
|
||||
// We disable media controls here. We do this because calls use audio and video elements and they sometimes capture the media keys. See https://github.com/vector-im/element-web/issues/15704
|
||||
app.commandLine.appendSwitch("disable-features", "HardwareMediaKeyHandling,MediaSessionService");
|
||||
|
||||
store.prepare(); // must be called before any async actions
|
||||
|
||||
// Disable hardware acceleration if the setting has been set.
|
||||
if (store.get("disableHardwareAcceleration") === true) {
|
||||
console.log("Disabling hardware acceleration.");
|
||||
|
@ -19,6 +19,8 @@
|
||||
"zoom_out": "Zoom Out"
|
||||
},
|
||||
"common": {
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"about": "About",
|
||||
"brand_help": "%(brand)s Help",
|
||||
"help": "Help",
|
||||
@ -59,5 +61,12 @@
|
||||
"bring_all_to_front": "Bring All to Front",
|
||||
"label": "Window",
|
||||
"zoom": "Zoom"
|
||||
},
|
||||
"store": {
|
||||
"error": {
|
||||
"title": "Failed to load configuration",
|
||||
"unsupported_backend_override": "TODO",
|
||||
"unknown_backend_override": "TODO"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
164
src/store.ts
164
src/store.ts
@ -16,7 +16,9 @@ limitations under the License.
|
||||
|
||||
import ElectronStore from "electron-store";
|
||||
import keytar from "keytar-forked";
|
||||
import { app, safeStorage } from "electron";
|
||||
import { app, safeStorage, dialog, type SafeStorage } from "electron";
|
||||
|
||||
import { _t } from "./language-helper.js";
|
||||
|
||||
/**
|
||||
* Legacy keytar service names for storing secrets.
|
||||
@ -24,6 +26,20 @@ import { app, safeStorage } from "electron";
|
||||
const KEYTAR_SERVICE = "element.io";
|
||||
const LEGACY_KEYTAR_SERVICE = "riot.im";
|
||||
|
||||
type SafeStorageBackend = ReturnType<SafeStorage["getSelectedStorageBackend"]>;
|
||||
|
||||
/**
|
||||
* Map of safeStorage backends to their command line arguments.
|
||||
* kwallet6 cannot be specified via command line
|
||||
* https://www.electronjs.org/docs/latest/api/safe-storage#safestoragegetselectedstoragebackend-linux
|
||||
*/
|
||||
const safeStorageBackendMap: Omit<Record<SafeStorageBackend, string>, "unknown" | "kwallet6"> = {
|
||||
basic_text: "basic",
|
||||
gnome_libsecret: "gnome-libsecret",
|
||||
kwallet: "kwallet",
|
||||
kwallet5: "kwallet5",
|
||||
};
|
||||
|
||||
/**
|
||||
* JSON-backed store for settings which need to be accessible by the main process.
|
||||
* Secrets are stored within the `safeStorage` object, encrypted with safeStorage.
|
||||
@ -37,6 +53,10 @@ class Store extends ElectronStore<{
|
||||
locale?: string | string[];
|
||||
disableHardwareAcceleration: boolean;
|
||||
safeStorage?: Record<string, string>;
|
||||
// Only known for Linux - the safeStorage backend used for the safeStorage data as written
|
||||
safeStorageBackend?: SafeStorageBackend;
|
||||
// Only valid for Linux - whether to override the safeStorage backend via commandLine
|
||||
safeStorageBackendOverride?: boolean;
|
||||
}> {
|
||||
public constructor() {
|
||||
super({
|
||||
@ -69,37 +89,147 @@ class Store extends ElectronStore<{
|
||||
safeStorage: {
|
||||
type: "object",
|
||||
},
|
||||
safeStorageBackend: {
|
||||
type: "string",
|
||||
},
|
||||
safeStorageBackendOverride: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private whenSafeStorageReadyPromise?: Promise<unknown>;
|
||||
public async safeStorageReady(): Promise<void> {
|
||||
if (!this.whenSafeStorageReadyPromise) {
|
||||
this.whenSafeStorageReadyPromise = Promise.allSettled([app.whenReady().then(() => this.migrateSecrets())]);
|
||||
/**
|
||||
* Prepare the store, does not prepare safeStorage, which needs to be done after the app is ready.
|
||||
* Must be executed in the first tick of the event loop so that it can call Electron APIs before ready state.
|
||||
*/
|
||||
public prepare(): void {
|
||||
if (process.platform === "linux") {
|
||||
if (this.get("safeStorageBackendOverride")) {
|
||||
const backend = this.get("safeStorageBackend")!;
|
||||
if (backend in safeStorageBackendMap) {
|
||||
app.commandLine.appendSwitch(
|
||||
"password-store",
|
||||
safeStorageBackendMap[backend as keyof typeof safeStorageBackendMap],
|
||||
);
|
||||
} else {
|
||||
// This case should never happen, but could due to a downgrade or a modified store.
|
||||
dialog.showErrorBox(_t("store|error|title"), _t("store|error|unsupported_backend_override"));
|
||||
throw new Error("safeStorage backend override is not supported");
|
||||
}
|
||||
await this.whenSafeStorageReadyPromise;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private safeStorageReadyPromise?: Promise<unknown>;
|
||||
public async safeStorageReady(): Promise<void> {
|
||||
if (!this.safeStorageReadyPromise) {
|
||||
this.safeStorageReadyPromise = this.prepareSafeStorage();
|
||||
}
|
||||
await this.safeStorageReadyPromise;
|
||||
}
|
||||
|
||||
private getSecretStorageKey = (key: string) => `safeStorage.${key}` as const;
|
||||
|
||||
private async prepareSafeStorage(): Promise<void> {
|
||||
await app.whenReady();
|
||||
|
||||
if (process.platform === "linux") {
|
||||
// Linux safeStorage support is hellish, the support varies on the Desktop Environment used rather than the store itself.
|
||||
// https://github.com/electron/electron/issues/39789 https://github.com/microsoft/vscode/issues/185212
|
||||
let safeStorageBackend = this.get("safeStorageBackend");
|
||||
const selectedSafeStorageBackend = safeStorage.getSelectedStorageBackend();
|
||||
|
||||
if (selectedSafeStorageBackend === "unknown") {
|
||||
// This should never happen but good to be safe
|
||||
dialog.showErrorBox(_t("store|error|title"), _t("store|error|unknown_backend_override"));
|
||||
throw new Error("safeStorage backend unknown");
|
||||
}
|
||||
|
||||
if (!safeStorageBackend) {
|
||||
if (selectedSafeStorageBackend === "basic_text") {
|
||||
// Ask the user if they want to use plain text encryption
|
||||
// TODO should we only do this if they have existing data
|
||||
const { response } = await dialog.showMessageBox({
|
||||
// TODO
|
||||
title: "Error 1",
|
||||
message: "Message",
|
||||
// detail: _t(""),
|
||||
type: "question",
|
||||
buttons: [_t("common|no"), _t("common|yes")],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
});
|
||||
if (response === 0) {
|
||||
throw new Error("safeStorage backend basic_text and user rejected it");
|
||||
}
|
||||
}
|
||||
|
||||
// Store the backend used for the safeStorage data so we can detect if it changes
|
||||
this.set("safeStorageBackend", selectedSafeStorageBackend);
|
||||
safeStorageBackend = selectedSafeStorageBackend;
|
||||
} else if (safeStorageBackend !== selectedSafeStorageBackend) {
|
||||
console.warn(`safeStorage backend changed from ${safeStorageBackend} to ${selectedSafeStorageBackend}`);
|
||||
|
||||
if (safeStorageBackend === "basic_text") {
|
||||
console.info(`Migrating safeStorage from basic_text to ${selectedSafeStorageBackend}`);
|
||||
const data = this.get("safeStorage");
|
||||
if (data) {
|
||||
for (const key in data) {
|
||||
const plaintext = data[key];
|
||||
await this.setSecret(key, plaintext);
|
||||
}
|
||||
}
|
||||
} else if (safeStorageBackend in safeStorageBackendMap) {
|
||||
// Warn the user that the backend has changed and ask if they wish to use the old one
|
||||
const { response } = await dialog.showMessageBox({
|
||||
// TODO
|
||||
title: "Error 2",
|
||||
message: "Message",
|
||||
// detail: _t(""),
|
||||
type: "question",
|
||||
buttons: [_t("common|no"), _t("common|yes")],
|
||||
defaultId: 0,
|
||||
cancelId: 0,
|
||||
});
|
||||
if (response === 0) {
|
||||
throw new Error("safeStorage backend changed and user rejected mitigation");
|
||||
}
|
||||
this.set("safeStorageBackendOverride", true);
|
||||
app.relaunch();
|
||||
} else {
|
||||
// Warn the user that the backend has changed and tell them that we cannot migrate
|
||||
// dialog.showErrorBox(_t(""), _t("")); TODO
|
||||
throw new Error("safeStorage backend changed and cannot migrate");
|
||||
}
|
||||
}
|
||||
|
||||
if (safeStorageBackend === "basic_text" && selectedSafeStorageBackend === safeStorageBackend) {
|
||||
// TODO verify if this even works, the docstring makes it sound ephemeral!
|
||||
safeStorage.setUsePlainTextEncryption(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!safeStorage.isEncryptionAvailable()) {
|
||||
console.error("Store migration: safeStorage is not available");
|
||||
throw new Error(`safeStorage is not available`);
|
||||
// TODO fatal error
|
||||
}
|
||||
|
||||
await this.migrateSecrets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates keytar data to safeStorage,
|
||||
* deletes data from legacy keytar but keeps it in the new keytar for downgrade compatibility.
|
||||
* @throws if safeStorage is not available.
|
||||
* @throws if safeStorage is not available. TODO
|
||||
*/
|
||||
private async migrateSecrets(): Promise<void> {
|
||||
if (this.has("safeStorage")) return;
|
||||
if (this.has("safeStorage")) return; // already migrated
|
||||
console.info("Store migration: started");
|
||||
if (
|
||||
!safeStorage.isEncryptionAvailable() &&
|
||||
!(process.platform === "linux" && safeStorage.getSelectedStorageBackend() === "basic_text")
|
||||
) {
|
||||
console.error(
|
||||
"Store migration: safeStorage is not available with backend",
|
||||
safeStorage.getSelectedStorageBackend(),
|
||||
);
|
||||
throw new Error("safeStorage is not available");
|
||||
|
||||
if (process.platform === "linux" && safeStorage.getSelectedStorageBackend() === "basic_text") {
|
||||
console.warn("Store migration: safeStorage is using basic text encryption");
|
||||
}
|
||||
|
||||
try {
|
||||
|
Loading…
x
Reference in New Issue
Block a user