diff --git a/src/main/notifications/dnd-windows.test.js b/src/main/notifications/dnd-windows.test.js new file mode 100644 index 00000000..dffb4562 --- /dev/null +++ b/src/main/notifications/dnd-windows.test.js @@ -0,0 +1,62 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {getFocusAssist, isPriority} from 'windows-focus-assist'; + +import doNotDisturb from './dnd-windows'; + +jest.mock('windows-focus-assist', () => ({ + getFocusAssist: jest.fn(), + isPriority: jest.fn(), +})); + +describe('main/notifications/dnd-windows', () => { + it('should return false if unsupported, failed, or off', () => { + const originalPlatform = process.platform; + Object.defineProperty(process, 'platform', { + value: 'win32', + }); + + getFocusAssist.mockReturnValue({value: 0}); + expect(doNotDisturb()).toBe(false); + getFocusAssist.mockReturnValue({value: -1}); + expect(doNotDisturb()).toBe(false); + getFocusAssist.mockReturnValue({value: -2}); + expect(doNotDisturb()).toBe(false); + + Object.defineProperty(process, 'platform', { + value: originalPlatform, + }); + }); + + it('should return true if alarms only', () => { + const originalPlatform = process.platform; + Object.defineProperty(process, 'platform', { + value: 'win32', + }); + + getFocusAssist.mockReturnValue({value: 2}); + expect(doNotDisturb()).toBe(true); + + Object.defineProperty(process, 'platform', { + value: originalPlatform, + }); + }); + + it('should check if the app is priority if priority only', () => { + const originalPlatform = process.platform; + Object.defineProperty(process, 'platform', { + value: 'win32', + }); + + getFocusAssist.mockReturnValue({value: 1}); + isPriority.mockReturnValue({value: 0}); + expect(doNotDisturb()).toBe(true); + isPriority.mockReturnValue({value: 1}); + expect(doNotDisturb()).toBe(false); + + Object.defineProperty(process, 'platform', { + value: originalPlatform, + }); + }); +}); diff --git a/src/main/notifications/dnd-windows.ts b/src/main/notifications/dnd-windows.ts index 19e6afb7..d12e9aa4 100644 --- a/src/main/notifications/dnd-windows.ts +++ b/src/main/notifications/dnd-windows.ts @@ -20,13 +20,9 @@ function getWindowsDoNotDisturb() { case 2: return true; case 1: - return !isPriority('Mattermost.Desktop'); - case 0: - case -1: - case -2: - return false; + return !(isPriority('Mattermost.Desktop').value); default: - return focusAssistValue; + return false; } } diff --git a/src/main/notifications/index.ts b/src/main/notifications/index.ts index 4c042f24..e7939c3c 100644 --- a/src/main/notifications/index.ts +++ b/src/main/notifications/index.ts @@ -92,34 +92,48 @@ class NotificationManager { log.debug('notification timeout', serverName, mention.uId); resolve({status: 'error', reason: 'notification_timeout'}); }, 10000); + let failed = false; mention.on('show', () => { - log.debug('displayMention.show', serverName, mention.uId); + // Ensure the failed event isn't also called, if it is we should resolve using its method + setTimeout(() => { + if (!failed) { + log.debug('displayMention.show', serverName, mention.uId); - // On Windows, manually dismiss notifications from the same channel and only show the latest one - if (process.platform === 'win32') { - const mentionKey = `${mention.teamId}:${mention.channelId}`; - if (this.mentionsPerChannel.has(mentionKey)) { - log.debug(`close ${mentionKey}`); - this.mentionsPerChannel.get(mentionKey)?.close(); - this.mentionsPerChannel.delete(mentionKey); + // On Windows, manually dismiss notifications from the same channel and only show the latest one + if (process.platform === 'win32') { + const mentionKey = `${mention.teamId}:${mention.channelId}`; + if (this.mentionsPerChannel.has(mentionKey)) { + log.debug(`close ${mentionKey}`); + this.mentionsPerChannel.get(mentionKey)?.close(); + this.mentionsPerChannel.delete(mentionKey); + } + this.mentionsPerChannel.set(mentionKey, mention); + } + const notificationSound = mention.getNotificationSound(); + if (notificationSound) { + MainWindow.sendToRenderer(PLAY_SOUND, notificationSound); + } + flashFrame(true); + clearTimeout(timeout); + resolve({status: 'success'}); } - this.mentionsPerChannel.set(mentionKey, mention); - } - const notificationSound = mention.getNotificationSound(); - if (notificationSound) { - MainWindow.sendToRenderer(PLAY_SOUND, notificationSound); - } - flashFrame(true); - clearTimeout(timeout); - resolve({status: 'success'}); + }, 0); }); mention.on('failed', (_, error) => { + failed = true; this.allActiveNotifications.delete(mention.uId); clearTimeout(timeout); - log.error('notification failed to show', serverName, mention.uId, error); - resolve({status: 'error', reason: 'electron_notification_failed', data: error}); + + // Special case for Windows - means that notifications are disabled at the OS level + if (error.includes('HRESULT:-2143420143')) { + log.warn('notifications disabled in Windows settings'); + resolve({status: 'not_sent', reason: 'windows_permissions_denied'}); + } else { + log.error('notification failed to show', serverName, mention.uId, error); + resolve({status: 'error', reason: 'electron_notification_failed', data: error}); + } }); mention.show(); });