diff --git a/apps/desktop/electron-builder.ts b/apps/desktop/electron-builder.ts index ce9f3b800b..967a129c1c 100644 --- a/apps/desktop/electron-builder.ts +++ b/apps/desktop/electron-builder.ts @@ -1,7 +1,7 @@ import * as os from "node:os"; import * as fs from "node:fs"; import * as path from "node:path"; -import { type Configuration as BaseConfiguration, type Protocol } from "electron-builder"; +import { type Configuration as BaseConfiguration } from "electron-builder"; /** * This script has different outputs depending on your os platform. @@ -38,6 +38,7 @@ interface Metadata { interface ExtraMetadata extends Metadata { electron_appId: string; electron_protocol: string; + electron_windows_cert_sn?: string; } /** @@ -208,6 +209,7 @@ if (variant["linux.deb.name"]) { if (process.env.ED_SIGNTOOL_SUBJECT_NAME && process.env.ED_SIGNTOOL_THUMBPRINT) { config.win.signtoolOptions!.certificateSubjectName = process.env.ED_SIGNTOOL_SUBJECT_NAME; config.win.signtoolOptions!.certificateSha1 = process.env.ED_SIGNTOOL_THUMBPRINT; + config.extraMetadata.electron_windows_cert_sn = config.win.signtoolOptions!.certificateSubjectName; } if (os.platform() === "linux") { diff --git a/apps/desktop/src/build-config.ts b/apps/desktop/src/build-config.ts index 09e83e005c..f9eeee7128 100644 --- a/apps/desktop/src/build-config.ts +++ b/apps/desktop/src/build-config.ts @@ -12,9 +12,14 @@ import { type JsonObject, loadJsonFile } from "./utils.js"; const __dirname = dirname(fileURLToPath(import.meta.url)); -interface BuildConfig { +export interface BuildConfig { + // Application User Model ID appId: string; + // Protocol string used for OIDC callbacks protocol: string; + // Subject name of the code signing cert used for Windows packages, if signed + // used as a basis for the Tray GUID which must be rolled if the certificate changes. + windowsCertSubjectName: string | undefined; } export function readBuildConfig(): BuildConfig { @@ -22,5 +27,6 @@ export function readBuildConfig(): BuildConfig { return { appId: (packageJson["electron_appId"] as string) || "im.riot.app", protocol: (packageJson["electron_protocol"] as string) || "io.element.desktop", + windowsCertSubjectName: packageJson["electron_windows_cert_sn"] as string, }; } diff --git a/apps/desktop/src/electron-main.ts b/apps/desktop/src/electron-main.ts index bd1832ecc8..43fb26b486 100644 --- a/apps/desktop/src/electron-main.ts +++ b/apps/desktop/src/electron-main.ts @@ -489,7 +489,7 @@ app.on("ready", async () => { global.mainWindow.webContents.session.setSpellCheckerEnabled(store.get("spellCheckerEnabled", true)); // Create trayIcon icon - if (store.get("minimizeToTray")) tray.create(global.trayConfig); + if (store.get("minimizeToTray")) tray.create(global.trayConfig, buildConfig); global.mainWindow.once("ready-to-show", () => { if (!global.mainWindow) return; diff --git a/apps/desktop/src/tray.ts b/apps/desktop/src/tray.ts index 94deec3547..4978913469 100644 --- a/apps/desktop/src/tray.ts +++ b/apps/desktop/src/tray.ts @@ -14,6 +14,10 @@ import pngToIco from "png-to-ico"; import path from "node:path"; import { _t } from "./language-helper.js"; +import { BuildConfig } from "./build-config.js"; + +// This hardcoded uuid is an arbitrary v4 uuid generated on https://www.uuidgenerator.net/version4 +const UUID_NAMESPACE = "9fc9c6a0-9ffe-45c9-9cd7-5639ae38b232"; let trayIcon: Tray | null = null; @@ -38,30 +42,22 @@ function toggleWin(): void { } } -function getUuid(): string { - // The uuid field is optional and only needed on unsigned Windows packages where the executable path changes - // The hardcoded uuid is an arbitrary v4 uuid generated on https://www.uuidgenerator.net/version4 - return global.vectorConfig["uuid"] || "eba84003-e499-4563-8e9d-166e34b5cc25"; -} - -export function create(config: (typeof global)["trayConfig"]): void { +export function create(config: (typeof global)["trayConfig"], buildConfig: BuildConfig): void { // no trays on darwin if (process.platform === "darwin" || trayIcon) return; const defaultIcon = nativeImage.createFromPath(config.icon_path); - let guid: string | undefined; - if (process.platform === "win32" && app.isPackaged) { + if (process.platform === "win32" && app.isPackaged && buildConfig.windowsCertSubjectName) { // Providing a GUID lets Windows be smarter about maintaining user's tray preferences // https://github.com/electron/electron/pull/21891 - // Ideally we would only specify it for signed packages but determining whether the app is signed sufficiently - // is non-trivial. So instead we have an escape hatch that unsigned packages can iterate the `uuid` in - // config.json to prevent Windows refusing GUID-reuse if their executable path changes. - guid = uuidv5(`${app.getName()}-${app.getPath("userData")}`, getUuid()); + // We generate the GUID in a custom arbitrary namespace and use the subject name & userData path + // to differentiate different app builds on the same system. + const guid = uuidv5(`${buildConfig.windowsCertSubjectName}:${app.getPath("userData")}`, UUID_NAMESPACE); + trayIcon = new Tray(defaultIcon, guid); + } else { + trayIcon = new Tray(defaultIcon); } - // Passing guid=undefined on Windows will cause it to throw `Error: Invalid GUID format` - // The type here is wrong, the param must be omitted, never undefined. - trayIcon = guid ? new Tray(defaultIcon, guid) : new Tray(defaultIcon); trayIcon.setToolTip(config.brand); initApplicationMenu(); trayIcon.on("click", toggleWin);