Fix notifications not working (#2865)
* Fix notifications not working * Transform into a manager * Remove test accessor
This commit is contained in:
parent
02261c9de3
commit
9aec7db821
|
@ -96,7 +96,8 @@
|
|||
"src/main/**/*.ts"
|
||||
],
|
||||
"testMatch": [
|
||||
"**/src/**/*.test.js"
|
||||
"**/src/**/*.test.js",
|
||||
"**/src/**/*.test.ts"
|
||||
],
|
||||
"testPathIgnorePatterns": [
|
||||
"/node_modules/",
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
|
@ -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 => {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = () => {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue