Skip to content

Commit

Permalink
Review
Browse files Browse the repository at this point in the history
  • Loading branch information
ggazzo committed Jun 28, 2022
1 parent 3cdf48b commit ded403c
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 131 deletions.
1 change: 1 addition & 0 deletions apps/meteor/client/contexts/CallContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type CallContextEnabled = {
ready: unknown;
};
type CallContextReady = {
canMakeCall: boolean;
enabled: true;
ready: true;
voipClient: VoIPUser;
Expand Down
34 changes: 0 additions & 34 deletions apps/meteor/client/lib/voip/SimpleVoipUser.ts

This file was deleted.

5 changes: 5 additions & 0 deletions apps/meteor/client/lib/voip/VoIPUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -998,4 +998,9 @@ export class VoIPUser extends Emitter<VoipEvents> {
},
});
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async makeCall(callee: string, mediaRenderer?: IMediaStreamRenderer): Promise<void> {
throw new Error('Not implemented');
}
}
174 changes: 88 additions & 86 deletions apps/meteor/client/providers/CallProvider/CallProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import {
isVoipEventQueueMemberRemoved,
isVoipEventCallAbandoned,
UserState,
ICallDetails,
} from '@rocket.chat/core-typings';
import { ICallDetails } from '@rocket.chat/core-typings/dist/voip/ICallDetails';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useRoute, useUser, useSetting, useEndpoint, useStream, useSetModal } from '@rocket.chat/ui-contexts';
import { Random } from 'meteor/random';
import React, { useMemo, FC, useRef, useCallback, useEffect, useState } from 'react';
Expand Down Expand Up @@ -90,15 +89,6 @@ export const CallProvider: FC = ({ children }) => {

const [networkStatus, setNetworkStatus] = useState<NetworkState>('online');

const handleWrapUp = useCallback(() => {
if (isEnterprise) {
openWrapUpModal();
return;
}

closeRoom();
}, [isEnterprise, openWrapUpModal, closeRoom]);

useEffect(() => {
if (!result?.voipClient) {
return;
Expand All @@ -107,9 +97,9 @@ export const CallProvider: FC = ({ children }) => {
setQueueAggregator(result.voipClient.getAggregator());
}, [result]);

const openRoom = (rid: IVoipRoom['_id']): void => {
const openRoom = useCallback((rid: IVoipRoom['_id']): void => {
roomCoordinator.openRouteLink('v', { rid });
};
}, []);

const createRoom = useCallback(
async (caller: ICallerInfo): Promise<IVoipRoom['_id']> => {
Expand Down Expand Up @@ -137,30 +127,7 @@ export const CallProvider: FC = ({ children }) => {
return '';
}
},
[result.voipClient, setRoomInfo, user, visitorEndpoint, voipEndpoint],
);

const onCallEstablished = useCallback(
async (callDetails: ICallDetails): Promise<IVoipRoom['_id'] | undefined> => {
if (!result.voipClient || !callDetails.callInfo) {
return;
}
stopRingback();
if (callDetails.userState !== UserState.UAC) {
return;
}
// Agent has sent Invite. So it must create a room.
const { callInfo } = callDetails;
// While making the call, there is no remote media element available.
// When the call is ringing we have that element created. But we still
// do not want it to be attached.
// When call gets established, then switch the media renderer.
remoteAudioMediaRef.current && result.voipClient.switchMediaRenderer({ remoteMediaElement: remoteAudioMediaRef.current });
const roomId = await createRoom(callInfo);
dispatchEvent({ event: VoipClientEvents['VOIP-CALL-STARTED'], rid: roomId });
return roomId;
},
[createRoom, dispatchEvent, result.voipClient],
[openRoom, result.voipClient, user, visitorEndpoint, voipEndpoint],
);

useEffect(() => {
Expand Down Expand Up @@ -224,13 +191,18 @@ export const CallProvider: FC = ({ children }) => {
const handleCallHangup = (_event: { roomId: string }): void => {
setQueueName(queueAggregator.getCurrentQueueName());

handleWrapUp();
if (isEnterprise) {
openWrapUpModal();
return;
}

closeRoom();

dispatchEvent({ event: VoipClientEvents['VOIP-CALL-ENDED'], rid: _event.roomId });
};

return subscribeToNotifyUser(`${user._id}/call.hangup`, handleCallHangup);
}, [openWrapUpModal, queueAggregator, subscribeToNotifyUser, user, voipEnabled, dispatchEvent, handleWrapUp]);
}, [openWrapUpModal, queueAggregator, subscribeToNotifyUser, user, voipEnabled, dispatchEvent, isEnterprise, closeRoom]);

useEffect(() => {
if (!result.voipClient) {
Expand All @@ -239,25 +211,25 @@ export const CallProvider: FC = ({ children }) => {

/*
* This code may need a revisit when we handle callinqueue differently.
* Check clickup taks for more details
* Check clickup tasks for more details
* https://app.clickup.com/t/22hy1k4
* When customer called a queue (Either using skype or using internal number), call would get established
* customer would hear agent's voice but agent would not hear anything from customer.
* This issue was observed on unstable. It was found to be incosistent to reproduce.
* This issue was observed on unstable. It was found to be inconsistent to reproduce.
* On some developer env, it would happen randomly. On Safari it did not happen if
* user refreshes before taking every call.
*
* The reason behind this was as soon as agent accepts a call, queueCounter would change.
* This change will trigger re-rendering of media and creation of audio element.
* This audio element gets used by voipClient to render the remote audio.
* Because the re-render happend, it would hold a stale reference.
* Because the re-render happened, it would hold a stale reference.
*
* If the dom is inspected, audio element just before body is usually created by this class.
* this audio element.srcObject contains null value. In working case, it should display
* valid stream object.
*
* Reason for inconsistecies :
* This element is utilised in VoIPUser::setupRemoteMedia
* Reason for inconsistencies :
* This element is utilized in VoIPUser::setupRemoteMedia
* This function is called when webRTC receives a remote track event. i.e when the webrtc's peer connection
* starts receiving media. This event call back depends on several factors. How does asterisk setup streams.
* How does it creates a bridge which patches up the agent and customer (Media is flowing thru asterisk).
Expand All @@ -278,57 +250,74 @@ export const CallProvider: FC = ({ children }) => {
remoteAudioMediaRef.current && result.voipClient.switchMediaRenderer({ remoteMediaElement: remoteAudioMediaRef.current });
}, [result.voipClient]);

const onNetworkConnected = useMutableCallback((): void => {
if (!result.voipClient) {
return;
}
if (networkStatus === 'offline') {
setNetworkStatus('online');
}
});
const hasLicenseToMakeVoIPCalls = useHasLicense('voip-enterprise');

const onNetworkDisconnected = useMutableCallback((): void => {
if (!result.voipClient) {
useEffect(() => {
if (!result.voipClient || !user) {
return;
}
// Transitioning from online -> offline
// If there is ongoing call, terminate it or if we are processing an incoming/outgoing call
// reject it.
if (networkStatus === 'online') {
setNetworkStatus('offline');
switch (result.voipClient.callerInfo.state) {
case 'IN_CALL':
case 'ON_HOLD':
result.voipClient?.endCall();
break;
case 'OFFER_RECEIVED':
case 'ANSWER_SENT':
result.voipClient?.rejectCall();
break;

const onCallEstablished = async (callDetails: ICallDetails): Promise<IVoipRoom['_id'] | undefined> => {
if (!result.voipClient || !callDetails.callInfo) {
return;
}
}
});
stopRingback();
if (callDetails.userState !== UserState.UAC) {
return;
}
// Agent has sent Invite. So it must create a room.
const { callInfo } = callDetails;
// While making the call, there is no remote media element available.
// When the call is ringing we have that element created. But we still
// do not want it to be attached.
// When call gets established, then switch the media renderer.
remoteAudioMediaRef.current && result.voipClient.switchMediaRenderer({ remoteMediaElement: remoteAudioMediaRef.current });
const roomId = await createRoom(callInfo);
dispatchEvent({ event: VoipClientEvents['VOIP-CALL-STARTED'], rid: roomId });
return roomId;
};

const onRinging = useMutableCallback((): void => {
if (!result.voipClient || !user) {
return;
}
startRingback(user);
});
const onNetworkConnected = (): void => {
if (!result.voipClient) {
return;
}
if (networkStatus === 'offline') {
setNetworkStatus('online');
}
};

const onNetworkDisconnected = (): void => {
// Transitioning from online -> offline
// If there is ongoing call, terminate it or if we are processing an incoming/outgoing call
// reject it.
if (networkStatus === 'online') {
setNetworkStatus('offline');
switch (result.voipClient.callerInfo.state) {
case 'IN_CALL':
case 'ON_HOLD':
result.voipClient?.endCall();
break;
case 'OFFER_RECEIVED':
case 'ANSWER_SENT':
result.voipClient?.rejectCall();
break;
}
}
};

const onRinging = (): void => {
startRingback(user);
};

useEffect(() => {
if (!result.voipClient) {
return;
}
result.voipClient.onNetworkEvent('connected', onNetworkConnected);
result.voipClient.onNetworkEvent('disconnected', onNetworkDisconnected);
result.voipClient.onNetworkEvent('connectionerror', onNetworkDisconnected);
result.voipClient.onNetworkEvent('localnetworkonline', onNetworkConnected);
result.voipClient.onNetworkEvent('localnetworkoffline', onNetworkDisconnected);
result.voipClient.on('callestablished', (callDetails: ICallDetails) => onCallEstablished(callDetails));
result.voipClient.on('callestablished', onCallEstablished);
result.voipClient.on('ringing', onRinging);
result.voipClient.on('incomingcall', onRinging);
result.voipClient.on('callterminated', () => stopRingback());
result.voipClient.on('callterminated', stopRingback);

return (): void => {
result.voipClient?.offNetworkEvent('connected', onNetworkConnected);
Expand All @@ -338,10 +327,10 @@ export const CallProvider: FC = ({ children }) => {
result.voipClient?.offNetworkEvent('localnetworkoffline', onNetworkDisconnected);
result.voipClient?.off('incomingcall', onRinging);
result.voipClient?.off('ringing', onRinging);
result.voipClient?.off('callestablished', (callDetails: ICallDetails) => onCallEstablished(callDetails));
result.voipClient?.off('callterminated', () => stopRingback());
result.voipClient?.off('callestablished', onCallEstablished);
result.voipClient?.off('callterminated', stopRingback);
};
}, [onCallEstablished, onNetworkConnected, onNetworkDisconnected, onRinging, result.voipClient, user]);
}, [createRoom, dispatchEvent, networkStatus, result.voipClient, user]);

const contextValue: CallContextValue = useMemo(() => {
if (!voipEnabled) {
Expand Down Expand Up @@ -376,6 +365,7 @@ export const CallProvider: FC = ({ children }) => {
const { registrationInfo, voipClient } = result;

return {
canMakeCall: hasLicenseToMakeVoIPCalls,
enabled: true,
ready: true,
openedRoomInfo: roomInfo,
Expand All @@ -398,7 +388,19 @@ export const CallProvider: FC = ({ children }) => {
closeRoom,
openWrapUpModal,
};
}, [voipEnabled, user?.extension, result, roomInfo, queueCounter, queueName, createRoom, closeRoom, openWrapUpModal]);
}, [
voipEnabled,
user?.extension,
result,
hasLicenseToMakeVoIPCalls,
roomInfo,
queueCounter,
queueName,
openRoom,
createRoom,
closeRoom,
openWrapUpModal,
]);

return (
<CallContext.Provider value={contextValue}>
Expand Down
29 changes: 18 additions & 11 deletions apps/meteor/client/providers/CallProvider/hooks/useVoipClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { useUser, useSetting, useEndpoint, useStream } from '@rocket.chat/ui-con
import { KJUR } from 'jsrsasign';
import { useEffect, useState } from 'react';

import { SimpleVoipUser } from '../../../lib/voip/SimpleVoipUser';
import { useHasLicense } from '../../../../ee/client/hooks/useHasLicense';
import { EEVoipClient } from '../../../../ee/client/lib/voip/EEVoipClient.ts';
import { VoIPUser } from '../../../lib/voip/VoIPUser';
import { useWebRtcServers } from './useWebRtcServers';

Expand All @@ -29,6 +30,8 @@ export const useVoipClient = (): UseVoipClientResult => {
const iceServers = useWebRtcServers();
const [result, setResult] = useSafely(useState<UseVoipClientResult>({}));

const isEE = useHasLicense('voip-enterprise');

useEffect(() => {
const voipEnableEventHandler = (enabled: boolean): void => {
setVoipEnabled(enabled);
Expand Down Expand Up @@ -64,16 +67,20 @@ export const useVoipClient = (): UseVoipClientResult => {
(async (): Promise<void> => {
try {
const subscription = await membership({ extension });
client = await SimpleVoipUser.create(
extension,
password,
host,
websocketPath,

const config = {
authUserName: extension,
authPassword: password,
sipRegistrarHostnameOrIP: host,
webSocketURI: websocketPath,
enableVideo: true,
iceServers,
Number(voipRetryCount),
Boolean(enableKeepAlive),
'video',
);
connectionRetryCount: Number(voipRetryCount),
enableKeepAliveUsingOptionsForUnstableNetworks: Boolean(enableKeepAlive),
};

client = await (isEE ? EEVoipClient.create(config) : VoIPUser.create(config));

// Today we are hardcoding workflow mode.
// In future, this should be ready from configuration
client.setWorkflowMode(WorkflowTypes.CONTACT_CENTER_USER);
Expand All @@ -93,7 +100,7 @@ export const useVoipClient = (): UseVoipClientResult => {
client.clear();
}
};
}, [iceServers, registrationInfo, setResult, membership, voipEnabled, user?._id, user?.extension, voipRetryCount, enableKeepAlive]);
}, [iceServers, registrationInfo, setResult, membership, voipEnabled, user?._id, user?.extension, voipRetryCount, enableKeepAlive, isEE]);

return result;
};

0 comments on commit ded403c

Please sign in to comment.