feature/IO-3205-Paint-Scale-Integrations: Pre Protocol Handler Keep ALive.

This commit is contained in:
Dave Richer
2025-04-29 20:41:15 -04:00
parent 122f4194f5
commit 7933a8965c
4 changed files with 62 additions and 57 deletions

View File

@@ -26,7 +26,7 @@ nsis:
shortcutName: ${productName} shortcutName: ${productName}
uninstallDisplayName: ${productName} uninstallDisplayName: ${productName}
createDesktopShortcut: always createDesktopShortcut: always
requestExecutionLevel: admin # Ensure elevated privileges perMachine: true # Ensure elevated privileges
include: "scripts/installer.nsh" # Reference NSIS script from scripts directory include: "scripts/installer.nsh" # Reference NSIS script from scripts directory
mac: mac:
entitlementsInherit: build/entitlements.mac.plist entitlementsInherit: build/entitlements.mac.plist
@@ -59,10 +59,6 @@ linux:
category: Utility category: Utility
desktop: desktop:
MimeType: x-scheme-handler/imexmedia; 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: appImage:
artifactName: ${name}-${version}.${ext} artifactName: ${name}-${version}.${ext}
npmRebuild: false npmRebuild: false

View File

@@ -26,7 +26,7 @@ nsis:
shortcutName: ${productName} shortcutName: ${productName}
uninstallDisplayName: ${productName} uninstallDisplayName: ${productName}
createDesktopShortcut: always createDesktopShortcut: always
requestExecutionLevel: admin # Ensure elevated privileges perMachine: true # Ensure elevated privileges
include: "scripts/installer.nsh" # Reference NSIS script from scripts directory include: "scripts/installer.nsh" # Reference NSIS script from scripts directory
mac: mac:
entitlementsInherit: build/entitlements.mac.plist entitlementsInherit: build/entitlements.mac.plist
@@ -59,10 +59,6 @@ linux:
category: Utility category: Utility
desktop: desktop:
MimeType: x-scheme-handler/imexmedia; 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: appImage:
artifactName: ${name}-${version}.${ext} artifactName: ${name}-${version}.${ext}
npmRebuild: false npmRebuild: false

View File

@@ -78,7 +78,7 @@ function createWindow(): void {
// { role: 'appMenu' } // { role: 'appMenu' }
// @ts-ignore // @ts-ignore
...(isMac ...(isMac
? [ ? [
{ {
label: app.name, label: app.name,
submenu: [ submenu: [
@@ -94,7 +94,7 @@ function createWindow(): void {
], ],
}, },
] ]
: []), : []),
// { role: 'fileMenu' } // { role: 'fileMenu' }
{ {
label: "File", label: "File",
@@ -117,7 +117,7 @@ function createWindow(): void {
{ role: "paste" }, { role: "paste" },
// @ts-ignore // @ts-ignore
...(isMac ...(isMac
? [ ? [
{ role: "pasteAndMatchStyle" }, { role: "pasteAndMatchStyle" },
{ role: "delete" }, { role: "delete" },
{ role: "selectAll" }, { role: "selectAll" },
@@ -127,7 +127,7 @@ function createWindow(): void {
submenu: [{ role: "startSpeaking" }, { role: "stopSpeaking" }], submenu: [{ role: "startSpeaking" }, { role: "stopSpeaking" }],
}, },
] ]
: [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]), : [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]),
], ],
}, },
// { role: 'viewMenu' } // { role: 'viewMenu' }
@@ -202,13 +202,13 @@ function createWindow(): void {
click: (): void => { click: (): void => {
/* action for item 1 */ /* action for item 1 */
shell shell
.openPath(log.transports.file.getFile().path) .openPath(log.transports.file.getFile().path)
.catch((error) => { .catch((error) => {
log.error( log.error(
"Failed to open log file:", "Failed to open log file:",
errorTypeCheck(error), errorTypeCheck(error),
); );
}); });
}, },
}, },
{ {
@@ -222,8 +222,8 @@ function createWindow(): void {
click: (): void => { click: (): void => {
shell.openPath(path.dirname(store.path)).catch((error) => { shell.openPath(path.dirname(store.path)).catch((error) => {
log.error( log.error(
"Failed to open config folder:", "Failed to open config folder:",
errorTypeCheck(error), errorTypeCheck(error),
); );
}); });
}, },
@@ -232,7 +232,7 @@ function createWindow(): void {
label: "Log the Store", label: "Log the Store",
click: (): void => { click: (): void => {
log.debug( 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<void> => { click: async (): Promise<void> => {
try { try {
if (platform.isWindows) { if (platform.isWindows) {
log.debug("Creating Windows keep-alive task");
await setupKeepAliveTask(); await setupKeepAliveTask();
log.info("Successfully installed Windows keep-alive task"); log.info("Successfully installed Windows keep-alive task");
} else if (platform.isMacOS) { } else if (platform.isMacOS) {
log.debug("Creating macOS keep-alive agent");
await setupKeepAliveAgent(); await setupKeepAliveAgent();
log.info("Successfully installed macOS keep-alive agent"); 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 // Rebuild menu and update enabled state
await updateKeepAliveMenuItem(); await updateKeepAliveMenuItem();
} catch (error) { } catch (error) {
log.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) // Optionally notify user (e.g., via dialog or log)
} }
@@ -285,13 +289,13 @@ function createWindow(): void {
{ role: "zoom" }, { role: "zoom" },
// @ts-ignore // @ts-ignore
...(isMac ...(isMac
? [ ? [
{ type: "separator" }, { type: "separator" },
{ role: "front" }, { role: "front" },
{ type: "separator" }, { type: "separator" },
{ role: "window" }, { role: "window" },
] ]
: [{ role: "close" }]), : [{ role: "close" }]),
], ],
}, },
]; ];
@@ -300,29 +304,29 @@ function createWindow(): void {
const updateKeepAliveMenuItem = async (): Promise<void> => { const updateKeepAliveMenuItem = async (): Promise<void> => {
try { try {
const isInstalled = platform.isWindows const isInstalled = platform.isWindows
? await isKeepAliveTaskInstalled() ? await isKeepAliveTaskInstalled()
: platform.isMacOS : platform.isMacOS
? await isKeepAliveAgentInstalled() ? await isKeepAliveAgentInstalled()
: false; : false;
const developmentMenu = template const developmentMenu = template
.find((item) => item.label === "Application") .find((item) => item.label === "Application")
// @ts-ignore // @ts-ignore
?.submenu?.find((item: { id: string }) => item.id === "development") ?.submenu?.find((item: { id: string }) => item.id === "development")
?.submenu as Electron.MenuItemConstructorOptions[]; ?.submenu as Electron.MenuItemConstructorOptions[];
const keepAliveItem = developmentMenu?.find( const keepAliveItem = developmentMenu?.find(
(item) => item.label === "Install Keep Alive", (item) => item.label === "Install Keep Alive",
); );
if (keepAliveItem) { if (keepAliveItem) {
keepAliveItem.enabled = !isInstalled; // Enable if not installed, disable if installed keepAliveItem.enabled = !isInstalled; // Enable if not installed, disable if installed
const menu = Menu.buildFromTemplate(template); const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu); Menu.setApplicationMenu(menu);
log.debug( log.debug(
`Updated Install Keep Alive menu item: enabled=${keepAliveItem.enabled}`, `Updated Install Keep Alive menu item: enabled=${keepAliveItem.enabled}`,
); );
} }
} catch (error) { } catch (error) {
log.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 // Update menu item enabled state on app start
updateKeepAliveMenuItem().catch((error) => { updateKeepAliveMenuItem().catch((error) => {
log.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"); const fileMenu = template.find((item) => item.label === "Application");
// @ts-ignore // @ts-ignore
const hiddenItem = fileMenu?.submenu?.find( const hiddenItem = fileMenu?.submenu?.find(
(item: { id: string }) => item.id === "development", (item: { id: string }) => item.id === "development",
); );
//Adjust the development menu as well. //Adjust the development menu as well.
@@ -397,13 +401,15 @@ function createWindow(): void {
// HMR for renderer base on electron-vite cli. // HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production. // Load the remote URL for development or the local html file for production.
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]).catch(error => { mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]).catch((error) => {
log.error("Failed to load URL:", errorTypeCheck(error)); log.error("Failed to load URL:", errorTypeCheck(error));
}); });
} else { } else {
mainWindow.loadFile(join(__dirname, "../renderer/index.html")).catch(error => { mainWindow
log.error("Failed to load file:", errorTypeCheck(error)); .loadFile(join(__dirname, "../renderer/index.html"))
}); .catch((error) => {
log.error("Failed to load file:", errorTypeCheck(error));
});
} }
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
mainWindow.webContents.openDevTools(); mainWindow.webContents.openDevTools();
@@ -438,9 +444,9 @@ app.whenReady().then(async () => {
// Set the path of electron.exe and your app. // Set the path of electron.exe and your app.
// These two additional parameters are only available on windows. // These two additional parameters are only available on windows.
isDefaultProtocolClient = app.setAsDefaultProtocolClient( isDefaultProtocolClient = app.setAsDefaultProtocolClient(
protocol, protocol,
process.execPath, process.execPath,
[path.resolve(process.argv[1])], [path.resolve(process.argv[1])],
); );
} else { } else {
isDefaultProtocolClient = app.setAsDefaultProtocolClient(protocol); isDefaultProtocolClient = app.setAsDefaultProtocolClient(protocol);
@@ -453,16 +459,18 @@ app.whenReady().then(async () => {
// Add this event handler for second instance // Add this event handler for second instance
app.on("second-instance", (_event: Electron.Event, argv: string[]) => { 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}://`)); const url = argv.find((arg) => arg.startsWith(`${protocol}://`));
if (url) { if (url) {
if (url.startsWith(`${protocol}://keep-alive`)) { if (url.startsWith(`${protocol}://keep-alive`)) {
log.info("Keep-alive protocol received, app is already running."); log.info("Keep-alive protocol received, app is already running.");
// Do nothing if already running // Do nothing if already running
return; // Skip openMainWindow to avoid focusing the window
} else { } else {
openInExplorer(url); 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}`); log.info(`Total downloaded ${progress.transferred}/${progress.total}`);
const mainWindow = BrowserWindow.getAllWindows()[0]; const mainWindow = BrowserWindow.getAllWindows()[0];
mainWindow?.webContents.send( mainWindow?.webContents.send(
ipcTypes.toRenderer.updates.downloading, ipcTypes.toRenderer.updates.downloading,
progress, progress,
); );
}); });
autoUpdater.on("update-downloaded", (info) => { 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. //Don't do anything for now. We just want to open the app.
if (url.startsWith(`${protocol}://keep-alive`)) { if (url.startsWith(`${protocol}://keep-alive`)) {
log.info("Keep-alive protocol received."); log.info("Keep-alive protocol received.");
isKeepAliveLaunch = true; if (BrowserWindow.getAllWindows().length === 0) {
openMainWindow(); isKeepAliveLaunch = true;
openMainWindow(); // Launch app if not running
}
// Do nothing if already running
return; // Skip openMainWindow to avoid focusing the window
} else { } else {
openInExplorer(url); openInExplorer(url);
openMainWindow(); // Focus window for non-keep-alive URLs
} }
}); });
@@ -625,4 +638,4 @@ function openInExplorer(url: string): void {
shell.openPath(folderPath).catch((error) => { shell.openPath(folderPath).catch((error) => {
log.error("Failed to open folder in explorer:", errorTypeCheck(error)); log.error("Failed to open folder in explorer:", errorTypeCheck(error));
}); });
} }

View File

@@ -7,8 +7,8 @@ const execPromise = promisify(exec);
export async function setupKeepAliveTask(): Promise<void> { export async function setupKeepAliveTask(): Promise<void> {
const taskName = "ImEXShopPartnerKeepAlive"; const taskName = "ImEXShopPartnerKeepAlive";
const protocolUrl = "imexmedia://keep-alive"; const protocolUrl = "imexmedia://keep-alive";
// Use PowerShell with -ExecutionPolicy Bypass to open the URL // Use rundll32.exe to silently open the URL as a protocol
const command = `powershell.exe -ExecutionPolicy Bypass -Command "Start-Process '${protocolUrl}'"`; const command = `rundll32.exe url.dll,OpenURL "${protocolUrl}"`;
// Escape quotes for schtasks /tr parameter // Escape quotes for schtasks /tr parameter
const escapedCommand = command.replace(/"/g, '\\"'); const escapedCommand = command.replace(/"/g, '\\"');