fix: keep notifications alive so clicks still work on macOS
GC was collecting the JS Notification object after ~1 min, silently
killing click handlers. Hold a ref in a Set, release on click/close/
failed. Also order app.focus({steal}) before restore/show so the app
comes forward when minimized to Dock or Cmd+H'd.
This commit is contained in:
@@ -581,12 +581,23 @@ const focusForegroundWindow = () => {
|
|||||||
const target = state.mainWindow && !state.mainWindow.isDestroyed()
|
const target = state.mainWindow && !state.mainWindow.isDestroyed()
|
||||||
? state.mainWindow
|
? state.mainWindow
|
||||||
: windows.find((window) => window.isVisible()) || windows[0];
|
: windows.find((window) => window.isVisible()) || windows[0];
|
||||||
if (target.isMinimized()) target.restore();
|
// macOS: bring the app to foreground FIRST. When the window is minimized
|
||||||
if (!target.isVisible()) target.show();
|
// to the Dock or hidden via Cmd+H, the app is in the background, and
|
||||||
target.focus();
|
// subsequent window.show/restore/focus calls won't pull it forward
|
||||||
|
// unless app.focus runs first.
|
||||||
if (process.platform === 'darwin') app.focus({ steal: true });
|
if (process.platform === 'darwin') app.focus({ steal: true });
|
||||||
|
if (target.isMinimized()) target.restore();
|
||||||
|
target.show();
|
||||||
|
target.focus();
|
||||||
|
if (typeof target.moveTop === 'function') target.moveTop();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Keep references to live notifications so they aren't garbage-collected
|
||||||
|
// before the OS fires click/close. On macOS, losing the JS reference causes
|
||||||
|
// click events to silently stop firing after ~1 min.
|
||||||
|
// See https://blog.bloomca.me/2025/02/22/electron-mac-notifications
|
||||||
|
const activeNotifications = new Set();
|
||||||
|
|
||||||
const maybeShowNativeNotification = (rawInput) => {
|
const maybeShowNativeNotification = (rawInput) => {
|
||||||
const payload = normalizeNotificationInput(rawInput);
|
const payload = normalizeNotificationInput(rawInput);
|
||||||
const requireHidden = Boolean(payload.requireHidden ?? payload.require_hidden);
|
const requireHidden = Boolean(payload.requireHidden ?? payload.require_hidden);
|
||||||
@@ -614,12 +625,18 @@ const maybeShowNativeNotification = (rawInput) => {
|
|||||||
...(process.platform === 'darwin' ? { sound: 'Glass' } : {}),
|
...(process.platform === 'darwin' ? { sound: 'Glass' } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
activeNotifications.add(notification);
|
||||||
|
const release = () => { activeNotifications.delete(notification); };
|
||||||
|
|
||||||
notification.on('click', () => {
|
notification.on('click', () => {
|
||||||
focusForegroundWindow();
|
focusForegroundWindow();
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
emitToAllWindows('openchamber:open-session', { sessionId });
|
emitToAllWindows('openchamber:open-session', { sessionId });
|
||||||
}
|
}
|
||||||
|
release();
|
||||||
});
|
});
|
||||||
|
notification.on('close', release);
|
||||||
|
notification.on('failed', release);
|
||||||
|
|
||||||
notification.show();
|
notification.show();
|
||||||
};
|
};
|
||||||
@@ -2179,9 +2196,8 @@ app.on('activate', async () => {
|
|||||||
if (windows.length > 0) {
|
if (windows.length > 0) {
|
||||||
const visibleWindow = windows.find((window) => window.isVisible());
|
const visibleWindow = windows.find((window) => window.isVisible());
|
||||||
const targetWindow = visibleWindow || state.mainWindow || windows[0];
|
const targetWindow = visibleWindow || state.mainWindow || windows[0];
|
||||||
if (!targetWindow.isVisible()) {
|
if (targetWindow.isMinimized()) targetWindow.restore();
|
||||||
targetWindow.show();
|
targetWindow.show();
|
||||||
}
|
|
||||||
targetWindow.focus();
|
targetWindow.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user