From b935ab31fcdd91e52bc03f002e9b69680d431ebc Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Wed, 13 Aug 2025 18:44:15 +0530 Subject: [PATCH 1/5] fix: use custom notification sound on incoming message --- .../notification/useNewMessageNotification.ts | 14 +++++++------- .../CustomSoundProvider/CustomSoundProvider.tsx | 2 ++ packages/ui-contexts/src/CustomSoundContext.ts | 2 ++ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/meteor/client/hooks/notification/useNewMessageNotification.ts b/apps/meteor/client/hooks/notification/useNewMessageNotification.ts index 36b94568a27a5..8f1442a5424c6 100644 --- a/apps/meteor/client/hooks/notification/useNewMessageNotification.ts +++ b/apps/meteor/client/hooks/notification/useNewMessageNotification.ts @@ -2,6 +2,8 @@ import type { AtLeast, ISubscription } from '@rocket.chat/core-typings'; import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; import { useCustomSound } from '@rocket.chat/ui-contexts'; +import { Subscriptions } from '../../stores'; + export const useNewMessageNotification = () => { const { notificationSounds } = useCustomSound(); @@ -9,14 +11,12 @@ export const useNewMessageNotification = () => { if (!sub || sub.audioNotificationValue === 'none') { return; } - // TODO: Fix this - Room Notifications Preferences > sound > desktop is not working. - // plays the user notificationSound preference - // if (sub.audioNotificationValue && sub.audioNotificationValue !== '0') { - // void CustomSounds.play(sub.audioNotificationValue, { - // volume: Number((notificationsSoundVolume / 100).toPrecision(2)), - // }); - // } + const subscription = Subscriptions.state.find((record) => record.rid === sub.rid); + + if (subscription?.audioNotificationValue) { + return notificationSounds.playNewMessageCustom(subscription.audioNotificationValue); + } notificationSounds.playNewMessage(); }); diff --git a/apps/meteor/client/providers/CustomSoundProvider/CustomSoundProvider.tsx b/apps/meteor/client/providers/CustomSoundProvider/CustomSoundProvider.tsx index 82c12f4febb83..e06dd7a354a19 100644 --- a/apps/meteor/client/providers/CustomSoundProvider/CustomSoundProvider.tsx +++ b/apps/meteor/client/providers/CustomSoundProvider/CustomSoundProvider.tsx @@ -76,6 +76,8 @@ const CustomSoundProvider = ({ children }: CustomSoundProviderProps) => { const notificationSounds = { playNewRoom: () => play(newRoomNotification, { loop: false, volume: formatVolume(notificationsSoundVolume) }), playNewMessage: () => play(newMessageNotification, { loop: false, volume: formatVolume(notificationsSoundVolume) }), + playNewMessageCustom: (soundId: ICustomSound['_id']) => + play(soundId, { loop: false, volume: formatVolume(notificationsSoundVolume) }), playNewMessageLoop: () => play(newMessageNotification, { loop: true, volume: formatVolume(notificationsSoundVolume) }), stopNewRoom: () => stop(newRoomNotification), stopNewMessage: () => stop(newMessageNotification), diff --git a/packages/ui-contexts/src/CustomSoundContext.ts b/packages/ui-contexts/src/CustomSoundContext.ts index 5c3838ba47c6a..a1a0d84d9e69f 100644 --- a/packages/ui-contexts/src/CustomSoundContext.ts +++ b/packages/ui-contexts/src/CustomSoundContext.ts @@ -34,6 +34,7 @@ export type CustomSoundContextValue = { playNewMessageLoop: () => void; stopNewRoom: () => void; stopNewMessage: () => void; + playNewMessageCustom: (soundId: ICustomSound['_id']) => void; }; list: ICustomSound[]; }; @@ -63,6 +64,7 @@ export const CustomSoundContext = createContext({ playNewMessageLoop: () => undefined, stopNewRoom: () => undefined, stopNewMessage: () => undefined, + playNewMessageCustom: () => undefined, }, list: [], }); From 3d869ece3a8de43f892f68a7dceb46212e7eed96 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Thu, 14 Aug 2025 04:11:51 +0530 Subject: [PATCH 2/5] add e2e tests --- .../tests/e2e/notification-sounds.spec.ts | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 apps/meteor/tests/e2e/notification-sounds.spec.ts diff --git a/apps/meteor/tests/e2e/notification-sounds.spec.ts b/apps/meteor/tests/e2e/notification-sounds.spec.ts new file mode 100644 index 0000000000000..eba0a3a6eb86a --- /dev/null +++ b/apps/meteor/tests/e2e/notification-sounds.spec.ts @@ -0,0 +1,144 @@ +import { Users } from './fixtures/userStates'; +import { HomeChannel } from './page-objects'; +import { createTargetChannelAndReturnFullRoom, deleteRoom, setUserPreferences } from './utils'; +import { test, expect } from './utils/test'; + +declare global { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface Window { + __audioCalls: { src?: string; played?: boolean }; + } +} + +test.use({ storageState: Users.admin.state }); + +test.describe.serial('Notification Sounds', () => { + let targetChannel: string; + let targetChannelId: string; + let poHomeChannel: HomeChannel; + + test.beforeAll(async ({ api }) => { + const { channel } = await createTargetChannelAndReturnFullRoom(api, { + members: [Users.admin.data.username, Users.user1.data.username], + }); + targetChannel = channel.name as string; + targetChannelId = channel._id; + }); + + test.afterAll(async ({ api }) => { + await deleteRoom(api, targetChannel); + }); + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + + await page.goto(`/channel/${targetChannel}`); + + await page.evaluate(() => { + const OriginalAudio = window.Audio; + window.__audioCalls = {} as { src?: string; played?: boolean }; + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore mock Audio constructor + window.Audio = function (src: string | undefined) { + if (!src) { + return new OriginalAudio(); + } + + window.__audioCalls = { src, played: false }; + const audioObj = new OriginalAudio(src); + const originalPlay = audioObj.play.bind(audioObj); + audioObj.play = () => { + window.__audioCalls.played = true; + return originalPlay(); + }; + return audioObj; + }; + }); + }); + + test('should play default notification sounds', async ({ page, browser }) => { + const user1Page = await browser.newPage({ storageState: Users.user1.state }); + await user1Page.goto(`/channel/${targetChannel}`); + const user1PoHomeChannel = new HomeChannel(user1Page); + await user1PoHomeChannel.content.waitForChannel(); + + await poHomeChannel.sidenav.sidebarHomeAction.click(); + + await user1PoHomeChannel.content.sendMessage(`Hello @${Users.admin.data.username} from User 1`); + + await page.waitForTimeout(100); // wait for the sound to play + + const audioCalls = await page.evaluate(() => window.__audioCalls); + expect(audioCalls).toHaveProperty('src'); + expect(audioCalls.src).toContain('chime'); + expect(audioCalls.played).toBe(true); + + await user1Page.close(); + }); + + test.describe('Notification sound preferences', () => { + test.beforeAll(async ({ api }) => { + await setUserPreferences(api, { + newMessageNotification: 'ringtone', + }); + }); + + test.afterAll(async ({ api }) => { + await setUserPreferences(api, { + newMessageNotification: 'chime', + }); + }); + + test('should play notification sound based on user preferences', async ({ page, browser }) => { + const user1Page = await browser.newPage({ storageState: Users.user1.state }); + await user1Page.goto(`/channel/${targetChannel}`); + const user1PoHomeChannel = new HomeChannel(user1Page); + await user1PoHomeChannel.content.waitForChannel(); + + await poHomeChannel.sidenav.sidebarHomeAction.click(); + + await user1PoHomeChannel.content.sendMessage(`Hello @${Users.admin.data.username} from User 1`); + + await page.waitForTimeout(100); // wait for the sound to play + + const audioCalls = await page.evaluate(() => window.__audioCalls); + expect(audioCalls).toHaveProperty('src'); + expect(audioCalls.src).toContain('ringtone'); + expect(audioCalls.played).toBe(true); + + await user1Page.close(); + }); + }); + + test.describe('Custom room notification preferences', () => { + test.beforeEach(async ({ api }) => { + await api.post('/rooms.saveNotification', { + roomId: targetChannelId, + notifications: { + audioNotificationValue: 'door', + }, + }); + }); + + test('should play custom room notification sound', async ({ page, browser }) => { + const user1Page = await browser.newPage({ storageState: Users.user1.state }); + await user1Page.goto(`/channel/${targetChannel}`); + const user1PoHomeChannel = new HomeChannel(user1Page); + await user1PoHomeChannel.content.waitForChannel(); + + await poHomeChannel.sidenav.sidebarHomeAction.click(); + + await user1PoHomeChannel.content.sendMessage(`Hello @${Users.admin.data.username} from User 1`); + + await page.waitForTimeout(100); // wait for the sound to play + + const audioCalls = await page.evaluate(() => window.__audioCalls); + expect(audioCalls).toHaveProperty('src'); + expect(audioCalls.src).toContain('door'); + expect(audioCalls.played).toBe(true); + + await user1Page.close(); + }); + }); +}); From e3498a8e220a76fbf7f113c690e820a8f9c6793a Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Thu, 14 Aug 2025 04:22:39 +0530 Subject: [PATCH 3/5] add changeset --- .changeset/cold-spiders-act.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cold-spiders-act.md diff --git a/.changeset/cold-spiders-act.md b/.changeset/cold-spiders-act.md new file mode 100644 index 0000000000000..6011adc699990 --- /dev/null +++ b/.changeset/cold-spiders-act.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes an issue where custom room notification sounds were not applied. From 6dfdefb2816f8f9afb101fd6899665eb65350ac5 Mon Sep 17 00:00:00 2001 From: Tasso Date: Thu, 14 Aug 2025 16:12:33 -0300 Subject: [PATCH 4/5] Override prototype method instead of constructor --- .../tests/e2e/notification-sounds.spec.ts | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/apps/meteor/tests/e2e/notification-sounds.spec.ts b/apps/meteor/tests/e2e/notification-sounds.spec.ts index eba0a3a6eb86a..81c9da174e2c2 100644 --- a/apps/meteor/tests/e2e/notification-sounds.spec.ts +++ b/apps/meteor/tests/e2e/notification-sounds.spec.ts @@ -35,25 +35,13 @@ test.describe.serial('Notification Sounds', () => { await page.goto(`/channel/${targetChannel}`); await page.evaluate(() => { - const OriginalAudio = window.Audio; - window.__audioCalls = {} as { src?: string; played?: boolean }; - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore mock Audio constructor - window.Audio = function (src: string | undefined) { - if (!src) { - return new OriginalAudio(); - } - - window.__audioCalls = { src, played: false }; - const audioObj = new OriginalAudio(src); - const originalPlay = audioObj.play.bind(audioObj); - audioObj.play = () => { + Audio.prototype.play = ((fn) => + function (this: HTMLAudioElement, ...args: unknown[]) { + window.__audioCalls = { src: this.src, played: false }; + const ret = fn.call(this, ...args); window.__audioCalls.played = true; - return originalPlay(); - }; - return audioObj; - }; + return ret; + })(Audio.prototype.play); }); }); From f4eb16fa4548dd0c4ac1b826377a8229c5099983 Mon Sep 17 00:00:00 2001 From: yash-rajpal Date: Tue, 19 Aug 2025 18:36:39 +0530 Subject: [PATCH 5/5] refactor: user page opening and closing --- .../tests/e2e/notification-sounds.spec.ts | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/meteor/tests/e2e/notification-sounds.spec.ts b/apps/meteor/tests/e2e/notification-sounds.spec.ts index 81c9da174e2c2..d6328c5d005ac 100644 --- a/apps/meteor/tests/e2e/notification-sounds.spec.ts +++ b/apps/meteor/tests/e2e/notification-sounds.spec.ts @@ -1,3 +1,5 @@ +import type { Page } from 'playwright-core'; + import { Users } from './fixtures/userStates'; import { HomeChannel } from './page-objects'; import { createTargetChannelAndReturnFullRoom, deleteRoom, setUserPreferences } from './utils'; @@ -16,6 +18,7 @@ test.describe.serial('Notification Sounds', () => { let targetChannel: string; let targetChannelId: string; let poHomeChannel: HomeChannel; + let user1Page: Page; test.beforeAll(async ({ api }) => { const { channel } = await createTargetChannelAndReturnFullRoom(api, { @@ -29,9 +32,9 @@ test.describe.serial('Notification Sounds', () => { await deleteRoom(api, targetChannel); }); - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ page, browser }) => { poHomeChannel = new HomeChannel(page); - + user1Page = await browser.newPage({ storageState: Users.user1.state }); await page.goto(`/channel/${targetChannel}`); await page.evaluate(() => { @@ -45,8 +48,11 @@ test.describe.serial('Notification Sounds', () => { }); }); - test('should play default notification sounds', async ({ page, browser }) => { - const user1Page = await browser.newPage({ storageState: Users.user1.state }); + test.afterEach(async () => { + await user1Page.close(); + }); + + test('should play default notification sounds', async ({ page }) => { await user1Page.goto(`/channel/${targetChannel}`); const user1PoHomeChannel = new HomeChannel(user1Page); await user1PoHomeChannel.content.waitForChannel(); @@ -61,8 +67,6 @@ test.describe.serial('Notification Sounds', () => { expect(audioCalls).toHaveProperty('src'); expect(audioCalls.src).toContain('chime'); expect(audioCalls.played).toBe(true); - - await user1Page.close(); }); test.describe('Notification sound preferences', () => { @@ -78,8 +82,7 @@ test.describe.serial('Notification Sounds', () => { }); }); - test('should play notification sound based on user preferences', async ({ page, browser }) => { - const user1Page = await browser.newPage({ storageState: Users.user1.state }); + test('should play notification sound based on user preferences', async ({ page }) => { await user1Page.goto(`/channel/${targetChannel}`); const user1PoHomeChannel = new HomeChannel(user1Page); await user1PoHomeChannel.content.waitForChannel(); @@ -94,8 +97,6 @@ test.describe.serial('Notification Sounds', () => { expect(audioCalls).toHaveProperty('src'); expect(audioCalls.src).toContain('ringtone'); expect(audioCalls.played).toBe(true); - - await user1Page.close(); }); }); @@ -109,8 +110,7 @@ test.describe.serial('Notification Sounds', () => { }); }); - test('should play custom room notification sound', async ({ page, browser }) => { - const user1Page = await browser.newPage({ storageState: Users.user1.state }); + test('should play custom room notification sound', async ({ page }) => { await user1Page.goto(`/channel/${targetChannel}`); const user1PoHomeChannel = new HomeChannel(user1Page); await user1PoHomeChannel.content.waitForChannel(); @@ -125,8 +125,6 @@ test.describe.serial('Notification Sounds', () => { expect(audioCalls).toHaveProperty('src'); expect(audioCalls.src).toContain('door'); expect(audioCalls.played).toBe(true); - - await user1Page.close(); }); }); });