Fix notifications not working (#2865)

* Fix notifications not working

* Transform into a manager

* Remove test accessor
This commit is contained in:
Daniel Espino García 2023-10-10 16:13:07 +02:00 committed by GitHub
parent 02261c9de3
commit 9aec7db821
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 269 additions and 199 deletions

View file

@ -96,7 +96,8 @@
"src/main/**/*.ts"
],
"testMatch": [
"**/src/**/*.test.js"
"**/src/**/*.test.js",
"**/src/**/*.test.ts"
],
"testPathIgnorePatterns": [
"/node_modules/",

View file

@ -12,7 +12,7 @@ import {Logger} from 'common/log';
import ServerManager from 'common/servers/serverManager';
import {ping} from 'common/utils/requests';
import {displayMention} from 'main/notifications';
import NotificationManager from 'main/notifications';
import {getLocalPreload, getLocalURLString} from 'main/utils';
import ModalManager from 'main/views/modalManager';
import MainWindow from 'main/windows/mainWindow';
@ -116,7 +116,7 @@ export function handleWelcomeScreenModal() {
export function handleMentionNotification(event: IpcMainEvent, title: string, body: string, channel: {id: string}, teamId: string, url: string, silent: boolean, data: MentionData) {
log.debug('handleMentionNotification', {title, body, channel, teamId, url, silent, data});
displayMention(title, body, channel, teamId, url, silent, event.sender, data);
NotificationManager.displayMention(title, body, channel, teamId, url, silent, event.sender, data);
}
export function handleOpenAppMenu() {

View file

@ -1,13 +1,17 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ipcMain} from 'electron';
import {autoUpdater} from 'electron-updater';
import {ipcMain as notMockedIpcMain} from 'electron';
import {autoUpdater as notMockedAutoUpdater} from 'electron-updater';
import {CHECK_FOR_UPDATES} from 'common/communication';
import NotificationManager from 'main/notifications';
import {UpdateManager} from './autoUpdater';
import {displayRestartToUpgrade, displayUpgrade} from './notifications';
const autoUpdater = jest.mocked(notMockedAutoUpdater);
const ipcMain = jest.mocked(notMockedIpcMain);
jest.mock('electron', () => ({
app: {
@ -44,6 +48,7 @@ jest.mock('main/notifications', () => ({
displayUpgrade: jest.fn(),
displayRestartToUpgrade: jest.fn(),
}));
jest.mock('main/windows/mainWindow', () => ({
sendToRenderer: jest.fn(),
}));
@ -55,6 +60,7 @@ jest.mock('main/i18nManager', () => ({
jest.mock('main/downloadsManager', () => ({
removeUpdateBeforeRestart: jest.fn(),
}));
describe('main/autoUpdater', () => {
describe('constructor', () => {
afterEach(() => {
@ -62,11 +68,12 @@ describe('main/autoUpdater', () => {
});
it('should notify user on update-available', () => {
let cb;
let cb: any;
autoUpdater.on.mockImplementation((event, callback) => {
if (event === 'update-available') {
cb = callback;
}
return autoUpdater;
});
const updateManager = new UpdateManager();
@ -78,11 +85,12 @@ describe('main/autoUpdater', () => {
});
it('should notify user on update-downloaded', () => {
let cb;
let cb: any;
autoUpdater.on.mockImplementation((event, callback) => {
if (event === 'update-downloaded') {
cb = callback;
}
return autoUpdater;
});
const updateManager = new UpdateManager();
@ -94,11 +102,12 @@ describe('main/autoUpdater', () => {
});
it('should check for updates when emitted', () => {
let cb;
let cb: any;
ipcMain.on.mockImplementation((event, callback) => {
if (event === CHECK_FOR_UPDATES) {
cb = callback;
}
return ipcMain;
});
const updateManager = new UpdateManager();
@ -133,7 +142,7 @@ describe('main/autoUpdater', () => {
updateManager.versionAvailable = '5.1.0';
updateManager.notify();
updateManager.notify = jest.fn();
expect(displayUpgrade).toHaveBeenCalledWith('5.1.0', expect.any(Function));
expect(NotificationManager.displayUpgrade).toHaveBeenCalledWith('5.1.0', expect.any(Function));
});
it('should display downloaded upgrade notification', () => {
@ -141,13 +150,13 @@ describe('main/autoUpdater', () => {
updateManager.versionDownloaded = '5.1.0';
updateManager.notify();
updateManager.notify = jest.fn();
expect(displayRestartToUpgrade).toHaveBeenCalledWith('5.1.0', expect.any(Function));
expect(NotificationManager.displayRestartToUpgrade).toHaveBeenCalledWith('5.1.0', expect.any(Function));
});
});
describe('checkForUpdates', () => {
beforeEach(() => {
autoUpdater.checkForUpdates.mockReturnValue(Promise.resolve());
autoUpdater.checkForUpdates.mockReturnValue(Promise.resolve(null));
jest.useFakeTimers();
});
@ -164,8 +173,9 @@ describe('main/autoUpdater', () => {
it('should show dialog if update is not available', () => {
autoUpdater.once.mockImplementation((event, callback) => {
if (event === 'update-not-available') {
callback();
(callback as any)();
}
return autoUpdater;
});
const updateManager = new UpdateManager();
@ -177,7 +187,7 @@ describe('main/autoUpdater', () => {
it('should check again at the next interval', () => {
const updateManager = new UpdateManager();
updateManager.checkForUpdates();
updateManager.checkForUpdates(false);
updateManager.checkForUpdates = jest.fn();
jest.runAllTimers();
expect(updateManager.checkForUpdates).toBeCalled();

View file

@ -10,7 +10,7 @@ import {Logger} from 'common/log';
import downloadsManager from 'main/downloadsManager';
import {localizeMessage} from 'main/i18nManager';
import {displayUpgrade, displayRestartToUpgrade} from 'main/notifications';
import NotificationManager from 'main/notifications';
import {
CANCEL_UPGRADE,
@ -113,12 +113,12 @@ export class UpdateManager {
notifyUpgrade = (): void => {
ipcMain.emit(UPDATE_AVAILABLE, null, this.versionAvailable);
displayUpgrade(this.versionAvailable || 'unknown', this.handleDownload);
NotificationManager.displayUpgrade(this.versionAvailable || 'unknown', this.handleDownload);
}
notifyDownloaded = (): void => {
ipcMain.emit(UPDATE_DOWNLOADED, null, this.downloadedInfo);
displayRestartToUpgrade(this.versionDownloaded || 'unknown', this.handleUpdate);
NotificationManager.displayRestartToUpgrade(this.versionDownloaded || 'unknown', this.handleUpdate);
}
handleDownload = (): void => {

View file

@ -31,7 +31,7 @@ import {APP_UPDATE_KEY, UPDATE_DOWNLOAD_ITEM} from 'common/constants';
import {DOWNLOADS_DROPDOWN_AUTOCLOSE_TIMEOUT, DOWNLOADS_DROPDOWN_MAX_ITEMS} from 'common/utils/constants';
import * as Validator from 'common/Validator';
import {localizeMessage} from 'main/i18nManager';
import {displayDownloadCompleted} from 'main/notifications';
import NotificationManager from 'main/notifications';
import ViewManager from 'main/views/viewManager';
import MainWindow from 'main/windows/mainWindow';
import {doubleSecToMs, getPercentage, isStringWithLength, readFilenameFromContentDispositionHeader, shouldIncrementFilename} from 'main/utils';
@ -559,7 +559,7 @@ export class DownloadsManager extends JsonFileManager<DownloadedItems> {
log.debug('doneEventController', {state});
if (state === 'completed' && !this.open) {
displayDownloadCompleted(path.basename(item.savePath), item.savePath, ViewManager.getViewByWebContentsId(webContents.id)?.view.server.name ?? '');
NotificationManager.displayDownloadCompleted(path.basename(item.savePath), item.savePath, ViewManager.getViewByWebContentsId(webContents.id)?.view.server.name ?? '');
}
const bookmark = this.bookmarks.get(this.getFileId(item));

View file

@ -4,6 +4,8 @@
import os from 'os';
import path from 'path';
import {v4 as uuid} from 'uuid';
import {app, Notification} from 'electron';
import Utils from 'common/utils/util';
@ -22,6 +24,8 @@ const defaultOptions = {
};
export class DownloadNotification extends Notification {
uId: string;
constructor(fileName: string, serverName: string) {
const options = {...defaultOptions};
if (process.platform === 'darwin' || (process.platform === 'win32' && Utils.isVersionGreaterThanOrEqualTo(os.release(), '10.0'))) {
@ -33,5 +37,7 @@ export class DownloadNotification extends Notification {
options.body = process.platform === 'win32' ? localizeMessage('main.notifications.download.complete.body', 'Download Complete \n {fileName}', {fileName}) : fileName;
super(options);
this.uId = uuid();
}
}

View file

@ -4,6 +4,8 @@
import os from 'os';
import path from 'path';
import {v4 as uuid} from 'uuid';
import {app, Notification} from 'electron';
import {MentionOptions} from 'types/notification';
@ -27,6 +29,7 @@ export class Mention extends Notification {
customSound: string;
channel: {id: string}; // TODO: Channel from mattermost-redux
teamId: string;
uId: string;
constructor(customOptions: MentionOptions, channel: {id: string}, teamId: string) {
const options = {...defaultOptions, ...customOptions};
@ -44,6 +47,7 @@ export class Mention extends Notification {
this.customSound = customSound;
this.channel = channel;
this.teamId = teamId;
this.uId = uuid();
}
getNotificationSound = () => {

View file

@ -2,26 +2,35 @@
// See LICENSE.txt for license information.
'use strict';
import cp from 'child_process';
import notMockedCP from 'child_process';
import {Notification, shell, app} from 'electron';
import {Notification as NotMockedNotification, shell, app, BrowserWindow, WebContents} from 'electron';
import {getFocusAssist} from 'windows-focus-assist';
import {getDoNotDisturb as getDarwinDoNotDisturb} from 'macos-notification-state';
import {getFocusAssist as notMockedGetFocusAssist} from 'windows-focus-assist';
import {getDoNotDisturb as notMockedGetDarwinDoNotDisturb} from 'macos-notification-state';
import {PLAY_SOUND} from 'common/communication';
import Config from 'common/config';
import notMockedConfig from 'common/config';
import {localizeMessage} from 'main/i18nManager';
import PermissionsManager from 'main/permissionsManager';
import MainWindow from 'main/windows/mainWindow';
import {localizeMessage as notMockedLocalizeMessage} from 'main/i18nManager';
import notMockedPermissionsManager from 'main/permissionsManager';
import notMockedMainWindow from 'main/windows/mainWindow';
import ViewManager from 'main/views/viewManager';
import getLinuxDoNotDisturb from './dnd-linux';
import {displayMention, displayDownloadCompleted, currentNotifications} from './index';
import NotificationManager from './index';
const mentions = [];
const Notification = jest.mocked(NotMockedNotification);
const getFocusAssist = jest.mocked(notMockedGetFocusAssist);
const PermissionsManager = jest.mocked(notMockedPermissionsManager);
const getDarwinDoNotDisturb = jest.mocked(notMockedGetDarwinDoNotDisturb);
const Config = jest.mocked(notMockedConfig);
const MainWindow = jest.mocked(notMockedMainWindow);
const localizeMessage = jest.mocked(notMockedLocalizeMessage);
const cp = jest.mocked(notMockedCP);
const mentions: Array<{body: string; value: any}> = [];
jest.mock('child_process', () => ({
execSync: jest.fn(),
@ -29,25 +38,26 @@ jest.mock('child_process', () => ({
jest.mock('electron', () => {
class NotificationMock {
callbackMap: Map<string, () => void>;
static isSupported = jest.fn();
static didConstruct = jest.fn();
constructor(options) {
constructor(options: any) {
NotificationMock.didConstruct();
this.callbackMap = new Map();
mentions.push({body: options.body, value: this});
}
on = (event, callback) => {
on = (event: string, callback: () => void) => {
this.callbackMap.set(event, callback);
}
show = jest.fn().mockImplementation(() => {
this.callbackMap.get('show')();
this.callbackMap.get('show')?.();
});
click = jest.fn().mockImplementation(() => {
this.callbackMap.get('click')();
this.callbackMap.get('click')?.();
});
close = jest.fn();
@ -105,35 +115,43 @@ describe('main/notifications', () => {
describe('displayMention', () => {
const mainWindow = {
flashFrame: jest.fn(),
};
} as unknown as BrowserWindow;
beforeEach(() => {
PermissionsManager.doPermissionRequest.mockReturnValue(Promise.resolve(true));
Notification.isSupported.mockImplementation(() => true);
getFocusAssist.mockReturnValue({value: false});
getFocusAssist.mockReturnValue({value: 0, name: ''});
getDarwinDoNotDisturb.mockReturnValue(false);
Config.notifications = {};
Config.notifications = {
flashWindow: 0,
bounceIcon: false,
bounceIconType: 'informational',
};
MainWindow.get.mockReturnValue(mainWindow);
});
afterEach(() => {
jest.resetAllMocks();
Config.notifications = {};
Config.notifications = {
flashWindow: 0,
bounceIcon: false,
bounceIconType: 'informational',
};
});
it('should do nothing when Notification is not supported', async () => {
Notification.isSupported.mockImplementation(() => false);
await displayMention(
await NotificationManager.displayMention(
'test',
'test body',
{id: 'channel_id'},
'team_id',
'http://server-1.com/team_id/channel_id',
false,
{id: 1},
{},
{id: 1} as WebContents,
{soundName: ''},
);
expect(Notification.didConstruct).not.toBeCalled();
expect(MainWindow.show).not.toBeCalled();
});
it('should do nothing when alarms only is enabled on windows', async () => {
@ -142,18 +160,18 @@ describe('main/notifications', () => {
value: 'win32',
});
getFocusAssist.mockReturnValue({value: 2});
await displayMention(
getFocusAssist.mockReturnValue({value: 2, name: ''});
await NotificationManager.displayMention(
'test',
'test body',
{id: 'channel_id'},
'team_id',
'http://server-1.com/team_id/channel_id',
false,
{id: 1},
{},
{id: 1} as WebContents,
{soundName: ''},
);
expect(Notification.didConstruct).not.toBeCalled();
expect(MainWindow.show).not.toBeCalled();
Object.defineProperty(process, 'platform', {
value: originalPlatform,
@ -167,17 +185,17 @@ describe('main/notifications', () => {
});
getDarwinDoNotDisturb.mockReturnValue(true);
await displayMention(
await NotificationManager.displayMention(
'test',
'test body',
{id: 'channel_id'},
'team_id',
'http://server-1.com/team_id/channel_id',
false,
{id: 1},
{},
{id: 1} as WebContents,
{soundName: ''},
);
expect(Notification.didConstruct).not.toBeCalled();
expect(MainWindow.show).not.toBeCalled();
Object.defineProperty(process, 'platform', {
value: originalPlatform,
@ -186,28 +204,28 @@ describe('main/notifications', () => {
it('should do nothing when the permission check fails', async () => {
PermissionsManager.doPermissionRequest.mockReturnValue(Promise.resolve(false));
await displayMention(
await NotificationManager.displayMention(
'test',
'test body',
{id: 'channel_id'},
'team_id',
'http://server-1.com/team_id/channel_id',
false,
{id: 1},
{},
{id: 1} as WebContents,
{soundName: ''},
);
expect(Notification.didConstruct).not.toBeCalled();
expect(MainWindow.show).not.toBeCalled();
});
it('should play notification sound when custom sound is provided', async () => {
await displayMention(
await NotificationManager.displayMention(
'test',
'test body',
{id: 'channel_id'},
'team_id',
'http://server-1.com/team_id/channel_id',
false,
{id: 1},
{id: 1} as WebContents,
{soundName: 'test_sound'},
);
expect(MainWindow.sendToRenderer).toHaveBeenCalledWith(PLAY_SOUND, 'test_sound');
@ -219,34 +237,36 @@ describe('main/notifications', () => {
value: 'win32',
});
await displayMention(
await NotificationManager.displayMention(
'test',
'test body',
{id: 'channel_id'},
'team_id',
'http://server-1.com/team_id/channel_id',
false,
{id: 1},
{},
{id: 1} as WebContents,
{soundName: ''},
);
expect(currentNotifications.has('team_id:channel_id')).toBe(true);
// convert to any to access private field
const mentionsPerChannel = (NotificationManager as any).mentionsPerChannel;
expect(mentionsPerChannel.has('team_id:channel_id')).toBe(true);
const existingMention = currentNotifications.get('team_id:channel_id');
currentNotifications.delete = jest.fn();
await displayMention(
const existingMention = mentionsPerChannel.get('team_id:channel_id');
mentionsPerChannel.delete = jest.fn();
await NotificationManager.displayMention(
'test',
'test body 2',
{id: 'channel_id'},
'team_id',
'http://server-1.com/team_id/channel_id',
false,
{id: 1},
{},
{id: 1} as WebContents,
{soundName: ''},
);
expect(currentNotifications.delete).toHaveBeenCalled();
expect(existingMention.close).toHaveBeenCalled();
expect(mentionsPerChannel.delete).toHaveBeenCalled();
expect(existingMention?.close).toHaveBeenCalled();
Object.defineProperty(process, 'platform', {
value: originalPlatform,
@ -254,18 +274,18 @@ describe('main/notifications', () => {
});
it('should switch view when clicking on notification', async () => {
await displayMention(
await NotificationManager.displayMention(
'click_test',
'mention_click_body',
{id: 'channel_id'},
'team_id',
'http://server-1.com/team_id/channel_id',
false,
{id: 1, send: jest.fn()},
{},
{id: 1, send: jest.fn()} as unknown as WebContents,
{soundName: ''},
);
const mention = mentions.find((m) => m.body === 'mention_click_body');
mention.value.click();
mention?.value.click();
expect(MainWindow.show).toHaveBeenCalled();
expect(ViewManager.showById).toHaveBeenCalledWith('server_id');
});
@ -275,15 +295,15 @@ describe('main/notifications', () => {
Object.defineProperty(process, 'platform', {
value: 'linux',
});
await displayMention(
await NotificationManager.displayMention(
'click_test',
'mention_click_body',
{id: 'channel_id'},
'team_id',
'http://server-1.com/team_id/channel_id',
false,
{id: 1, send: jest.fn()},
{},
{id: 1, send: jest.fn()} as unknown as WebContents,
{soundName: ''},
);
Object.defineProperty(process, 'platform', {
value: originalPlatform,
@ -293,21 +313,23 @@ describe('main/notifications', () => {
it('linux/windows - should flash frame when config item is set', async () => {
Config.notifications = {
flashWindow: true,
flashWindow: 1,
bounceIcon: false,
bounceIconType: 'informational',
};
const originalPlatform = process.platform;
Object.defineProperty(process, 'platform', {
value: 'linux',
});
await displayMention(
await NotificationManager.displayMention(
'click_test',
'mention_click_body',
{id: 'channel_id'},
'team_id',
'http://server-1.com/team_id/channel_id',
false,
{id: 1, send: jest.fn()},
{},
{id: 1, send: jest.fn()} as unknown as WebContents,
{soundName: ''},
);
Object.defineProperty(process, 'platform', {
value: originalPlatform,
@ -320,15 +342,15 @@ describe('main/notifications', () => {
Object.defineProperty(process, 'platform', {
value: 'darwin',
});
await displayMention(
await NotificationManager.displayMention(
'click_test',
'mention_click_body',
{id: 'channel_id'},
'team_id',
'http://server-1.com/team_id/channel_id',
false,
{id: 1, send: jest.fn()},
{},
{id: 1, send: jest.fn()} as unknown as WebContents,
{soundName: ''},
);
Object.defineProperty(process, 'platform', {
value: originalPlatform,
@ -340,20 +362,21 @@ describe('main/notifications', () => {
Config.notifications = {
bounceIcon: true,
bounceIconType: 'critical',
flashWindow: 0,
};
const originalPlatform = process.platform;
Object.defineProperty(process, 'platform', {
value: 'darwin',
});
await displayMention(
await NotificationManager.displayMention(
'click_test',
'mention_click_body',
{id: 'channel_id'},
'team_id',
'http://server-1.com/team_id/channel_id',
false,
{id: 1, send: jest.fn()},
{},
{id: 1, send: jest.fn()} as unknown as WebContents,
{soundName: ''},
);
Object.defineProperty(process, 'platform', {
value: originalPlatform,
@ -365,26 +388,26 @@ describe('main/notifications', () => {
describe('displayDownloadCompleted', () => {
beforeEach(() => {
Notification.isSupported.mockImplementation(() => true);
getFocusAssist.mockReturnValue({value: false});
getFocusAssist.mockReturnValue({value: 0, name: ''});
getDarwinDoNotDisturb.mockReturnValue(false);
});
it('should open file when clicked', () => {
getDarwinDoNotDisturb.mockReturnValue(false);
localizeMessage.mockReturnValue('test_filename');
displayDownloadCompleted(
NotificationManager.displayDownloadCompleted(
'test_filename',
'/path/to/file',
'server_name',
);
const mention = mentions.find((m) => m.body.includes('test_filename'));
mention.value.click();
mention?.value.click();
expect(shell.showItemInFolder).toHaveBeenCalledWith('/path/to/file');
});
});
describe('getLinuxDoNotDisturb', () => {
let originalPlatform;
let originalPlatform: NodeJS.Platform;
beforeAll(() => {
originalPlatform = process.platform;
Object.defineProperty(process, 'platform', {
@ -399,7 +422,7 @@ describe('main/notifications', () => {
});
it('should return false', () => {
cp.execSync.mockReturnValue('true');
cp.execSync.mockReturnValue(Buffer.from('true'));
expect(getLinuxDoNotDisturb()).toBe(false);
});
@ -411,7 +434,7 @@ describe('main/notifications', () => {
});
it('should return true', () => {
cp.execSync.mockReturnValue('false');
cp.execSync.mockReturnValue(Buffer.from('false'));
expect(getLinuxDoNotDisturb()).toBe(true);
});
});

View file

@ -21,134 +21,157 @@ import {NewVersionNotification, UpgradeNotification} from './Upgrade';
import getLinuxDoNotDisturb from './dnd-linux';
import getWindowsDoNotDisturb from './dnd-windows';
export const currentNotifications = new Map();
const log = new Logger('Notifications');
export async function displayMention(title: string, body: string, channel: {id: string}, teamId: string, url: string, silent: boolean, webcontents: Electron.WebContents, data: MentionData) {
log.debug('displayMention', {title, body, channel, teamId, url, silent, data});
class NotificationManager {
private mentionsPerChannel: Map<string, Mention> = new Map();
private allActiveNotifications: Map<string, Notification> = new Map();
private upgradeNotification?: NewVersionNotification;
private restartToUpgradeNotification?: UpgradeNotification;
if (!Notification.isSupported()) {
log.error('notification not supported');
return;
}
public async displayMention(title: string, body: string, channel: {id: string}, teamId: string, url: string, silent: boolean, webcontents: Electron.WebContents, data: MentionData) {
log.debug('displayMention', {title, body, channel, teamId, url, silent, data});
if (getDoNotDisturb()) {
return;
}
if (!Notification.isSupported()) {
log.error('notification not supported');
return;
}
const view = ViewManager.getViewByWebContentsId(webcontents.id);
if (!view) {
return;
}
const serverName = view.view.server.name;
if (getDoNotDisturb()) {
return;
}
const options = {
title: `${serverName}: ${title}`,
body,
silent,
data,
};
const view = ViewManager.getViewByWebContentsId(webcontents.id);
if (!view) {
return;
}
const serverName = view.view.server.name;
if (!await PermissionsManager.doPermissionRequest(webcontents.id, 'notifications', view.view.server.url.toString())) {
return;
}
const options = {
title: `${serverName}: ${title}`,
body,
silent,
data,
};
const mention = new Mention(options, channel, teamId);
const mentionKey = `${mention.teamId}:${mention.channel.id}`;
if (!await PermissionsManager.doPermissionRequest(webcontents.id, 'notifications', view.view.server.url.toString())) {
return;
}
mention.on('show', () => {
log.debug('displayMention.show');
const mention = new Mention(options, channel, teamId);
const mentionKey = `${mention.teamId}:${mention.channel.id}`;
this.allActiveNotifications.set(mention.uId, mention);
// On Windows, manually dismiss notifications from the same channel and only show the latest one
if (process.platform === 'win32') {
if (currentNotifications.has(mentionKey)) {
log.debug(`close ${mentionKey}`);
currentNotifications.get(mentionKey).close();
currentNotifications.delete(mentionKey);
mention.on('show', () => {
log.debug('displayMention.show');
// On Windows, manually dismiss notifications from the same channel and only show the latest one
if (process.platform === 'win32') {
if (this.mentionsPerChannel.has(mentionKey)) {
log.debug(`close ${mentionKey}`);
this.mentionsPerChannel.get(mentionKey)?.close();
this.mentionsPerChannel.delete(mentionKey);
}
this.mentionsPerChannel.set(mentionKey, mention);
}
currentNotifications.set(mentionKey, mention);
const notificationSound = mention.getNotificationSound();
if (notificationSound) {
MainWindow.sendToRenderer(PLAY_SOUND, notificationSound);
}
flashFrame(true);
});
mention.on('click', () => {
log.debug('notification click', serverName, mention);
this.allActiveNotifications.delete(mention.uId);
MainWindow.show();
if (serverName) {
ViewManager.showById(view.id);
webcontents.send('notification-clicked', {channel, teamId, url});
}
});
mention.on('close', () => {
this.allActiveNotifications.delete(mention.uId);
});
mention.on('failed', () => {
this.allActiveNotifications.delete(mention.uId);
});
mention.show();
}
public displayDownloadCompleted(fileName: string, path: string, serverName: string) {
log.debug('displayDownloadCompleted', {fileName, path, serverName});
if (!Notification.isSupported()) {
log.error('notification not supported');
return;
}
const notificationSound = mention.getNotificationSound();
if (notificationSound) {
MainWindow.sendToRenderer(PLAY_SOUND, notificationSound);
if (getDoNotDisturb()) {
return;
}
flashFrame(true);
});
mention.on('click', () => {
log.debug('notification click', serverName, mention);
MainWindow.show();
if (serverName) {
ViewManager.showById(view.id);
webcontents.send('notification-clicked', {channel, teamId, url});
const download = new DownloadNotification(fileName, serverName);
this.allActiveNotifications.set(download.uId, download);
download.on('show', () => {
flashFrame(true);
});
download.on('click', () => {
shell.showItemInFolder(path.normalize());
this.allActiveNotifications.delete(download.uId);
});
download.on('close', () => {
this.allActiveNotifications.delete(download.uId);
});
download.on('failed', () => {
this.allActiveNotifications.delete(download.uId);
});
download.show();
}
public displayUpgrade(version: string, handleUpgrade: () => void): void {
if (!Notification.isSupported()) {
log.error('notification not supported');
return;
}
if (getDoNotDisturb()) {
return;
}
});
mention.show();
}
export function displayDownloadCompleted(fileName: string, path: string, serverName: string) {
log.debug('displayDownloadCompleted', {fileName, path, serverName});
if (!Notification.isSupported()) {
log.error('notification not supported');
return;
if (this.upgradeNotification) {
this.upgradeNotification.close();
}
this.upgradeNotification = new NewVersionNotification();
this.upgradeNotification.on('click', () => {
log.info(`User clicked to upgrade to ${version}`);
handleUpgrade();
});
this.upgradeNotification.show();
}
if (getDoNotDisturb()) {
return;
public displayRestartToUpgrade(version: string, handleUpgrade: () => void): void {
if (!Notification.isSupported()) {
log.error('notification not supported');
return;
}
if (getDoNotDisturb()) {
return;
}
this.restartToUpgradeNotification = new UpgradeNotification();
this.restartToUpgradeNotification.on('click', () => {
log.info(`User requested perform the upgrade now to ${version}`);
handleUpgrade();
});
this.restartToUpgradeNotification.show();
}
const download = new DownloadNotification(fileName, serverName);
download.on('show', () => {
flashFrame(true);
});
download.on('click', () => {
shell.showItemInFolder(path.normalize());
});
download.show();
}
let upgrade: NewVersionNotification;
export function displayUpgrade(version: string, handleUpgrade: () => void): void {
if (!Notification.isSupported()) {
log.error('notification not supported');
return;
}
if (getDoNotDisturb()) {
return;
}
if (upgrade) {
upgrade.close();
}
upgrade = new NewVersionNotification();
upgrade.on('click', () => {
log.info(`User clicked to upgrade to ${version}`);
handleUpgrade();
});
upgrade.show();
}
let restartToUpgrade;
export function displayRestartToUpgrade(version: string, handleUpgrade: () => void): void {
if (!Notification.isSupported()) {
log.error('notification not supported');
return;
}
if (getDoNotDisturb()) {
return;
}
restartToUpgrade = new UpgradeNotification();
restartToUpgrade.on('click', () => {
log.info(`User requested perform the upgrade now to ${version}`);
handleUpgrade();
});
restartToUpgrade.show();
}
function getDoNotDisturb() {
@ -177,3 +200,6 @@ function flashFrame(flash: boolean) {
app.dock.bounce(Config.notifications.bounceIconType);
}
}
const notificationManager = new NotificationManager();
export default notificationManager;