[IMPROVE] Unify voip streams into single stream (#25108)

This commit is contained in:
Kevin Aleman 2022-05-23 13:00:16 -06:00 committed by GitHub
parent 2192b91b0a
commit 4bc080b6cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 154 additions and 131 deletions

2
.gitignore vendored
View File

@ -40,3 +40,5 @@ yarn-error.log*
!.yarn/releases
!.yarn/sdks
!.yarn/versions
.nvmrc

View File

@ -39,7 +39,7 @@ export class QueueAggregator {
}
private updateQueueInfo(queueName: string, queuedCalls: number): void {
if (!this.currentQueueMembershipStatus[queueName]) {
if (!this.currentQueueMembershipStatus?.[queueName]) {
// something is wrong. Queue is not found in the membership details.
return;
}
@ -48,14 +48,14 @@ export class QueueAggregator {
setMembership(subscription: IQueueMembershipSubscription): void {
this.extension = subscription.extension;
for (let i = 0; i < subscription.queues.length; i++) {
const queue = subscription.queues[i];
subscription.queues.forEach((queue) => {
const queueInfo: IQueueInfo = {
queueName: queue.name,
callsInQueue: 0,
};
this.currentQueueMembershipStatus[queue.name] = queueInfo;
}
});
}
queueJoined(joiningDetails: { queuename: string; callerid: { id: string }; queuedcalls: string }): void {
@ -77,7 +77,7 @@ export class QueueAggregator {
memberRemoved(queue: { queuename: string; queuedcalls: string }): void {
// current user is removed from the queue which has queue count |queuedcalls|
if (!this.currentQueueMembershipStatus[queue.queuename]) {
if (!this.currentQueueMembershipStatus?.[queue.queuename]) {
// something is wrong. Queue is not found in the membership details.
return;
}
@ -89,15 +89,11 @@ export class QueueAggregator {
}
getCallWaitingCount(): number {
let totalCallWaitingCount = 0;
Object.entries(this.currentQueueMembershipStatus).forEach(([, value]) => {
totalCallWaitingCount += value.callsInQueue;
});
return totalCallWaitingCount;
return Object.entries(this.currentQueueMembershipStatus).reduce((acc, [_, value]) => acc + value.callsInQueue, 0);
}
getCurrentQueueName(): string {
if (this.currentlyServing.queueInfo) {
if (this.currentlyServing?.queueInfo) {
return this.currentlyServing.queueInfo.queueName;
}
@ -105,7 +101,7 @@ export class QueueAggregator {
}
callRinging(queueInfo: { queuename: string; callerid: { id: string; name: string } }): void {
if (!this.currentQueueMembershipStatus[queueInfo.queuename]) {
if (!this.currentQueueMembershipStatus?.[queueInfo.queuename]) {
return;
}

View File

@ -1,5 +1,13 @@
import type { IVoipRoom, IUser } from '@rocket.chat/core-typings';
import { ICallerInfo } from '@rocket.chat/core-typings';
import type { IVoipRoom, IUser, VoipEventDataSignature } from '@rocket.chat/core-typings';
import {
ICallerInfo,
isVoipEventAgentCalled,
isVoipEventAgentConnected,
isVoipEventCallerJoined,
isVoipEventQueueMemberAdded,
isVoipEventQueueMemberRemoved,
isVoipEventCallAbandoned,
} from '@rocket.chat/core-typings';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useRoute, useUser, useSetting, useEndpoint, useStream } from '@rocket.chat/ui-contexts';
import { Random } from 'meteor/random';
@ -65,90 +73,54 @@ export const CallProvider: FC = ({ children }) => {
return;
}
const handleAgentCalled = async (queue: {
queuename: string;
callerId: { id: string; name: string };
queuedcalls: string;
}): Promise<void> => {
queueAggregator.callRinging({ queuename: queue.queuename, callerid: queue.callerId });
setQueueName(queueAggregator.getCurrentQueueName());
const handleEventReceived = async (event: VoipEventDataSignature): Promise<void> => {
if (isVoipEventAgentCalled(event)) {
const { data } = event;
queueAggregator.callRinging({ queuename: data.queue, callerid: data.callerId });
setQueueName(queueAggregator.getCurrentQueueName());
return;
}
if (isVoipEventAgentConnected(event)) {
const { data } = event;
queueAggregator.callPickedup({ queuename: data.queue, queuedcalls: data.queuedCalls, waittimeinqueue: data.waitTimeInQueue });
setQueueName(queueAggregator.getCurrentQueueName());
setQueueCounter(queueAggregator.getCallWaitingCount());
return;
}
if (isVoipEventCallerJoined(event)) {
const { data } = event;
queueAggregator.queueJoined({ queuename: data.queue, callerid: data.callerId, queuedcalls: data.queuedCalls });
setQueueCounter(queueAggregator.getCallWaitingCount());
return;
}
if (isVoipEventQueueMemberAdded(event)) {
const { data } = event;
queueAggregator.memberAdded({ queuename: data.queue, queuedcalls: data.queuedCalls });
setQueueName(queueAggregator.getCurrentQueueName());
setQueueCounter(queueAggregator.getCallWaitingCount());
return;
}
if (isVoipEventQueueMemberRemoved(event)) {
const { data } = event;
queueAggregator.memberRemoved({ queuename: data.queue, queuedcalls: data.queuedCalls });
setQueueCounter(queueAggregator.getCallWaitingCount());
return;
}
if (isVoipEventCallAbandoned(event)) {
const { data } = event;
queueAggregator.queueAbandoned({ queuename: data.queue, queuedcallafterabandon: data.queuedCallAfterAbandon });
setQueueName(queueAggregator.getCurrentQueueName());
setQueueCounter(queueAggregator.getCallWaitingCount());
return;
}
console.warn('Unknown event received');
};
return subscribeToNotifyUser(`${user._id}/agentcalled`, handleAgentCalled);
}, [subscribeToNotifyUser, user, voipEnabled, queueAggregator]);
useEffect(() => {
if (!voipEnabled || !user || !queueAggregator) {
return;
}
const handleQueueJoined = async (joiningDetails: {
queuename: string;
callerid: { id: string };
queuedcalls: string;
}): Promise<void> => {
queueAggregator.queueJoined(joiningDetails);
setQueueCounter(queueAggregator.getCallWaitingCount());
};
return subscribeToNotifyUser(`${user._id}/callerjoined`, handleQueueJoined);
}, [subscribeToNotifyUser, user, voipEnabled, queueAggregator]);
useEffect(() => {
if (!voipEnabled || !user || !queueAggregator) {
return;
}
const handleAgentConnected = (queue: { queuename: string; queuedcalls: string; waittimeinqueue: string }): void => {
queueAggregator.callPickedup(queue);
setQueueName(queueAggregator.getCurrentQueueName());
setQueueCounter(queueAggregator.getCallWaitingCount());
};
return subscribeToNotifyUser(`${user._id}/agentconnected`, handleAgentConnected);
}, [queueAggregator, subscribeToNotifyUser, user, voipEnabled]);
useEffect(() => {
if (!voipEnabled || !user || !queueAggregator) {
return;
}
const handleMemberAdded = (queue: { queuename: string; queuedcalls: string }): void => {
queueAggregator.memberAdded(queue);
setQueueName(queueAggregator.getCurrentQueueName());
setQueueCounter(queueAggregator.getCallWaitingCount());
};
return subscribeToNotifyUser(`${user._id}/queuememberadded`, handleMemberAdded);
}, [queueAggregator, subscribeToNotifyUser, user, voipEnabled]);
useEffect(() => {
if (!voipEnabled || !user || !queueAggregator) {
return;
}
const handleMemberRemoved = (queue: { queuename: string; queuedcalls: string }): void => {
queueAggregator.memberRemoved(queue);
setQueueCounter(queueAggregator.getCallWaitingCount());
};
return subscribeToNotifyUser(`${user._id}/queuememberremoved`, handleMemberRemoved);
}, [queueAggregator, subscribeToNotifyUser, user, voipEnabled]);
useEffect(() => {
if (!voipEnabled || !user || !queueAggregator) {
return;
}
const handleCallAbandon = (queue: { queuename: string; queuedcallafterabandon: string }): void => {
queueAggregator.queueAbandoned(queue);
setQueueName(queueAggregator.getCurrentQueueName());
setQueueCounter(queueAggregator.getCallWaitingCount());
};
return subscribeToNotifyUser(`${user._id}/callabandoned`, handleCallAbandon);
}, [queueAggregator, subscribeToNotifyUser, user, voipEnabled]);
return subscribeToNotifyUser(`${user._id}/voip.events`, handleEventReceived);
}, [subscribeToNotifyUser, user, queueAggregator, voipEnabled]);
// This was causing event duplication before, so we'll leave this here for now
useEffect(() => {
if (!voipEnabled || !user || !queueAggregator) {
return;
@ -159,7 +131,7 @@ export const CallProvider: FC = ({ children }) => {
openWrapUpModal();
};
return subscribeToNotifyUser(`${user._id}/call.callerhangup`, handleCallHangup);
return subscribeToNotifyUser(`${user._id}/call.hangup`, handleCallHangup);
}, [openWrapUpModal, queueAggregator, subscribeToNotifyUser, user, voipEnabled]);
useEffect(() => {

View File

@ -278,23 +278,13 @@ export class ListenersModule {
service.onEvent('banner.enabled', (bannerId): void => {
notifications.notifyLoggedInThisInstance('banner-changed', { bannerId });
});
service.onEvent('queue.agentcalled', (userId, queuename, callerId): void => {
notifications.notifyUserInThisInstance(userId, 'agentcalled', { queuename, callerId });
service.onEvent('voip.events', (userId, data): void => {
notifications.notifyUserInThisInstance(userId, 'voip.events', data);
});
service.onEvent('queue.agentconnected', (userId, queuename: string, queuedcalls: string, waittimeinqueue: string): void => {
notifications.notifyUserInThisInstance(userId, 'agentconnected', { queuename, queuedcalls, waittimeinqueue });
});
service.onEvent('queue.callerjoined', (userId, queuename, callerid, queuedcalls): void => {
notifications.notifyUserInThisInstance(userId, 'callerjoined', { queuename, callerid, queuedcalls });
});
service.onEvent('queue.queuememberadded', (userId, queuename: string, queuedcalls: string): void => {
notifications.notifyUserInThisInstance(userId, 'queuememberadded', { queuename, queuedcalls });
});
service.onEvent('queue.queuememberremoved', (userId, queuename: string, queuedcalls: string): void => {
notifications.notifyUserInThisInstance(userId, 'queuememberremoved', { queuename, queuedcalls });
});
service.onEvent('queue.callabandoned', (userId, queuename: string, queuedcallafterabandon: string): void => {
notifications.notifyUserInThisInstance(userId, 'callabandoned', { queuename, queuedcallafterabandon });
service.onEvent('call.callerhangup', (userId, data): void => {
notifications.notifyUserInThisInstance(userId, 'call.hangup', data);
});
service.onEvent('notify.desktop', (uid, notification): void => {

View File

@ -344,6 +344,12 @@ export function initWatchers(models: IModelsParam, broadcast: BroadcastCallback,
// TODO: Prevent flood from database on username change, what causes changes on all past messages from that user
// and most of those messages are not loaded by the clients.
watch<IUser>(Users, ({ clientAction, id, data, diff, unset }) => {
// LivechatCount is updated each time an agent is routed to a chat. This prop is not used on the UI so we don't need
// to broadcast events originated by it when it's the only update on the user
if (diff && Object.keys(diff).length === 1 && 'livechatCount' in diff) {
return;
}
broadcast('watch.users', { clientAction, data, diff, unset, id });
});

View File

@ -22,6 +22,7 @@ import type {
IInvite,
IWebdavAccount,
ICustomSound,
VoipEventDataSignature,
} from '@rocket.chat/core-typings';
import { AutoUpdateRecord } from '../types/IMeteor';
@ -123,12 +124,10 @@ export type EventSignatures = {
diff?: undefined | Record<string, any>;
id: string;
}): void;
'queue.agentcalled'(userid: string, queuename: string, callerid: Record<string, string>): void;
'queue.agentconnected'(userid: string, queuename: string, queuedcalls: string, waittimeinqueue: string): void;
'queue.callerjoined'(userid: string, queuename: string, callerid: Record<string, string>, queuedcalls: string): void;
'queue.queuememberadded'(userid: string, queuename: string, queuedcalls: string): void;
'queue.queuememberremoved'(userid: string, queuename: string, queuedcalls: string): void;
'queue.callabandoned'(userid: string, queuename: string, queuedcallafterabandon: string): void;
// Send all events from here
'voip.events'(userId: string, data: VoipEventDataSignature): void;
'call.callerhangup'(userId: string, data: { roomId: string }): void;
'watch.pbxevents'(data: { clientAction: ClientAction; data: Partial<IPbxEvent>; id: string }): void;
'connector.statuschanged'(enabled: boolean): void;
};

View File

@ -28,7 +28,7 @@ import { VoipRoomsRaw } from '../../../app/models/server/raw/VoipRooms';
import { PbxEventsRaw } from '../../../app/models/server/raw/PbxEvents';
import { sendMessage } from '../../../app/lib/server/functions/sendMessage';
import { FindVoipRoomsParams } from './internalTypes';
import { Notifications } from '../../../app/notifications/server';
import { api } from '../../sdk/api';
export class OmnichannelVoipService extends ServiceClassInternal implements IOmnichannelVoipService {
protected name = 'omnichannel-voip';
@ -80,8 +80,7 @@ export class OmnichannelVoipService extends ServiceClassInternal implements IOmn
return;
}
this.logger.debug(`Notifying agent ${agent._id} of hangup on room ${currentRoom._id}`);
// TODO evalute why this is 'notifyUserInThisInstance'
Notifications.notifyUserInThisInstance(agent._id, 'call.callerhangup', { roomId: currentRoom._id });
api.broadcast('call.callerhangup', agent._id, { roomId: currentRoom._id });
}
private async processAgentDisconnect(extension: string): Promise<void> {

View File

@ -89,8 +89,8 @@ export class ContinuousMonitor extends Command {
async processQueueMembershipChange(event: IQueueMemberAdded | IQueueMemberRemoved): Promise<void> {
const extension = event.interface.toLowerCase().replace('pjsip/', '');
const queueName = event.queue;
const queueDetails = await this.getQueueDetails(queueName);
const { queue } = event;
const queueDetails = await this.getQueueDetails(queue);
const { calls } = queueDetails;
const user = await this.users.findOneByExtension(extension, {
projection: {
@ -101,9 +101,9 @@ export class ContinuousMonitor extends Command {
});
if (user) {
if (isIQueueMemberAddedEvent(event)) {
api.broadcast(`queue.queuememberadded`, user._id, queueName, calls);
api.broadcast(`voip.events`, user._id, { data: { queue, queuedCalls: calls }, event: 'queue-member-added' });
} else if (isIQueueMemberRemovedEvent(event)) {
api.broadcast(`queue.queuememberremoved`, user._id, queueName, calls);
api.broadcast(`voip.events`, user._id, { event: 'queue-member-removed', data: { queue, queuedCalls: calls } });
}
}
}
@ -130,7 +130,8 @@ export class ContinuousMonitor extends Command {
name: event.calleridname,
};
api.broadcast('queue.agentcalled', user._id, event.queue, callerId);
api.broadcast('voip.events', user._id, { event: 'agent-called', data: { callerId, queue: event.queue } });
// api.broadcast('queue.agentcalled', user._id, event.queue, callerId);
}
async storePbxEvent(event: IQueueEvent | IContactStatus, eventName: string): Promise<void> {
@ -185,7 +186,7 @@ export class ContinuousMonitor extends Command {
await this.storePbxEvent(event, 'QueueCallerJoin');
this.logger.debug(`Broadcasting event queue.callerjoined to ${members.length} agents on queue ${event.queue}`);
members.forEach((m) => {
api.broadcast('queue.callerjoined', m, event.queue, callerId, event.count);
api.broadcast('voip.events', m, { event: 'caller-joined', data: { callerId, queue: event.queue, queuedCalls: event.count } });
});
break;
}
@ -194,7 +195,7 @@ export class ContinuousMonitor extends Command {
await this.storePbxEvent(event, 'QueueCallerAbandon');
this.logger.debug(`Broadcasting event queue.callabandoned to ${members.length} agents on queue ${event.queue}`);
members.forEach((m) => {
api.broadcast('queue.callabandoned', m, event.queue, calls);
api.broadcast('voip.events', m, { event: 'call-abandoned', data: { queue: event.queue, queuedCallAfterAbandon: calls } });
});
break;
}
@ -205,7 +206,10 @@ export class ContinuousMonitor extends Command {
this.logger.debug(`Broadcasting event queue.agentconnected to ${members.length} agents on queue ${event.queue}`);
members.forEach((m) => {
// event.holdtime signifies wait time in the queue.
api.broadcast('queue.agentconnected', m, event.queue, calls, event.holdtime);
api.broadcast('voip.events', m, {
event: 'agent-connected',
data: { queue: event.queue, queuedCalls: calls, waitTimeInQueue: event.holdtime },
});
});
break;
}

View File

@ -97,8 +97,6 @@ export * from './IOmnichannelAgent';
export * from './OmichannelRoutingConfig';
export * from './IVoipExtension';
export * from './voip';
export * from './voip/VoIPUserConfiguration';
export * from './voip/VoIpCallerInfo';
export * from './ACDQueues';
export * from './IVoipConnectorResult';
export * from './IVoipServerConfig';

View File

@ -0,0 +1,54 @@
export type VoipPropagatedEvents =
| 'agentcalled'
| 'agentconnected'
| 'callerjoined'
| 'queuememberadded'
| 'queuememberremoved'
| 'callabandoned';
export type VoipEventDataSignature =
| {
event: 'agent-called';
data: { callerId: { id: string; name: string }; queue: string };
}
| {
event: 'agent-connected';
data: { queue: string; queuedCalls: string; waitTimeInQueue: string };
}
| {
event: 'caller-joined';
data: { callerId: { id: string; name: string }; queue: string; queuedCalls: string };
}
| {
event: 'queue-member-added';
data: { queue: string; queuedCalls: string };
}
| {
event: 'queue-member-removed';
data: { queue: string; queuedCalls: string };
}
| {
event: 'call-abandoned';
data: { queuedCallAfterAbandon: string; queue: string };
};
export const isVoipEventAgentCalled = (
data: VoipEventDataSignature,
): data is { event: 'agent-called'; data: { callerId: { id: string; name: string }; queue: string } } => data.event === 'agent-called';
export const isVoipEventAgentConnected = (
data: VoipEventDataSignature,
): data is { event: 'agent-connected'; data: { queue: string; queuedCalls: string; waitTimeInQueue: string } } =>
data.event === 'agent-connected';
export const isVoipEventCallerJoined = (
data: VoipEventDataSignature,
): data is { event: 'caller-joined'; data: { callerId: { id: string; name: string }; queue: string; queuedCalls: string } } =>
data.event === 'caller-joined';
export const isVoipEventQueueMemberAdded = (
data: VoipEventDataSignature,
): data is { event: 'queue-member-added'; data: { queue: string; queuedCalls: string } } => data.event === 'queue-member-added';
export const isVoipEventQueueMemberRemoved = (
data: VoipEventDataSignature,
): data is { event: 'queue-member-removed'; data: { queue: string; queuedCalls: string } } => data.event === 'queue-member-removed';
export const isVoipEventCallAbandoned = (
data: VoipEventDataSignature,
): data is { event: 'call-abandoned'; data: { queuedCallAfterAbandon: string; queue: string } } => data.event === 'call-abandoned';

View File

@ -12,3 +12,6 @@ export * from './UserState';
export * from './VoipClientEvents';
export * from './VoipEvents';
export * from './WorkflowTypes';
export * from './IVoipClientEvents';
export * from './VoIPUserConfiguration';
export * from './VoIpCallerInfo';