Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/meteor/client/contexts/CallContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export const useIsCallError = (): boolean => {
return Boolean(isCallContextError(context));
};

export const useCallContext = (): CallContextValue => useContext(CallContext);

export const useCallActions = (): CallActionsType => {
const context = useContext(CallContext);

Expand Down Expand Up @@ -142,6 +144,7 @@ export const useCallClient = (): VoIPUser => {
if (!isCallContextReady(context)) {
throw new Error('useClient only if Calls are enabled and ready');
}

return context.voipClient;
};

Expand Down
23 changes: 23 additions & 0 deletions apps/meteor/client/contexts/VoIPAgentContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createContext, Dispatch, SetStateAction } from 'react';

export type VoIPAgentContextValue = {
agentEnabled: boolean;
registered: boolean;
networkStatus: 'online' | 'offline';
voipButtonEnabled: boolean;
setAgentEnabled: Dispatch<SetStateAction<boolean>>;
setRegistered: Dispatch<SetStateAction<boolean>>;
setNetworkStatus: Dispatch<SetStateAction<'online' | 'offline'>>;
setVoipButtonEnabled: Dispatch<SetStateAction<boolean>>;
};

export const VoIPAgentContext = createContext<VoIPAgentContextValue>({
agentEnabled: false,
registered: false,
networkStatus: 'offline',
voipButtonEnabled: false,
setAgentEnabled: () => undefined,
setRegistered: () => undefined,
setNetworkStatus: () => undefined,
setVoipButtonEnabled: () => undefined,
});
71 changes: 39 additions & 32 deletions apps/meteor/client/providers/CallProvider/CallProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ import { OutgoingByeRequest } from 'sip.js/lib/core';
import { CustomSounds } from '../../../app/custom-sounds/client';
import { getUserPreference } from '../../../app/utils/client';
import { WrapUpCallModal } from '../../components/voip/modal/WrapUpCallModal';
import { CallContext, CallContextValue, useCallCloseRoom } from '../../contexts/CallContext';
import { CallContext, CallContextValue } from '../../contexts/CallContext';
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
import { QueueAggregator } from '../../lib/voip/QueueAggregator';
import VoIPAgentProvider from '../VoIPAgentProvider';
import { useVoipClient } from './hooks/useVoipClient';

const startRingback = (user: IUser): void => {
Expand All @@ -44,6 +45,9 @@ export const CallProvider: FC = ({ children }) => {
const voipEnabled = useSetting('VoIP_Enabled');
const subscribeToNotifyUser = useStream('notify-user');
const dispatchEvent = useEndpoint('POST', '/v1/voip/events');
const visitorEndpoint = useEndpoint('POST', '/v1/livechat/visitor');
const voipEndpoint = useEndpoint('GET', '/v1/voip/room');
const voipCloseRoomEndpoint = useEndpoint('POST', '/v1/voip/room.close');
const setModal = useSetModal();

const result = useVoipClient();
Expand All @@ -54,10 +58,29 @@ export const CallProvider: FC = ({ children }) => {

const [queueCounter, setQueueCounter] = useState(0);
const [queueName, setQueueName] = useState('');
const [roomInfo, setRoomInfo] = useState<{ v: { token?: string }; rid: string }>();

const closeRoom = useCallback(
async (data): Promise<void> => {
roomInfo &&
(await voipCloseRoomEndpoint({
rid: roomInfo.rid,
token: roomInfo.v.token || '',
options: { comment: data?.comment, tags: data?.tags },
}));
homeRoute.push({});

const queueAggregator = result.voipClient?.getAggregator();
if (queueAggregator) {
queueAggregator.callEnded();
}
},
[homeRoute, result?.voipClient, roomInfo, voipCloseRoomEndpoint],
);

const openWrapUpModal = useCallback((): void => {
setModal(() => <WrapUpCallModal closeRoom={useCallCloseRoom} />);
}, [setModal]);
setModal(() => <WrapUpCallModal closeRoom={closeRoom} />);
}, [closeRoom, setModal]);

const [queueAggregator, setQueueAggregator] = useState<QueueAggregator>();

Expand Down Expand Up @@ -234,12 +257,6 @@ export const CallProvider: FC = ({ children }) => {
};
}, [onNetworkConnected, onNetworkDisconnected, result.voipClient]);

const visitorEndpoint = useEndpoint('POST', '/v1/livechat/visitor');
const voipEndpoint = useEndpoint('GET', '/v1/voip/room');
const voipCloseRoomEndpoint = useEndpoint('POST', '/v1/voip/room.close');

const [roomInfo, setRoomInfo] = useState<{ v: { token?: string }; rid: string }>();

const openRoom = (rid: IVoipRoom['_id']): void => {
roomCoordinator.openRouteLink('v', { rid });
};
Expand Down Expand Up @@ -319,34 +336,24 @@ export const CallProvider: FC = ({ children }) => {
}
return '';
},
closeRoom: async ({ comment, tags }: { comment?: string; tags?: string[] }): Promise<void> => {
roomInfo && (await voipCloseRoomEndpoint({ rid: roomInfo.rid, token: roomInfo.v.token || '', options: { comment, tags } }));
homeRoute.push({});
const queueAggregator = voipClient.getAggregator();
if (queueAggregator) {
queueAggregator.callEnded();
}
},
closeRoom,
openWrapUpModal,
};
}, [
voipEnabled,
user,
result,
roomInfo,
queueCounter,
queueName,
openWrapUpModal,
visitorEndpoint,
voipEndpoint,
voipCloseRoomEndpoint,
homeRoute,
]);
}, [voipEnabled, user, result, roomInfo, queueCounter, queueName, closeRoom, openWrapUpModal, visitorEndpoint, voipEndpoint]);

return (
<CallContext.Provider value={contextValue}>
{children}
{contextValue.enabled && createPortal(<audio ref={remoteAudioMediaRef} />, document.body)}
{contextValue.ready ? (
<VoIPAgentProvider>
{children}
{contextValue.enabled && createPortal(<audio ref={remoteAudioMediaRef} />, document.body)}
</VoIPAgentProvider>
) : (
<>
{children}
{contextValue.enabled && createPortal(<audio ref={remoteAudioMediaRef} />, document.body)}
</>
)}
</CallContext.Provider>
);
};
8 changes: 4 additions & 4 deletions apps/meteor/client/providers/MeteorProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ const MeteorProvider: FC = ({ children }) => (
<AvatarUrlProvider>
<CustomSoundProvider>
<UserProvider>
<AuthorizationProvider>
<ModalProvider>
<ModalProvider>
<AuthorizationProvider>
<CallProvider>
<OmnichannelProvider>
<AttachmentProvider>{children}</AttachmentProvider>
</OmnichannelProvider>
</CallProvider>
</ModalProvider>
</AuthorizationProvider>
</AuthorizationProvider>
</ModalProvider>
</UserProvider>
</CustomSoundProvider>
</AvatarUrlProvider>
Expand Down
102 changes: 102 additions & 0 deletions apps/meteor/client/providers/VoIPAgentProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';

import { useCallActions, useCallClient } from '../contexts/CallContext';
import { VoIPAgentContext } from '../contexts/VoIPAgentContext';

const VoIPAgentProvider: FC = ({ children }) => {
const [agentEnabled, setAgentEnabled] = useState(false);
const [registered, setRegistered] = useState(false);
const [networkStatus, setNetworkStatus] = useState<'online' | 'offline'>('online');
const [voipButtonEnabled, setVoipButtonEnabled] = useState(false);
const callActions = useCallActions();

const voipClient = useCallClient();
const registerState = useMemo(() => voipClient.getRegistrarState(), [voipClient]);

const toggleRegistered = useCallback((): void => {
setRegistered((registered) => !registered);
}, []);

const toggleRegistrationError = useCallback((): void => {
setRegistered(false);
setAgentEnabled(false);
}, []);

const onNetworkConnected = useCallback((): void => {
setVoipButtonEnabled(['IN_CALL', 'ON_HOLD'].includes(voipClient.callerInfo.state));
setNetworkStatus('online');
}, [setNetworkStatus, setVoipButtonEnabled, voipClient.callerInfo.state]);

const onNetworkDisconnected = useCallback((): void => {
setVoipButtonEnabled(true);
setNetworkStatus('offline');
}, [setNetworkStatus, setVoipButtonEnabled]);

useEffect(() => {
if (!agentEnabled) {
return;
}

voipClient.register();

return (): void => voipClient.unregister();
}, [agentEnabled, voipClient]);

useEffect(() => {
setVoipButtonEnabled(['IN_CALL', 'ON_HOLD'].includes(voipClient.callerInfo.state));
}, [setVoipButtonEnabled, voipClient.callerInfo.state]);

useEffect(() => {
setRegistered(registerState === 'registered');
}, [registerState]);

useEffect(() => {
if (voipButtonEnabled) {
return;
}

voipClient.callerInfo.state === 'OFFER_RECEIVED' && callActions.reject();
}, [callActions, voipButtonEnabled, voipClient.callerInfo.state]);

useEffect(() => {
voipClient.on('registered', toggleRegistered);
voipClient.on('unregistered', toggleRegistered);
voipClient.on('registrationerror', toggleRegistrationError);
voipClient.on('unregistrationerror', toggleRegistrationError);
voipClient.onNetworkEvent('connected', onNetworkConnected);
voipClient.onNetworkEvent('disconnected', onNetworkDisconnected);
voipClient.onNetworkEvent('connectionerror', onNetworkDisconnected);
voipClient.onNetworkEvent('localnetworkonline', onNetworkConnected);
voipClient.onNetworkEvent('localnetworkoffline', onNetworkDisconnected);

return (): void => {
voipClient.off('registered', toggleRegistered);
voipClient.off('unregistered', toggleRegistered);
voipClient.off('registrationerror', toggleRegistrationError);
voipClient.off('unregistrationerror', toggleRegistrationError);
voipClient.offNetworkEvent('connected', onNetworkConnected);
voipClient.offNetworkEvent('disconnected', onNetworkDisconnected);
voipClient.offNetworkEvent('connectionerror', onNetworkDisconnected);
voipClient.offNetworkEvent('localnetworkonline', onNetworkConnected);
voipClient.offNetworkEvent('localnetworkoffline', onNetworkDisconnected);
};
}, [voipClient, onNetworkConnected, onNetworkDisconnected, toggleRegistered, toggleRegistrationError]);

return (
<VoIPAgentContext.Provider
children={children}
value={{
agentEnabled,
registered,
networkStatus,
voipButtonEnabled,
setAgentEnabled,
setRegistered,
setNetworkStatus,
setVoipButtonEnabled,
}}
/>
);
};

export default VoIPAgentProvider;
2 changes: 0 additions & 2 deletions apps/meteor/client/sidebar/footer/voip/VoipFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ export const VoipFooter = ({
small
square
danger
primary
onClick={(e): unknown => {
e.stopPropagation();
toggleMic(false);
Expand All @@ -152,7 +151,6 @@ export const VoipFooter = ({
small
square
success
primary
onClick={async (): Promise<void> => {
callActions.pickUp();
const rid = await createRoom(caller);
Expand Down
Loading