Skip to content

Commit 4364222

Browse files
fix: blank notifications when a voice call ends (#39741)
1 parent 4235cd9 commit 4364222

File tree

9 files changed

+80
-19
lines changed

9 files changed

+80
-19
lines changed

.changeset/eighty-experts-sort.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@rocket.chat/ui-voip': patch
3+
'@rocket.chat/meteor': patch
4+
---
5+
6+
Fixes empty notifications sent when a voice call ends

apps/meteor/app/lib/server/functions/sendMessage.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ import { afterSaveMessage } from '../lib/afterSaveMessage';
1313
import { notifyOnRoomChangedById } from '../lib/notifyListener';
1414
import { validateCustomMessageFields } from '../lib/validateCustomMessageFields';
1515

16-
type SendMessageOptions = {
16+
export type SendMessageOptions = {
1717
upsert?: boolean;
1818
previewUrls?: string[];
19+
skipNotifications?: boolean;
1920
};
2021

2122
// TODO: most of the types here are wrong, but I don't want to change them now
@@ -289,7 +290,7 @@ export const sendMessage = async function (user: any, message: any, room: any, o
289290
void Apps.self?.triggerEvent(messageEvent, message);
290291
}
291292

292-
await afterSaveMessage(message, room, user);
293+
await afterSaveMessage(message, room, user, { options });
293294

294295
void notifyOnRoomChangedById(message.rid);
295296

apps/meteor/app/lib/server/lib/afterSaveMessage.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,28 @@ import type { Updater } from '@rocket.chat/models';
44
import { Rooms } from '@rocket.chat/models';
55

66
import { callbacks } from '../../../../server/lib/callbacks';
7-
8-
export async function afterSaveMessage(message: IMessage, room: IRoom, user: IUser, roomUpdater?: Updater<IRoom>): Promise<IMessage> {
7+
import type { SendMessageOptions } from '../functions/sendMessage';
8+
9+
export async function afterSaveMessage(
10+
message: IMessage,
11+
room: IRoom,
12+
user: IUser,
13+
{
14+
roomUpdater,
15+
options,
16+
}: {
17+
roomUpdater?: Updater<IRoom>;
18+
options?: SendMessageOptions;
19+
} = {},
20+
): Promise<IMessage> {
921
const updater = roomUpdater ?? Rooms.getUpdater();
10-
const data: IMessage = (await callbacks.run('afterSaveMessage', message, { room, user, roomUpdater: updater })) as unknown as IMessage;
22+
23+
const data: IMessage = (await callbacks.run('afterSaveMessage', message, {
24+
room,
25+
user,
26+
roomUpdater: updater,
27+
options,
28+
})) as unknown as IMessage;
1129

1230
if (!roomUpdater && updater.hasChanges()) {
1331
await Rooms.updateFromUpdater({ _id: room._id }, updater);
@@ -19,8 +37,21 @@ export async function afterSaveMessage(message: IMessage, room: IRoom, user: IUs
1937
return data;
2038
}
2139

22-
export function afterSaveMessageAsync(message: IMessage, room: IRoom, user: IUser, roomUpdater: Updater<IRoom> = Rooms.getUpdater()): void {
23-
callbacks.runAsync('afterSaveMessage', message, { room, user, roomUpdater });
40+
export function afterSaveMessageAsync(
41+
message: IMessage,
42+
room: IRoom,
43+
user: IUser,
44+
{
45+
roomUpdater: updater,
46+
options,
47+
}: {
48+
roomUpdater?: Updater<IRoom>;
49+
options?: SendMessageOptions;
50+
} = {},
51+
): void {
52+
const roomUpdater = updater ?? Rooms.getUpdater();
53+
54+
callbacks.runAsync('afterSaveMessage', message, { room, user, roomUpdater, options });
2455

2556
if (roomUpdater.hasChanges()) {
2657
void Rooms.updateFromUpdater({ _id: room._id }, roomUpdater);

apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,13 @@ settings.watch('Troubleshoot_Disable_Notifications', (value) => {
417417

418418
callbacks.add(
419419
'afterSaveMessage',
420-
(message, { room }) => sendAllNotifications(message, room),
420+
(message, { room, options }) => {
421+
if (options?.skipNotifications) {
422+
return message;
423+
}
424+
425+
return sendAllNotifications(message, room);
426+
},
421427
callbacks.priority.LOW,
422428
'sendNotificationsOnMessage',
423429
);

apps/meteor/app/threads/server/hooks/aftersavemessage.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Messages } from '@rocket.chat/models';
44
import { Meteor } from 'meteor/meteor';
55

66
import { callbacks } from '../../../../server/lib/callbacks';
7+
import type { SendMessageOptions } from '../../../lib/server/functions/sendMessage';
78
import { notifyOnMessageChange } from '../../../lib/server/lib/notifyListener';
89
import { updateThreadUsersSubscriptions, getMentions } from '../../../lib/server/lib/notifyUsersOnMessage';
910
import { sendMessageNotifications } from '../../../lib/server/lib/sendNotificationsOnMessage';
@@ -39,7 +40,7 @@ const notification = async (message: IMessage, room: IRoom, replies: string[]) =
3940
return message;
4041
};
4142

42-
export async function processThreads(message: IMessage, room: IRoom) {
43+
export async function processThreads(message: IMessage, room: IRoom, options?: SendMessageOptions) {
4344
if (!message.tmid) {
4445
return message;
4546
}
@@ -61,7 +62,9 @@ export async function processThreads(message: IMessage, room: IRoom) {
6162

6263
await notifyUsersOnReply(message, replies);
6364
await metaData(message, parentMessage, replies);
64-
await notification(message, room, replies);
65+
if (!options?.skipNotifications) {
66+
await notification(message, room, replies);
67+
}
6568
void notifyOnMessageChange({
6669
id: message.tmid,
6770
});
@@ -77,8 +80,8 @@ Meteor.startup(() => {
7780
}
7881
callbacks.add(
7982
'afterSaveMessage',
80-
async (message, { room }) => {
81-
return processThreads(message, room);
83+
async (message, { room, options }) => {
84+
return processThreads(message, room, options);
8285
},
8386
callbacks.priority.LOW,
8487
'threads-after-save-message',

apps/meteor/server/lib/callbacks.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type { FilterOperators } from 'mongodb';
2424

2525
import { Callbacks } from './callbacks/callbacksBase';
2626
import type { ILoginAttempt } from '../../app/authentication/server/ILoginAttempt';
27+
import type { SendMessageOptions } from '../../app/lib/server/functions/sendMessage';
2728
import type { IBusinessHourBehavior } from '../../app/livechat/server/business-hour/AbstractBusinessHour';
2829
import type { CloseRoomParams } from '../../app/livechat/server/lib/localTypes';
2930

@@ -46,7 +47,10 @@ interface EventLikeCallbackSignatures {
4647
'afterDeleteUser': (user: IUser) => void;
4748
'afterFileUpload': (params: { user: IUser; room: IRoom; message: IMessage }) => void;
4849
'afterRoomNameChange': (params: { room: IRoom; name: string; oldName: string; user: IUser }) => void;
49-
'afterSaveMessage': (message: IMessage, params: { room: IRoom; user: IUser; roomUpdater?: Updater<IRoom> }) => void;
50+
'afterSaveMessage': (
51+
message: IMessage,
52+
params: { room: IRoom; user: IUser; roomUpdater?: Updater<IRoom>; options?: SendMessageOptions },
53+
) => void;
5054
'afterOmnichannelSaveMessage': (message: IMessage, constant: { room: IOmnichannelRoom; roomUpdater: Updater<IOmnichannelRoom> }) => void;
5155
'livechat.removeAgentDepartment': (params: { departmentId: ILivechatDepartmentRecord['_id']; agentsId: ILivechatAgent['_id'][] }) => void;
5256
'livechat.saveAgentDepartment': (params: { departmentId: ILivechatDepartmentRecord['_id']; agentsId: ILivechatAgent['_id'][] }) => void;

apps/meteor/server/services/media-call/service.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ import { callServer, type IMediaCallServerSettings } from '@rocket.chat/media-ca
1212
import { type CallFeature, isClientMediaSignal, type ClientMediaSignal, type ServerMediaSignal } from '@rocket.chat/media-signaling';
1313
import type { InsertionModel } from '@rocket.chat/model-typings';
1414
import { CallHistory, MediaCalls, Rooms, Users } from '@rocket.chat/models';
15-
import { getHistoryMessagePayload } from '@rocket.chat/ui-voip/dist/ui-kit/getHistoryMessagePayload';
15+
import { callStateToTranslationKey, getHistoryMessagePayload } from '@rocket.chat/ui-voip/dist/ui-kit/getHistoryMessagePayload';
1616

1717
import { sendMessage } from '../../../app/lib/server/functions/sendMessage';
1818
import { settings } from '../../../app/settings/server';
19+
import { i18n } from '../../lib/i18n';
1920
import { createDirectMessage } from '../../methods/createDirectMessage';
2021

2122
const logger = new Logger('media-call service');
@@ -187,6 +188,10 @@ export class MediaCallService extends ServiceClassInternal implements IMediaCall
187188
}
188189
}
189190

191+
private getLanguageForUser(user: IUser): string {
192+
return user.language || settings.get('Language') || 'en';
193+
}
194+
190195
private async sendHistoryMessage(call: IMediaCall, room: IRoom): Promise<void> {
191196
const userId = call.caller.id || call.createdBy?.id; // I think this should always be the caller, since during a transfer the createdBy contact is the one that transferred the call
192197

@@ -196,12 +201,16 @@ export class MediaCallService extends ServiceClassInternal implements IMediaCall
196201
}
197202

198203
const state = this.getCallHistoryItemState(call);
204+
const skipNotifications = state !== 'not-answered' || call.hangupReason === 'rejected';
205+
const i18nKey = callStateToTranslationKey(state).i18n?.key;
206+
207+
const msg = i18nKey ? i18n.t(i18nKey, { lng: this.getLanguageForUser(user) }) : '';
199208
const duration = this.getCallDuration(call);
200209

201-
const record = getHistoryMessagePayload(state, duration, call._id);
210+
const record = getHistoryMessagePayload(state, duration, call._id, msg);
202211

203212
try {
204-
const message = await sendMessage(user, record, room);
213+
const message = await sendMessage(user, record, room, { skipNotifications });
205214

206215
if ('_id' in message) {
207216
await CallHistory.updateMany({ callId: call._id }, { $set: { messageId: message._id } });

packages/ui-voip/src/ui-kit/getHistoryMessagePayload.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,9 @@ describe('getHistoryMessagePayload', () => {
177177
});
178178

179179
it('should return correct payload for "not-answered" state', () => {
180-
const result = getHistoryMessagePayload('not-answered', undefined, 'callid');
180+
const result = getHistoryMessagePayload('not-answered', undefined, 'callid', 'call was not answered');
181181
expect(result).toEqual({
182-
msg: '',
182+
msg: 'call was not answered',
183183
groupable: false,
184184
blocks: [
185185
{

packages/ui-voip/src/ui-kit/getHistoryMessagePayload.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,14 @@ export const getHistoryMessagePayload = (
7676
callState: CallHistoryItemState,
7777
callDuration: number | undefined,
7878
callId?: string,
79+
msg: string = '',
7980
): Pick<IMessage, 'msg' | 'groupable'> & { blocks: [InfoCardBlock] } => {
8081
const callStateTranslationKey = callStateToTranslationKey(callState);
8182
const icon = callStateToIcon(callState);
8283
const callDurationFormatted = getFormattedCallDuration(callDuration);
8384

8485
return {
85-
msg: '',
86+
msg,
8687
groupable: false,
8788
blocks: [
8889
{

0 commit comments

Comments
 (0)