diff --git a/js/notifications.js b/js/notifications.js index 1244feb84353..24997ea0ab71 100644 --- a/js/notifications.js +++ b/js/notifications.js @@ -10,7 +10,6 @@ // eslint-disable-next-line func-names (function() { window.Whisper = window.Whisper || {}; - const { Settings } = Signal.Types; // The keys and values don't match here. This is because the values correspond to old // setting names. In the future, we may wish to migrate these to match. @@ -76,13 +75,11 @@ const isAppFocused = window.isActive(); const isAudioNotificationEnabled = storage.get('audio-notification') || false; - const isAudioNotificationSupported = Settings.isAudioNotificationSupported(); const userSetting = this.getUserSetting(); const status = Signal.Notifications.getStatus({ isAppFocused, isAudioNotificationEnabled, - isAudioNotificationSupported, isEnabled, hasNotifications: Boolean(this.notificationData), userSetting, @@ -157,7 +154,6 @@ } this.lastNotification = window.Signal.Services.notify({ - platform: window.platform, title: notificationTitle, icon: notificationIconUrl, message: notificationMessage, diff --git a/sounds/notification.ogg b/sounds/notification.ogg new file mode 100755 index 000000000000..93b3a0144e69 Binary files /dev/null and b/sounds/notification.ogg differ diff --git a/ts/notifications/getStatus.ts b/ts/notifications/getStatus.ts index 75faaefad881..2d83e54e7c5c 100644 --- a/ts/notifications/getStatus.ts +++ b/ts/notifications/getStatus.ts @@ -1,7 +1,6 @@ interface Environment { isAppFocused: boolean; isAudioNotificationEnabled: boolean; - isAudioNotificationSupported: boolean; isEnabled: boolean; hasNotifications: boolean; userSetting: UserSetting; @@ -26,7 +25,6 @@ type Type = export const getStatus = ({ isAppFocused, isAudioNotificationEnabled, - isAudioNotificationSupported, isEnabled, hasNotifications, userSetting, @@ -51,15 +49,10 @@ export const getStatus = ({ return 'ok'; })(); - const shouldPlayNotificationSound = - isAudioNotificationSupported && isAudioNotificationEnabled; - const shouldShowNotifications = type === 'ok'; - const shouldClearNotifications = type === 'appIsFocused'; - return { - shouldClearNotifications, - shouldPlayNotificationSound, - shouldShowNotifications, + shouldClearNotifications: type === 'appIsFocused', + shouldPlayNotificationSound: isAudioNotificationEnabled, + shouldShowNotifications: type === 'ok', type, }; }; diff --git a/ts/services/notify.ts b/ts/services/notify.ts index 2d490e2eddb6..974aa14ec0ab 100644 --- a/ts/services/notify.ts +++ b/ts/services/notify.ts @@ -1,3 +1,10 @@ +import { Sound } from '../util/Sound'; +import { + AudioNotificationSupport, + getAudioNotificationSupport, +} from '../types/Settings'; +import * as OS from '../OS'; + function filter(text: string) { return (text || '') .replace(/&/g, '&') @@ -8,7 +15,6 @@ function filter(text: string) { } type NotificationType = { - platform: string; icon: string; message: string; onNotificationClick: () => void; @@ -17,18 +23,27 @@ type NotificationType = { }; export function notify({ - platform, icon, message, onNotificationClick, silent, title, }: NotificationType): Notification { + const audioNotificationSupport = getAudioNotificationSupport(); + const notification = new window.Notification(title, { - body: platform === 'linux' ? filter(message) : message, + body: OS.isLinux() ? filter(message) : message, icon, - silent, + silent: + silent || audioNotificationSupport !== AudioNotificationSupport.Native, }); notification.onclick = onNotificationClick; + + if (!silent && audioNotificationSupport === AudioNotificationSupport.Custom) { + // We kick off the sound to be played. No neet to await it. + // tslint:disable-next-line no-floating-promises + new Sound({ src: 'sounds/notification.ogg' }).play(); + } + return notification; } diff --git a/ts/state/ducks/calling.ts b/ts/state/ducks/calling.ts index d0eb95de6f63..f1f45c8a09c0 100644 --- a/ts/state/ducks/calling.ts +++ b/ts/state/ducks/calling.ts @@ -277,7 +277,6 @@ async function showCallNotification(callDetails: CallDetailsType) { } const { title, isVideoCall } = callDetails; notify({ - platform: window.platform, title, icon: isVideoCall ? 'images/icons/v2/video-solid-24.svg' diff --git a/ts/test/types/Settings_test.ts b/ts/test/types/Settings_test.ts index 3772b5abb3a0..f5e684d5e917 100644 --- a/ts/test/types/Settings_test.ts +++ b/ts/test/types/Settings_test.ts @@ -5,251 +5,145 @@ import { assert } from 'chai'; import * as Settings from '../../../ts/types/Settings'; describe('Settings', () => { - const sandbox = Sinon.createSandbox(); + let sandbox: Sinon.SinonSandbox; + + beforeEach(() => { + sandbox = Sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('getAudioNotificationSupport', () => { + it('returns native support on macOS', () => { + sandbox.stub(process, 'platform').value('darwin'); + assert.strictEqual( + Settings.getAudioNotificationSupport(), + Settings.AudioNotificationSupport.Native + ); + }); + + it('returns no support on Windows 7', () => { + sandbox.stub(process, 'platform').value('win32'); + sandbox.stub(os, 'release').returns('7.0.0'); + assert.strictEqual( + Settings.getAudioNotificationSupport(), + Settings.AudioNotificationSupport.None + ); + }); + + it('returns native support on Windows 8', () => { + sandbox.stub(process, 'platform').value('win32'); + sandbox.stub(os, 'release').returns('8.0.0'); + assert.strictEqual( + Settings.getAudioNotificationSupport(), + Settings.AudioNotificationSupport.Native + ); + }); + + it('returns custom support on Linux', () => { + sandbox.stub(process, 'platform').value('linux'); + assert.strictEqual( + Settings.getAudioNotificationSupport(), + Settings.AudioNotificationSupport.Custom + ); + }); + }); describe('isAudioNotificationSupported', () => { - context('on macOS', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('darwin'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return true', () => { - assert.isTrue(Settings.isAudioNotificationSupported()); - }); + it('returns true on macOS', () => { + sandbox.stub(process, 'platform').value('darwin'); + assert.isTrue(Settings.isAudioNotificationSupported()); }); - context('on Windows', () => { - context('version 7', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('win32'); - sandbox.stub(os, 'release').returns('7.0.0'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return false', () => { - assert.isFalse(Settings.isAudioNotificationSupported()); - }); - }); - - context('version 8+', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('win32'); - sandbox.stub(os, 'release').returns('8.0.0'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return true', () => { - assert.isTrue(Settings.isAudioNotificationSupported()); - }); - }); + it('returns false on Windows 7', () => { + sandbox.stub(process, 'platform').value('win32'); + sandbox.stub(os, 'release').returns('7.0.0'); + assert.isFalse(Settings.isAudioNotificationSupported()); }); - context('on Linux', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('linux'); - }); + it('returns true on Windows 8', () => { + sandbox.stub(process, 'platform').value('win32'); + sandbox.stub(os, 'release').returns('8.0.0'); + assert.isTrue(Settings.isAudioNotificationSupported()); + }); - afterEach(() => { - sandbox.restore(); - }); - - it('should return false', () => { - assert.isFalse(Settings.isAudioNotificationSupported()); - }); + it('returns true on Linux', () => { + sandbox.stub(process, 'platform').value('linux'); + assert.isTrue(Settings.isAudioNotificationSupported()); }); }); describe('isNotificationGroupingSupported', () => { - context('on macOS', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('darwin'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return true', () => { - assert.isTrue(Settings.isNotificationGroupingSupported()); - }); + it('returns true on macOS', () => { + sandbox.stub(process, 'platform').value('darwin'); + assert.isTrue(Settings.isNotificationGroupingSupported()); }); - context('on Windows', () => { - context('version 7', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('win32'); - sandbox.stub(os, 'release').returns('7.0.0'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return false', () => { - assert.isFalse(Settings.isNotificationGroupingSupported()); - }); - }); - - context('version 8+', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('win32'); - sandbox.stub(os, 'release').returns('8.0.0'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return true', () => { - assert.isTrue(Settings.isNotificationGroupingSupported()); - }); - }); + it('returns true on Windows 7', () => { + sandbox.stub(process, 'platform').value('win32'); + sandbox.stub(os, 'release').returns('7.0.0'); + assert.isFalse(Settings.isNotificationGroupingSupported()); }); - context('on Linux', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('linux'); - }); + it('returns true on Windows 8', () => { + sandbox.stub(process, 'platform').value('win32'); + sandbox.stub(os, 'release').returns('8.0.0'); + assert.isTrue(Settings.isNotificationGroupingSupported()); + }); - afterEach(() => { - sandbox.restore(); - }); - - it('should return true', () => { - assert.isTrue(Settings.isNotificationGroupingSupported()); - }); + it('returns true on Linux', () => { + sandbox.stub(process, 'platform').value('linux'); + assert.isTrue(Settings.isNotificationGroupingSupported()); }); }); + describe('isHideMenuBarSupported', () => { - context('on macOS', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('darwin'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return false', () => { - assert.isFalse(Settings.isHideMenuBarSupported()); - }); + it('returns false on macOS', () => { + sandbox.stub(process, 'platform').value('darwin'); + assert.isFalse(Settings.isHideMenuBarSupported()); }); - context('on Windows', () => { - context('version 7', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('win32'); - sandbox.stub(os, 'release').returns('7.0.0'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return true', () => { - assert.isTrue(Settings.isHideMenuBarSupported()); - }); - }); - - context('version 8+', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('win32'); - sandbox.stub(os, 'release').returns('8.0.0'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return true', () => { - assert.isTrue(Settings.isHideMenuBarSupported()); - }); - }); + it('returns true on Windows 7', () => { + sandbox.stub(process, 'platform').value('win32'); + sandbox.stub(os, 'release').returns('7.0.0'); + assert.isTrue(Settings.isHideMenuBarSupported()); }); - context('on Linux', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('linux'); - }); + it('returns true on Windows 8', () => { + sandbox.stub(process, 'platform').value('win32'); + sandbox.stub(os, 'release').returns('8.0.0'); + assert.isTrue(Settings.isHideMenuBarSupported()); + }); - afterEach(() => { - sandbox.restore(); - }); - - it('should return true', () => { - assert.isTrue(Settings.isHideMenuBarSupported()); - }); + it('returns true on Linux', () => { + sandbox.stub(process, 'platform').value('linux'); + assert.isTrue(Settings.isHideMenuBarSupported()); }); }); + describe('isDrawAttentionSupported', () => { - context('on macOS', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('darwin'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return false', () => { - assert.isFalse(Settings.isDrawAttentionSupported()); - }); + it('returns false on macOS', () => { + sandbox.stub(process, 'platform').value('darwin'); + assert.isFalse(Settings.isDrawAttentionSupported()); }); - context('on Windows', () => { - context('version 7', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('win32'); - sandbox.stub(os, 'release').returns('7.0.0'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return true', () => { - assert.isTrue(Settings.isDrawAttentionSupported()); - }); - }); - - context('version 8+', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('win32'); - sandbox.stub(os, 'release').returns('8.0.0'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return true', () => { - assert.isTrue(Settings.isDrawAttentionSupported()); - }); - }); + it('returns true on Windows 7', () => { + sandbox.stub(process, 'platform').value('win32'); + sandbox.stub(os, 'release').returns('7.0.0'); + assert.isTrue(Settings.isDrawAttentionSupported()); }); - context('on Linux', () => { - beforeEach(() => { - sandbox.stub(process, 'platform').value('linux'); - }); + it('returns true on Windows 8', () => { + sandbox.stub(process, 'platform').value('win32'); + sandbox.stub(os, 'release').returns('8.0.0'); + assert.isTrue(Settings.isDrawAttentionSupported()); + }); - afterEach(() => { - sandbox.restore(); - }); - - it('should return true', () => { - assert.isTrue(Settings.isDrawAttentionSupported()); - }); + it('returns true on Linux', () => { + sandbox.stub(process, 'platform').value('linux'); + assert.isTrue(Settings.isDrawAttentionSupported()); }); }); }); diff --git a/ts/types/Settings.ts b/ts/types/Settings.ts index 0d6ca05d9270..71a738e60553 100644 --- a/ts/types/Settings.ts +++ b/ts/types/Settings.ts @@ -2,8 +2,23 @@ import * as OS from '../OS'; const MIN_WINDOWS_VERSION = '8.0.0'; -export const isAudioNotificationSupported = () => - OS.isWindows(MIN_WINDOWS_VERSION) || OS.isMacOS(); +export enum AudioNotificationSupport { + None, + Native, + Custom, +} + +export function getAudioNotificationSupport(): AudioNotificationSupport { + if (OS.isWindows(MIN_WINDOWS_VERSION) || OS.isMacOS()) { + return AudioNotificationSupport.Native; + } else if (OS.isLinux()) { + return AudioNotificationSupport.Custom; + } + return AudioNotificationSupport.None; +} + +export const isAudioNotificationSupported = (): boolean => + getAudioNotificationSupport() !== AudioNotificationSupport.None; // Using `Notification::tag` has a bug on Windows 7: // https://github.com/electron/electron/issues/11189