From cf88e520a03e3b6d8450171533c915c2b00da6f5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 4 Jun 2025 14:01:14 +0100 Subject: [PATCH 1/6] Fix restart loop in safeStorage if we started using a backend but it's now unusable, we need to prompt the user that we can't migrate: if the override flag is already set then we'll just restart in a loop. --- src/store.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/store.ts b/src/store.ts index 357e479636..409b33a2f7 100644 --- a/src/store.ts +++ b/src/store.ts @@ -305,9 +305,14 @@ class Store extends ElectronStore { // Store the backend used for the safeStorage data so we can detect if it changes, and we know how the data is encoded this.recordSafeStorageBackend(backend); } else if (existingSafeStorageBackend !== backend) { + // We already appear to have started using a backend other than the one that we picked, so + // set the override flag and relaunch with the backend we were previously using, unless we + // already have the override flag, in which case we must assume the previous backend is no + // longer usable, in which case we should fall into the next block and warn the user we can't + // migrate. console.warn(`safeStorage backend changed from ${existingSafeStorageBackend} to ${backend}`); - if (existingSafeStorageBackend in safeStorageBackendMap) { + if (existingSafeStorageBackend in safeStorageBackendMap && !this.get("safeStorageBackendOverride")) { this.set("safeStorageBackendOverride", true); return relaunchApp(); } else { From 6901bff548194ca172e302e661a07836ad6fbd7b Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 4 Jun 2025 16:01:03 +0100 Subject: [PATCH 2/6] Fix clear storage not working It failed because it went looking for the focused / first window to clear the storage on, but we called it before we had a window. Just rewrite it without electron-clear-storage which doesn't really seem necessary as a dependency, and also relaunched the app when clearing stprage (you-had-one-job.gif). Pass the session in explicitly so it's clear it needs it. --- package.json | 1 - src/electron-main.ts | 17 +++++++++-------- src/ipc.ts | 2 +- src/store.ts | 39 +++++++++++++++++++++++++++------------ yarn.lock | 9 +-------- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index adede12862..4f24ffbf8d 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ }, "dependencies": { "@sentry/electron": "^6.0.0", - "@standardnotes/electron-clear-data": "^1.0.5", "auto-launch": "^5.0.5", "counterpart": "^0.18.6", "electron-store": "^10.0.0", diff --git a/src/electron-main.ts b/src/electron-main.ts index a00166c773..8d589bfee3 100644 --- a/src/electron-main.ts +++ b/src/electron-main.ts @@ -453,14 +453,6 @@ app.on("ready", async () => { store, }); - try { - console.debug("Ensuring storage is ready"); - await store.safeStorageReady(); - } catch (e) { - console.error(e); - app.exit(1); - } - // Load the previous window state with fallback to defaults const mainWindowState = windowStateKeeper({ defaultWidth: 1024, @@ -492,6 +484,15 @@ app.on("ready", async () => { webgl: true, }, }); + + try { + console.debug("Ensuring storage is ready"); + if (!await store.prepareSafeStorage(global.mainWindow.webContents.session)) return; + } catch (e) { + console.error(e); + app.exit(1); + } + void global.mainWindow.loadURL("vector://vector/webapp/"); if (process.platform === "darwin") { diff --git a/src/ipc.ts b/src/ipc.ts index eee1c06b8c..67c2eafc66 100644 --- a/src/ipc.ts +++ b/src/ipc.ts @@ -174,7 +174,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) { break; case "clearStorage": - await clearDataAndRelaunch(); + await clearDataAndRelaunch(global.mainWindow.webContents.session); return; // the app is about to stop, we don't need to reply to the IPC case "breadcrumbs": { diff --git a/src/store.ts b/src/store.ts index 409b33a2f7..bf7069932f 100644 --- a/src/store.ts +++ b/src/store.ts @@ -16,8 +16,7 @@ limitations under the License. import ElectronStore from "electron-store"; import keytar from "keytar-forked"; -import { app, safeStorage, dialog, type SafeStorage } from "electron"; -import { clearAllUserData, relaunchApp } from "@standardnotes/electron-clear-data"; +import { app, safeStorage, dialog, type SafeStorage, type Session } from "electron"; import { _t } from "./language-helper.js"; @@ -61,12 +60,18 @@ const safeStorageBackendMap: Omit< kwallet5: "kwallet5", }; +function relaunchApp(): void { + app.relaunch(); + app.exit(); +} + /** * Clear all data and relaunch the app. */ -export async function clearDataAndRelaunch(): Promise { +export async function clearDataAndRelaunch(electronSession: Session): Promise { Store.instance?.clear(); - clearAllUserData(); + electronSession.flushStorageData(); + await electronSession.clearStorageData(); relaunchApp(); } @@ -230,7 +235,7 @@ class Store extends ElectronStore { private safeStorageReadyPromise?: Promise; public async safeStorageReady(): Promise { if (!this.safeStorageReadyPromise) { - this.safeStorageReadyPromise = this.prepareSafeStorage(); + throw new Error("prepareSafeStorage must be called before using storage methods"); } await this.safeStorageReadyPromise; } @@ -270,8 +275,13 @@ class Store extends ElectronStore { * Prepare the safeStorage backend for use. * We don't eagerly import from keytar as that would bring in data for all Element profiles and not just the current one, * so we import lazily in getSecret. + * + * This will relaunch the app in some cases, in which case it will return false and the caller should abort startup. + * + * @param electronSession - The Electron session to use for storage (will be used to clear storage if necessary). + * @returns true if safeStorage was initialised successfully or false if the app will be relaunched */ - private async prepareSafeStorage(): Promise { + public async prepareSafeStorage(electronSession: Session): Promise { await app.whenReady(); // The backend the existing data is written with if any @@ -282,11 +292,13 @@ class Store extends ElectronStore { // Handle migrations if (existingSafeStorageBackend) { if (existingSafeStorageBackend === "basic_text" && backend !== "plaintext" && backend !== "basic_text") { - return this.prepareMigrateBasicTextToPlaintext(); + this.prepareMigrateBasicTextToPlaintext(); + return false; } if (this.get("safeStorageBackendMigrate") && backend === "basic_text") { - return this.migrateBasicTextToPlaintext(); + this.migrateBasicTextToPlaintext(); + return false; } if (existingSafeStorageBackend === "plaintext" && backend !== "plaintext") { @@ -314,9 +326,10 @@ class Store extends ElectronStore { if (existingSafeStorageBackend in safeStorageBackendMap && !this.get("safeStorageBackendOverride")) { this.set("safeStorageBackendOverride", true); - return relaunchApp(); + relaunchApp(); + return false; } else { - await this.consultUserBackendChangedUnableToMigrate(); + await this.consultUserBackendChangedUnableToMigrate(electronSession); } } @@ -326,9 +339,11 @@ class Store extends ElectronStore { } else { this.secrets = new StorageWriter(this); } + + return true; } - private async consultUserBackendChangedUnableToMigrate(): Promise { + private async consultUserBackendChangedUnableToMigrate(electronSession: Session): Promise { const { response } = await dialog.showMessageBox({ title: _t("store|error|backend_changed_title"), message: _t("store|error|backend_changed"), @@ -341,7 +356,7 @@ class Store extends ElectronStore { if (response === 0) { throw new Error("safeStorage backend changed and cannot migrate"); } - return clearDataAndRelaunch(); + return clearDataAndRelaunch(electronSession); } private async consultUserConsentDegradedMode(backend: "plaintext" | "basic_text"): Promise { diff --git a/yarn.lock b/yarn.lock index 1418ea29e2..9d7a6fd1d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1048,7 +1048,6 @@ "@electron/node-gyp@https://github.com/electron/node-gyp#06b29aafb7708acef8b3669835c8a7857ebc92d2": version "10.2.0-electron.1" - uid "06b29aafb7708acef8b3669835c8a7857ebc92d2" resolved "https://github.com/electron/node-gyp#06b29aafb7708acef8b3669835c8a7857ebc92d2" dependencies: env-paths "^2.2.0" @@ -1334,7 +1333,6 @@ "@fastify/otel@github:getsentry/fastify-otel#otel-v1": version "0.8.0" - uid ae3088d65e286bdc94ac5d722573537d6a6671bb resolved "https://codeload.github.com/getsentry/fastify-otel/tar.gz/ae3088d65e286bdc94ac5d722573537d6a6671bb" dependencies: "@opentelemetry/core" "^1.30.1" @@ -2034,7 +2032,7 @@ resolved "https://registry.yarnpkg.com/@sentry/node/-/node-9.18.0.tgz#682ad11030548eb0e0674ff091afc65319da6d8d" integrity sha512-n0H13YVfynZJnKQLHoTlyBK2P960X8+B08za9VaRnJ4zikDx23Xk2Owtj006ZUItUKtKLFi70NyQGGDp7gVAyw== dependencies: - "@fastify/otel" "github:getsentry/fastify-otel#otel-v1" + "@fastify/otel" getsentry/fastify-otel#otel-v1 "@opentelemetry/api" "^1.9.0" "@opentelemetry/context-async-hooks" "^1.30.1" "@opentelemetry/core" "^1.30.1" @@ -2127,11 +2125,6 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== -"@standardnotes/electron-clear-data@^1.0.5": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@standardnotes/electron-clear-data/-/electron-clear-data-1.1.1.tgz#45eab118ed5d1ee9369b540d7e62a9ca96778335" - integrity sha512-R0YivtSwSQpNt5nPOi7YRTGlk6kpcz6/2/sAQZf6ZCU8vIGm1cBMo++6kkGQcDEumkwbmagxmLWinL9d1W5g3Q== - "@stylistic/eslint-plugin@^4.0.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-4.4.0.tgz#e1a3c9fd7109411d32dc0bcb575d2b4066fbbc63" From 8f24f45090120038c3b942a99ff815a224bdb201 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 4 Jun 2025 16:12:43 +0100 Subject: [PATCH 3/6] Need to return false here too --- src/store.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/store.ts b/src/store.ts index bf7069932f..cb6ebcc6c3 100644 --- a/src/store.ts +++ b/src/store.ts @@ -329,7 +329,9 @@ class Store extends ElectronStore { relaunchApp(); return false; } else { + // This will either relaunch the app or throw an execption await this.consultUserBackendChangedUnableToMigrate(electronSession); + return false; } } From 2e039f4bab4b65a2d47d4f2eed1847792377adf1 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 4 Jun 2025 16:15:08 +0100 Subject: [PATCH 4/6] Add log message while I'm here --- src/store.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/store.ts b/src/store.ts index cb6ebcc6c3..c20ec3c12b 100644 --- a/src/store.ts +++ b/src/store.ts @@ -61,6 +61,7 @@ const safeStorageBackendMap: Omit< }; function relaunchApp(): void { + console.info("Relaunching app..."); app.relaunch(); app.exit(); } From 86f6136257ead2df0d2b67d480e28f57f7bc33af Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 4 Jun 2025 16:23:59 +0100 Subject: [PATCH 5/6] Prettier --- src/electron-main.ts | 2 +- src/store.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/electron-main.ts b/src/electron-main.ts index 8d589bfee3..aaa0e7e79b 100644 --- a/src/electron-main.ts +++ b/src/electron-main.ts @@ -487,7 +487,7 @@ app.on("ready", async () => { try { console.debug("Ensuring storage is ready"); - if (!await store.prepareSafeStorage(global.mainWindow.webContents.session)) return; + if (!(await store.prepareSafeStorage(global.mainWindow.webContents.session))) return; } catch (e) { console.error(e); app.exit(1); diff --git a/src/store.ts b/src/store.ts index c20ec3c12b..9517d84e67 100644 --- a/src/store.ts +++ b/src/store.ts @@ -276,9 +276,9 @@ class Store extends ElectronStore { * Prepare the safeStorage backend for use. * We don't eagerly import from keytar as that would bring in data for all Element profiles and not just the current one, * so we import lazily in getSecret. - * + * * This will relaunch the app in some cases, in which case it will return false and the caller should abort startup. - * + * * @param electronSession - The Electron session to use for storage (will be used to clear storage if necessary). * @returns true if safeStorage was initialised successfully or false if the app will be relaunched */ From 39f460b6363a062616581b5f655dcefbe4f8077e Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 4 Jun 2025 18:26:56 +0100 Subject: [PATCH 6/6] Actually assign the promise --- src/store.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/store.ts b/src/store.ts index 9517d84e67..c9b18fb843 100644 --- a/src/store.ts +++ b/src/store.ts @@ -233,7 +233,7 @@ class Store extends ElectronStore { }); } - private safeStorageReadyPromise?: Promise; + private safeStorageReadyPromise?: Promise; public async safeStorageReady(): Promise { if (!this.safeStorageReadyPromise) { throw new Error("prepareSafeStorage must be called before using storage methods"); @@ -283,6 +283,11 @@ class Store extends ElectronStore { * @returns true if safeStorage was initialised successfully or false if the app will be relaunched */ public async prepareSafeStorage(electronSession: Session): Promise { + this.safeStorageReadyPromise = this.reallyPrepareSafeStorage(electronSession); + return this.safeStorageReadyPromise; + } + + private async reallyPrepareSafeStorage(electronSession: Session): Promise { await app.whenReady(); // The backend the existing data is written with if any