From 7933a8965c8660274d94cd9838441f960fbbda88 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 29 Apr 2025 20:41:15 -0400 Subject: [PATCH] feature/IO-3205-Paint-Scale-Integrations: Pre Protocol Handler Keep ALive. --- electron-builder.imex.yml | 6 +- electron-builder.rome.yml | 6 +- src/main/index.ts | 103 +++++++++++++++++------------- src/main/setup-keep-alive-task.ts | 4 +- 4 files changed, 62 insertions(+), 57 deletions(-) diff --git a/electron-builder.imex.yml b/electron-builder.imex.yml index 7c72f21..b0158a6 100644 --- a/electron-builder.imex.yml +++ b/electron-builder.imex.yml @@ -26,7 +26,7 @@ nsis: shortcutName: ${productName} uninstallDisplayName: ${productName} createDesktopShortcut: always - requestExecutionLevel: admin # Ensure elevated privileges + perMachine: true # Ensure elevated privileges include: "scripts/installer.nsh" # Reference NSIS script from scripts directory mac: entitlementsInherit: build/entitlements.mac.plist @@ -59,10 +59,6 @@ linux: category: Utility desktop: MimeType: x-scheme-handler/imexmedia; - afterInstall: | - # Install .desktop file for protocol handling - cp scripts/imex-shop-partner.desktop $HOME/.local/share/applications/ - update-desktop-database $HOME/.local/share/applications/ appImage: artifactName: ${name}-${version}.${ext} npmRebuild: false diff --git a/electron-builder.rome.yml b/electron-builder.rome.yml index eeb5f37..b31686b 100644 --- a/electron-builder.rome.yml +++ b/electron-builder.rome.yml @@ -26,7 +26,7 @@ nsis: shortcutName: ${productName} uninstallDisplayName: ${productName} createDesktopShortcut: always - requestExecutionLevel: admin # Ensure elevated privileges + perMachine: true # Ensure elevated privileges include: "scripts/installer.nsh" # Reference NSIS script from scripts directory mac: entitlementsInherit: build/entitlements.mac.plist @@ -59,10 +59,6 @@ linux: category: Utility desktop: MimeType: x-scheme-handler/imexmedia; - afterInstall: | - # Install .desktop file for protocol handling - cp scripts/rome-shop-partner.desktop $HOME/.local/share/applications/ - update-desktop-database $HOME/.local/share/applications/ appImage: artifactName: ${name}-${version}.${ext} npmRebuild: false diff --git a/src/main/index.ts b/src/main/index.ts index 711e21f..28a7abb 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -78,7 +78,7 @@ function createWindow(): void { // { role: 'appMenu' } // @ts-ignore ...(isMac - ? [ + ? [ { label: app.name, submenu: [ @@ -94,7 +94,7 @@ function createWindow(): void { ], }, ] - : []), + : []), // { role: 'fileMenu' } { label: "File", @@ -117,7 +117,7 @@ function createWindow(): void { { role: "paste" }, // @ts-ignore ...(isMac - ? [ + ? [ { role: "pasteAndMatchStyle" }, { role: "delete" }, { role: "selectAll" }, @@ -127,7 +127,7 @@ function createWindow(): void { submenu: [{ role: "startSpeaking" }, { role: "stopSpeaking" }], }, ] - : [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]), + : [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]), ], }, // { role: 'viewMenu' } @@ -202,13 +202,13 @@ function createWindow(): void { click: (): void => { /* action for item 1 */ shell - .openPath(log.transports.file.getFile().path) - .catch((error) => { - log.error( - "Failed to open log file:", - errorTypeCheck(error), - ); - }); + .openPath(log.transports.file.getFile().path) + .catch((error) => { + log.error( + "Failed to open log file:", + errorTypeCheck(error), + ); + }); }, }, { @@ -222,8 +222,8 @@ function createWindow(): void { click: (): void => { shell.openPath(path.dirname(store.path)).catch((error) => { log.error( - "Failed to open config folder:", - errorTypeCheck(error), + "Failed to open config folder:", + errorTypeCheck(error), ); }); }, @@ -232,7 +232,7 @@ function createWindow(): void { label: "Log the Store", click: (): void => { log.debug( - "Store Contents" + JSON.stringify(store.store, null, 4), + "Store Contents" + JSON.stringify(store.store, null, 4), ); }, }, @@ -251,17 +251,21 @@ function createWindow(): void { click: async (): Promise => { try { if (platform.isWindows) { + log.debug("Creating Windows keep-alive task"); await setupKeepAliveTask(); log.info("Successfully installed Windows keep-alive task"); } else if (platform.isMacOS) { + log.debug("Creating macOS keep-alive agent"); await setupKeepAliveAgent(); log.info("Successfully installed macOS keep-alive agent"); } + // Wait to ensure task/agent is registered + await new Promise((resolve) => setTimeout(resolve, 1500)); // Rebuild menu and update enabled state await updateKeepAliveMenuItem(); } catch (error) { log.error( - `Failed to install keep-alive: ${error instanceof Error ? error.message : String(error)}`, + `Failed to install keep-alive: ${error instanceof Error ? error.message : String(error)}`, ); // Optionally notify user (e.g., via dialog or log) } @@ -285,13 +289,13 @@ function createWindow(): void { { role: "zoom" }, // @ts-ignore ...(isMac - ? [ + ? [ { type: "separator" }, { role: "front" }, { type: "separator" }, { role: "window" }, ] - : [{ role: "close" }]), + : [{ role: "close" }]), ], }, ]; @@ -300,29 +304,29 @@ function createWindow(): void { const updateKeepAliveMenuItem = async (): Promise => { try { const isInstalled = platform.isWindows - ? await isKeepAliveTaskInstalled() - : platform.isMacOS - ? await isKeepAliveAgentInstalled() - : false; + ? await isKeepAliveTaskInstalled() + : platform.isMacOS + ? await isKeepAliveAgentInstalled() + : false; const developmentMenu = template - .find((item) => item.label === "Application") - // @ts-ignore - ?.submenu?.find((item: { id: string }) => item.id === "development") - ?.submenu as Electron.MenuItemConstructorOptions[]; + .find((item) => item.label === "Application") + // @ts-ignore + ?.submenu?.find((item: { id: string }) => item.id === "development") + ?.submenu as Electron.MenuItemConstructorOptions[]; const keepAliveItem = developmentMenu?.find( - (item) => item.label === "Install Keep Alive", + (item) => item.label === "Install Keep Alive", ); if (keepAliveItem) { keepAliveItem.enabled = !isInstalled; // Enable if not installed, disable if installed const menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); log.debug( - `Updated Install Keep Alive menu item: enabled=${keepAliveItem.enabled}`, + `Updated Install Keep Alive menu item: enabled=${keepAliveItem.enabled}`, ); } } catch (error) { log.error( - `Error updating Keep Alive menu item: ${error instanceof Error ? error.message : String(error)}`, + `Error updating Keep Alive menu item: ${error instanceof Error ? error.message : String(error)}`, ); } }; @@ -332,7 +336,7 @@ function createWindow(): void { // Update menu item enabled state on app start updateKeepAliveMenuItem().catch((error) => { log.error( - `Error updating Keep Alive menu item: ${error instanceof Error ? error.message : String(error)}`, + `Error updating Keep Alive menu item: ${error instanceof Error ? error.message : String(error)}`, ); }); @@ -345,7 +349,7 @@ function createWindow(): void { const fileMenu = template.find((item) => item.label === "Application"); // @ts-ignore const hiddenItem = fileMenu?.submenu?.find( - (item: { id: string }) => item.id === "development", + (item: { id: string }) => item.id === "development", ); //Adjust the development menu as well. @@ -397,13 +401,15 @@ function createWindow(): void { // HMR for renderer base on electron-vite cli. // Load the remote URL for development or the local html file for production. if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { - mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]).catch(error => { - log.error("Failed to load URL:", errorTypeCheck(error)); + mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]).catch((error) => { + log.error("Failed to load URL:", errorTypeCheck(error)); }); } else { - mainWindow.loadFile(join(__dirname, "../renderer/index.html")).catch(error => { - log.error("Failed to load file:", errorTypeCheck(error)); - }); + mainWindow + .loadFile(join(__dirname, "../renderer/index.html")) + .catch((error) => { + log.error("Failed to load file:", errorTypeCheck(error)); + }); } if (import.meta.env.DEV) { mainWindow.webContents.openDevTools(); @@ -438,9 +444,9 @@ app.whenReady().then(async () => { // Set the path of electron.exe and your app. // These two additional parameters are only available on windows. isDefaultProtocolClient = app.setAsDefaultProtocolClient( - protocol, - process.execPath, - [path.resolve(process.argv[1])], + protocol, + process.execPath, + [path.resolve(process.argv[1])], ); } else { isDefaultProtocolClient = app.setAsDefaultProtocolClient(protocol); @@ -453,16 +459,18 @@ app.whenReady().then(async () => { // Add this event handler for second instance app.on("second-instance", (_event: Electron.Event, argv: string[]) => { - // Someone tried to run a second instance, we should focus our window - openMainWindow(); const url = argv.find((arg) => arg.startsWith(`${protocol}://`)); if (url) { if (url.startsWith(`${protocol}://keep-alive`)) { log.info("Keep-alive protocol received, app is already running."); // Do nothing if already running + return; // Skip openMainWindow to avoid focusing the window } else { openInExplorer(url); + openMainWindow(); // Focus window for non-keep-alive URLs } + } else { + openMainWindow(); // Focus window if no URL } }); @@ -535,8 +543,8 @@ app.whenReady().then(async () => { log.info(`Total downloaded ${progress.transferred}/${progress.total}`); const mainWindow = BrowserWindow.getAllWindows()[0]; mainWindow?.webContents.send( - ipcTypes.toRenderer.updates.downloading, - progress, + ipcTypes.toRenderer.updates.downloading, + progress, ); }); autoUpdater.on("update-downloaded", (info) => { @@ -564,10 +572,15 @@ app.on("open-url", (event: Electron.Event, url: string) => { //Don't do anything for now. We just want to open the app. if (url.startsWith(`${protocol}://keep-alive`)) { log.info("Keep-alive protocol received."); - isKeepAliveLaunch = true; - openMainWindow(); + if (BrowserWindow.getAllWindows().length === 0) { + isKeepAliveLaunch = true; + openMainWindow(); // Launch app if not running + } + // Do nothing if already running + return; // Skip openMainWindow to avoid focusing the window } else { openInExplorer(url); + openMainWindow(); // Focus window for non-keep-alive URLs } }); @@ -625,4 +638,4 @@ function openInExplorer(url: string): void { shell.openPath(folderPath).catch((error) => { log.error("Failed to open folder in explorer:", errorTypeCheck(error)); }); -} +} \ No newline at end of file diff --git a/src/main/setup-keep-alive-task.ts b/src/main/setup-keep-alive-task.ts index afcfbb0..14d917e 100644 --- a/src/main/setup-keep-alive-task.ts +++ b/src/main/setup-keep-alive-task.ts @@ -7,8 +7,8 @@ const execPromise = promisify(exec); export async function setupKeepAliveTask(): Promise { const taskName = "ImEXShopPartnerKeepAlive"; const protocolUrl = "imexmedia://keep-alive"; - // Use PowerShell with -ExecutionPolicy Bypass to open the URL - const command = `powershell.exe -ExecutionPolicy Bypass -Command "Start-Process '${protocolUrl}'"`; + // Use rundll32.exe to silently open the URL as a protocol + const command = `rundll32.exe url.dll,OpenURL "${protocolUrl}"`; // Escape quotes for schtasks /tr parameter const escapedCommand = command.replace(/"/g, '\\"');