From 2b27889e0757d4c4bd0d581283fd25c4232d826a Mon Sep 17 00:00:00 2001 From: sravan Date: Thu, 2 Jul 2026 11:16:35 +0530 Subject: [PATCH] fix(desktop): use Electron's native Notification (reliable click-to-open) node-notifier/SnoreToast click callback never fired without the crashing wait mode. Switch the chat toast to Electron's built-in Notification: shows avatar + message and its 'click' event reliably raises the app + opens the chat. No SnoreToast, no external tools. Co-Authored-By: Claude Opus 4.8 --- desktop/main.js | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/desktop/main.js b/desktop/main.js index 8e4494e..fe04ad9 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -8,7 +8,7 @@ // - external links open in the user's browser, not inside the app // // Server origin is configurable so the same build works against prod or a dev server. -const { app, BrowserWindow, session, desktopCapturer, shell, Menu, ipcMain, nativeImage } = require('electron'); +const { app, BrowserWindow, session, desktopCapturer, shell, Menu, ipcMain, nativeImage, Notification } = require('electron'); const path = require('path'); const fs = require('fs'); const os = require('os'); @@ -35,15 +35,11 @@ ipcMain.on('get-install-info', (e) => { // on the next restart. No-op in dev (unpackaged). let autoUpdater = null; try { ({ autoUpdater } = require('electron-updater')); } catch (_) { /* not installed in dev */ } -// Chat toast: shows the sender/group avatar + message; clicking it opens the chat. Uses -// node-notifier's WindowsToaster for reliable DISPLAY. Inline text reply is intentionally not -// used — the bundled SnoreToast crashes (0xC0000409) handling a text reply — so this is a clean -// click-to-open toast. Installed-app only (needs the AppUserModelID shortcut the installer sets). -let notifier = null; -try { notifier = require('node-notifier'); } catch (_) { /* optional */ } +// Chat toast: sender/group avatar + message; clicking it raises the app and opens that chat. +// Uses Electron's OWN Notification (native, no SnoreToast) whose 'click' event fires reliably. const APP_ID = 'com.bizgaze.connect.desktop'; -// Write the avatar the renderer drew (a data: URL) to a temp PNG for the toast image. +// Write the avatar the renderer drew (a data: URL) to a temp PNG for the toast icon. function writeTempPng(dataUrl) { try { if (!dataUrl || !/^data:image\/png;base64,/.test(dataUrl)) return null; @@ -53,20 +49,26 @@ function writeTempPng(dataUrl) { } catch (_) { return null; } } -// Resolves {open} when the toast is clicked (the renderer then opens that chat), else null. +// Resolves {open} when the toast is clicked (renderer then opens that chat), else null. ipcMain.handle('reply-notification', (_e, payload = {}) => new Promise((resolve) => { - if (!notifier) return resolve(null); + if (!Notification.isSupported()) return resolve(null); const img = writeTempPng(payload.avatar); let done = false; const finish = (v) => { if (!done) { done = true; if (img) { try { fs.unlinkSync(img); } catch (_) {} } resolve(v); } }; try { - notifier.notify( - Object.assign({ appID: APP_ID, title: payload.title || 'Biz Connect', message: payload.body || ' ' }, img ? { icon: img } : {}), - (err, response, metadata) => { - const act = String(response || (metadata && metadata.action) || '').toLowerCase(); - finish(act.includes('activat') || act === 'click' ? { kind: payload.kind, id: payload.id, open: true } : null); - } - ); + const n = new Notification({ + title: payload.title || 'Biz Connect', + body: payload.body || '', + icon: img ? nativeImage.createFromPath(img) : undefined, + silent: false, + }); + n.on('click', () => { + if (win) { if (win.isMinimized()) win.restore(); win.show(); win.focus(); } // raise the app + finish({ kind: payload.kind, id: payload.id, open: true }); + }); + n.on('close', () => finish(null)); + n.show(); + setTimeout(() => finish(null), 30000); // don't leave the promise pending forever } catch (_) { finish(null); } }));