[NEW] Audio and Video calling in Livechat using Jitsi and WebRTC (#23004)
* [NEW] Livechat WebRTC call settings (#22559) * [NEW] Livechat WebRTC call settings * [FIX] Omnichannel Call Settings - Change Livechat call to Video and Audio Call as it will apply to other omnichannels in future - Change Audio and Video Setting alert to description to be conformant with the other settings - Remove unrelated changes(base.css) that got induced unknowingly - Refactor to remove translation for "Jitsi" and "WebRTC" and remove unnecessary dependency on t - Refactor to add return type of handleClick - Add/remove related i18n labels * [FIX] Livechat videoCall api and method * [FIX] Add migrations for webRTC enabled settings and omnichannel call provider * [FIX] 'Jitsi' typo * [NEW] Join call action button (#22689) * [NEW] WebRTC Call Session * [IMPROVEMENT] Use API endpoint instead of method, fix handleClick dependency warning * [FIX] Return updated callStatus, use translation for join call message, remove system logger, make callStatus optional in room interface * [NEW] Join and End Call Action Message Button for Agent * [FIX] Use translation for Call ALready Ended toastr, convert actionLink to tsx * [IMPROVE] Remove redundant callStatus from message collection, Display Call Ended message with call duration * [REF] Use translation for call ended message, Store danger field in db with other action fields, store actionAlignment field in db * [NEW] update call status (#22854) * add code for update call status * remove fourth param Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> * [NEW] P2P WebRTC Connection Establishment (#22847) * [NEW] WebRTC P2P Connection with Basic Call UI * [FIX] Set Stream on a stable connection * [FIX] userId typecheck error * [REFACTOR] - Restore type of userId to string by removing `| undefined` - Add translation for visitor does not exist toastr - Set visitorId from room object fetched instead of fetching from livechat widget as query param - Type Checking * [FIX] Running startCall 2 times for agent * [FIX] Call declined Page * [NEW] Control Buttons - mic, cam, expand and end call (#22928) * [NEW] Control Buttons - mic, cam, expand and end call * [REFACTOR] Add an empty file on EOF en18.i18n that was mistakenly removed * [FIX] UI responsiveness (#22934) * [NEW] Control Buttons - mic, cam, expand and end call * [REFACTOR] Add an empty file on EOF en18.i18n that was mistakenly removed * [FIX] UI Responsiveness * [REF] Use const and ternary op * [FIX] Handle decline message action link (#22936) * [NEW] Control Buttons - mic, cam, expand and end call * [REFACTOR] Add an empty file on EOF en18.i18n that was mistakenly removed * [FIX] UI Responsiveness * [REF] Use const and ternary op * [FIX] Action Link not updating when call declined * [FIX] WebRTC_call_declined_message Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> * [REF] Use a single IF statement to handle status Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> * [FIX] Call button visible even when chat is queued (#22943) * [FIX] add callstatus attribute in room object (#22959) * [IMPROVE] Attach jitsi link to message object for Livechat (#22690) * [Improve] Attach jitsi link to message object for Livechat (cherry picked from commit c888961da3313de06eaeb0700b7ce0b6371ef469) * Update WebRTCClass.js * [NEW] Webrtc meet page layout (#22932) * [NEW] Control Buttons - mic, cam, expand and end call * webrtc meet page desgin * [REFACTOR] Add an empty file on EOF en18.i18n that was mistakenly removed * [FIX] UI Responsiveness * [FIX] Action Link not updating when call declined * webrtc meet page desgin * [FIX] Remote user avatar screen and video switching * fix-alert * fix 2 aletrs * improve codebase * make ui responsive * fix issue * Add call timer component + minor refactoring * some css changes Co-authored-by: Dhruv Jain <dhruv.jain93@gmail.com> Co-authored-by: murtaza98 <murtaza.patrawala@rocket.chat> Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> * Add migration * Changing files to tsx * Update default value for setting -Jitsi_Open_New_Window * Update invalid-livechat-config issue * Fix typescript errors * Fix build errors caused by new room prop - webRtcCallStartTime * Simplify call duration calculation logic * Add definition PUT method for REST on client Co-authored-by: Dhruv Jain <51796498+djcruz93@users.noreply.github.com> Co-authored-by: Deepak Agarwal <deepak710agarwal@gmail.com> Co-authored-by: Dhruv Jain <dhruv.jain93@gmail.com>pull/23547/head^2
parent
9771ee67f8
commit
6d7752fd21
@ -0,0 +1,27 @@ |
||||
import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; |
||||
import toastr from 'toastr'; |
||||
|
||||
import { actionLinks } from '../../action-links/client'; |
||||
import { APIClient } from '../../utils/client'; |
||||
import { Rooms } from '../../models/client'; |
||||
import { IMessage } from '../../../definition/IMessage'; |
||||
import { Notifications } from '../../notifications/client'; |
||||
|
||||
actionLinks.register('joinLivechatWebRTCCall', (message: IMessage) => { |
||||
const { callStatus, _id } = Rooms.findOne({ _id: message.rid }); |
||||
if (callStatus === 'declined' || callStatus === 'ended') { |
||||
toastr.info(TAPi18n.__('Call_Already_Ended')); |
||||
return; |
||||
} |
||||
window.open(`/meet/${ _id }`, _id); |
||||
}); |
||||
|
||||
actionLinks.register('endLivechatWebRTCCall', async (message: IMessage) => { |
||||
const { callStatus, _id } = Rooms.findOne({ _id: message.rid }); |
||||
if (callStatus === 'declined' || callStatus === 'ended') { |
||||
toastr.info(TAPi18n.__('Call_Already_Ended')); |
||||
return; |
||||
} |
||||
await APIClient.v1.put(`livechat/webrtc.call/${ message._id }`, {}, { rid: _id, status: 'ended' }); |
||||
Notifications.notifyRoom(_id, 'webrtc', 'callStatus', { callStatus: 'ended' }); |
||||
}); |
@ -1,3 +1,5 @@ |
||||
import './adapter'; |
||||
import './tabBar'; |
||||
import './actionLink'; |
||||
|
||||
export * from './WebRTCClass'; |
||||
|
@ -0,0 +1,26 @@ |
||||
import { useMemo, useCallback } from 'react'; |
||||
|
||||
import { useSetting } from '../../../client/contexts/SettingsContext'; |
||||
import { addAction } from '../../../client/views/room/lib/Toolbox'; |
||||
import { APIClient } from '../../utils/client'; |
||||
|
||||
addAction('webRTCVideo', ({ room }) => { |
||||
const enabled = useSetting('WebRTC_Enabled') && (useSetting('Omnichannel_call_provider') === 'WebRTC') && room.servedBy; |
||||
|
||||
const handleClick = useCallback(async (): Promise<void> => { |
||||
if (!room.callStatus || room.callStatus === 'declined' || room.callStatus === 'ended') { |
||||
await APIClient.v1.get('livechat/webrtc.call', { rid: room._id }); |
||||
} |
||||
window.open(`/meet/${ room._id }`, room._id); |
||||
}, [room._id, room.callStatus]); |
||||
|
||||
return useMemo(() => (enabled ? { |
||||
groups: ['live'], |
||||
id: 'webRTCVideo', |
||||
title: 'WebRTC_Call', |
||||
icon: 'phone', |
||||
action: handleClick, |
||||
full: true, |
||||
order: 4, |
||||
} : null), [enabled, handleClick]); |
||||
}); |
@ -1,24 +1,34 @@ |
||||
import { settingsRegistry } from '../../settings/server'; |
||||
|
||||
settingsRegistry.addGroup('WebRTC', function() { |
||||
this.add('WebRTC_Enabled', false, { |
||||
type: 'boolean', |
||||
group: 'WebRTC', |
||||
public: true, |
||||
i18nLabel: 'Enabled', |
||||
}); |
||||
this.add('WebRTC_Enable_Channel', false, { |
||||
type: 'boolean', |
||||
group: 'WebRTC', |
||||
public: true, |
||||
enableQuery: { _id: 'WebRTC_Enabled', value: true }, |
||||
}); |
||||
this.add('WebRTC_Enable_Private', false, { |
||||
type: 'boolean', |
||||
group: 'WebRTC', |
||||
public: true, |
||||
enableQuery: { _id: 'WebRTC_Enabled', value: true }, |
||||
}); |
||||
this.add('WebRTC_Enable_Direct', false, { |
||||
type: 'boolean', |
||||
group: 'WebRTC', |
||||
public: true, |
||||
enableQuery: { _id: 'WebRTC_Enabled', value: true }, |
||||
}); |
||||
return this.add('WebRTC_Servers', 'stun:stun.l.google.com:19302, stun:23.21.150.121, team%40rocket.chat:demo@turn:numb.viagenie.ca:3478', { |
||||
type: 'string', |
||||
group: 'WebRTC', |
||||
public: true, |
||||
enableQuery: { _id: 'WebRTC_Enabled', value: true }, |
||||
}); |
||||
}); |
||||
|
@ -0,0 +1,396 @@ |
||||
import { Box, Flex, ButtonGroup, Button, Icon } from '@rocket.chat/fuselage'; |
||||
import moment from 'moment'; |
||||
import React, { FC, useEffect, useState } from 'react'; |
||||
|
||||
import { Notifications } from '../../../app/notifications/client'; |
||||
import { WebRTC } from '../../../app/webrtc/client'; |
||||
import { WEB_RTC_EVENTS } from '../../../app/webrtc/index'; |
||||
import UserAvatar from '../../components/avatar/UserAvatar'; |
||||
import { useTranslation } from '../../contexts/TranslationContext'; |
||||
import OngoingCallDuration from './OngoingCallDuration'; |
||||
import './styles.css'; |
||||
|
||||
type CallPageProps = { |
||||
roomId: any; |
||||
visitorToken: any; |
||||
visitorId: any; |
||||
status: any; |
||||
setStatus: any; |
||||
layout: any; |
||||
visitorName: any; |
||||
agentName: any; |
||||
callStartTime: any; |
||||
}; |
||||
|
||||
const CallPage: FC<CallPageProps> = ({ |
||||
roomId, |
||||
visitorToken, |
||||
visitorId, |
||||
status, |
||||
setStatus, |
||||
layout, |
||||
visitorName, |
||||
agentName, |
||||
callStartTime, |
||||
}) => { |
||||
const [isAgentActive, setIsAgentActive] = useState(false); |
||||
const [isMicOn, setIsMicOn] = useState(false); |
||||
const [isCameraOn, setIsCameraOn] = useState(false); |
||||
const [isRemoteMobileDevice, setIsRemoteMobileDevice] = useState(false); |
||||
const [callInIframe, setCallInIframe] = useState(false); |
||||
const [isRemoteCameraOn, setIsRemoteCameraOn] = useState(false); |
||||
const [isLocalMobileDevice, setIsLocalMobileDevice] = useState(false); |
||||
|
||||
let iconSize = 'x21'; |
||||
let buttonSize = 'x40'; |
||||
const avatarSize = 'x48'; |
||||
if (layout === 'embedded') { |
||||
iconSize = 'x19'; |
||||
buttonSize = 'x35'; |
||||
} |
||||
|
||||
const t = useTranslation(); |
||||
useEffect(() => { |
||||
if (visitorToken) { |
||||
const webrtcInstance = WebRTC.getInstanceByRoomId(roomId, visitorId); |
||||
const isMobileDevice = (): boolean => { |
||||
if (layout === 'embedded') { |
||||
setCallInIframe(true); |
||||
} |
||||
if (window.innerWidth <= 450 && window.innerHeight >= 629 && window.innerHeight <= 900) { |
||||
setIsLocalMobileDevice(true); |
||||
webrtcInstance.media = { |
||||
audio: true, |
||||
video: { |
||||
width: { ideal: 440 }, |
||||
height: { ideal: 580 }, |
||||
}, |
||||
}; |
||||
return true; |
||||
} |
||||
return false; |
||||
}; |
||||
Notifications.onUser( |
||||
WEB_RTC_EVENTS.WEB_RTC, |
||||
(type: any, data: any) => { |
||||
if (data.room == null) { |
||||
return; |
||||
} |
||||
webrtcInstance.onUserStream(type, data); |
||||
}, |
||||
visitorId, |
||||
); |
||||
Notifications.onRoom(roomId, 'webrtc', (type: any, data: any) => { |
||||
if (type === 'callStatus' && data.callStatus === 'ended') { |
||||
webrtcInstance.stop(); |
||||
setStatus(data.callStatus); |
||||
} else if (type === 'getDeviceType') { |
||||
Notifications.notifyRoom(roomId, 'webrtc', 'deviceType', { |
||||
isMobileDevice: isMobileDevice(), |
||||
}); |
||||
} else if (type === 'cameraStatus') { |
||||
setIsRemoteCameraOn(data.isCameraOn); |
||||
} |
||||
}); |
||||
Notifications.notifyRoom(roomId, 'webrtc', 'deviceType', { |
||||
isMobileDevice: isMobileDevice(), |
||||
}); |
||||
Notifications.notifyRoom(roomId, 'webrtc', 'callStatus', { callStatus: 'inProgress' }); |
||||
} else if (!isAgentActive) { |
||||
const webrtcInstance = WebRTC.getInstanceByRoomId(roomId); |
||||
if (status === 'inProgress') { |
||||
Notifications.notifyRoom(roomId, 'webrtc', 'getDeviceType'); |
||||
webrtcInstance.startCall({ |
||||
audio: true, |
||||
video: { |
||||
width: { ideal: 1920 }, |
||||
height: { ideal: 1080 }, |
||||
}, |
||||
}); |
||||
} |
||||
Notifications.onRoom(roomId, 'webrtc', (type: any, data: any) => { |
||||
if (type === 'callStatus') { |
||||
switch (data.callStatus) { |
||||
case 'ended': |
||||
webrtcInstance.stop(); |
||||
break; |
||||
case 'inProgress': |
||||
webrtcInstance.startCall({ |
||||
audio: true, |
||||
video: { |
||||
width: { ideal: 1920 }, |
||||
height: { ideal: 1080 }, |
||||
}, |
||||
}); |
||||
} |
||||
setStatus(data.callStatus); |
||||
} else if (type === 'deviceType' && data.isMobileDevice) { |
||||
setIsRemoteMobileDevice(true); |
||||
} else if (type === 'cameraStatus') { |
||||
setIsRemoteCameraOn(data.isCameraOn); |
||||
} |
||||
}); |
||||
setIsAgentActive(true); |
||||
} |
||||
}, [isAgentActive, status, setStatus, visitorId, roomId, visitorToken, layout]); |
||||
|
||||
const toggleButton = (control: any): any => { |
||||
if (control === 'mic') { |
||||
WebRTC.getInstanceByRoomId(roomId, visitorToken).toggleAudio(); |
||||
return setIsMicOn(!isMicOn); |
||||
} |
||||
WebRTC.getInstanceByRoomId(roomId, visitorToken).toggleVideo(); |
||||
setIsCameraOn(!isCameraOn); |
||||
Notifications.notifyRoom(roomId, 'webrtc', 'cameraStatus', { isCameraOn: !isCameraOn }); |
||||
}; |
||||
|
||||
const closeWindow = (): void => { |
||||
if (layout === 'embedded') { |
||||
return (parent as any)?.handleIframeClose(); |
||||
} |
||||
return window.close(); |
||||
}; |
||||
|
||||
const getCallDuration = (callStartTime: any): any => |
||||
moment.duration(moment(new Date()).diff(moment(callStartTime))).asSeconds(); |
||||
|
||||
const showCallPage = (localAvatar: any, remoteAvatar: any): any => ( |
||||
<Flex.Container direction='column' justifyContent='center'> |
||||
<Box |
||||
width='full' |
||||
minHeight='sh' |
||||
alignItems='center' |
||||
backgroundColor='neutral-900' |
||||
overflow='hidden' |
||||
position='relative' |
||||
> |
||||
<Box |
||||
position='absolute' |
||||
zIndex={1} |
||||
style={{ |
||||
top: '5%', |
||||
right: '2%', |
||||
}} |
||||
className='Self_Video' |
||||
alignItems='center' |
||||
backgroundColor='#2F343D' |
||||
> |
||||
<video |
||||
id='localVideo' |
||||
autoPlay |
||||
playsInline |
||||
muted |
||||
style={{ |
||||
width: '100%', |
||||
transform: 'scaleX(-1)', |
||||
display: isCameraOn ? 'block' : 'none', |
||||
}} |
||||
></video> |
||||
<UserAvatar |
||||
style={{ |
||||
display: isCameraOn ? 'none' : 'block', |
||||
margin: 'auto', |
||||
}} |
||||
username={localAvatar} |
||||
className='rcx-message__avatar' |
||||
size={isLocalMobileDevice || callInIframe ? 'x32' : 'x48'} |
||||
/> |
||||
</Box> |
||||
<ButtonGroup |
||||
position='absolute' |
||||
zIndex={1} |
||||
style={{ |
||||
bottom: '5%', |
||||
}} |
||||
> |
||||
<Button |
||||
id='mic' |
||||
square |
||||
data-title={isMicOn ? t('Mute_microphone') : t('Unmute_microphone')} |
||||
onClick={(): any => toggleButton('mic')} |
||||
className={isMicOn ? 'On' : 'Off'} |
||||
size={Number(buttonSize)} |
||||
> |
||||
{isMicOn ? ( |
||||
<Icon name='mic' size={iconSize} /> |
||||
) : ( |
||||
<Icon name='mic-off' size={iconSize} /> |
||||
)} |
||||
</Button> |
||||
<Button |
||||
id='camera' |
||||
square |
||||
data-title={isCameraOn ? t('Turn_off_video') : t('Turn_on_video')} |
||||
onClick={(): void => toggleButton('camera')} |
||||
className={isCameraOn ? 'On' : 'Off'} |
||||
size={parseInt(buttonSize)} |
||||
> |
||||
{isCameraOn ? ( |
||||
<Icon name='video' size={iconSize} /> |
||||
) : ( |
||||
<Icon name='video-off' size={iconSize} /> |
||||
)} |
||||
</Button> |
||||
{layout === 'embedded' && ( |
||||
<Button |
||||
square |
||||
backgroundColor='#2F343D' |
||||
borderColor='#2F343D' |
||||
data-title={t('Expand_view')} |
||||
onClick={(): void => (parent as any)?.expandCall()} |
||||
size={parseInt(buttonSize)} |
||||
> |
||||
<Icon name='arrow-expand' size={iconSize} color='white' /> |
||||
</Button> |
||||
)} |
||||
<Button |
||||
square |
||||
primary |
||||
danger |
||||
data-title={t('End_call')} |
||||
onClick={closeWindow} |
||||
size={parseInt(buttonSize)} |
||||
> |
||||
<Icon name='phone-off' size={iconSize} color='white' /> |
||||
</Button> |
||||
</ButtonGroup> |
||||
<video |
||||
id='remoteVideo' |
||||
autoPlay |
||||
playsInline |
||||
style={{ |
||||
width: isRemoteMobileDevice ? '45%' : '100%', |
||||
transform: 'scaleX(-1)', |
||||
display: isRemoteCameraOn ? 'block' : 'none', |
||||
}} |
||||
></video> |
||||
<Box |
||||
position='absolute' |
||||
zIndex={1} |
||||
display={isRemoteCameraOn ? 'none' : 'flex'} |
||||
justifyContent='center' |
||||
flexDirection='column' |
||||
alignItems='center' |
||||
style={{ |
||||
top: isRemoteMobileDevice || isLocalMobileDevice ? '10%' : '30%', |
||||
}} |
||||
> |
||||
<UserAvatar |
||||
style={{ |
||||
display: 'block', |
||||
margin: 'auto', |
||||
}} |
||||
username={remoteAvatar} |
||||
className='rcx-message__avatar' |
||||
size={!callInIframe ? 'x124' : avatarSize} |
||||
/> |
||||
<Box color='white' fontSize={callInIframe ? 12 : 18} textAlign='center' margin={3}> |
||||
<OngoingCallDuration counter={getCallDuration(callStartTime)} /> |
||||
</Box> |
||||
<Box |
||||
style={{ |
||||
color: 'white', |
||||
fontSize: callInIframe ? 12 : 22, |
||||
margin: callInIframe ? 5 : 9, |
||||
...(callInIframe && { marginTop: 0 }), |
||||
}} |
||||
> |
||||
{remoteAvatar} |
||||
</Box> |
||||
</Box> |
||||
</Box> |
||||
</Flex.Container> |
||||
); |
||||
|
||||
return ( |
||||
<> |
||||
{status === 'ringing' && ( |
||||
<Flex.Container direction='column' justifyContent='center'> |
||||
<Box |
||||
width='full' |
||||
minHeight='sh' |
||||
alignItems='center' |
||||
backgroundColor='neutral-900' |
||||
overflow='hidden' |
||||
position='relative' |
||||
> |
||||
<Box |
||||
position='absolute' |
||||
zIndex={1} |
||||
style={{ |
||||
top: '5%', |
||||
right: '2%', |
||||
}} |
||||
className='Self_Video' |
||||
backgroundColor='#2F343D' |
||||
alignItems='center' |
||||
> |
||||
<UserAvatar |
||||
style={{ |
||||
display: 'block', |
||||
margin: 'auto', |
||||
}} |
||||
username={agentName} |
||||
className='rcx-message__avatar' |
||||
size={isLocalMobileDevice ? 'x32' : 'x48'} |
||||
/> |
||||
</Box> |
||||
<Box |
||||
position='absolute' |
||||
zIndex={1} |
||||
style={{ |
||||
top: '20%', |
||||
display: 'flex', |
||||
justifyContent: 'center', |
||||
flexDirection: 'column', |
||||
}} |
||||
alignItems='center' |
||||
> |
||||
<UserAvatar |
||||
style={{ |
||||
display: 'block', |
||||
margin: 'auto', |
||||
}} |
||||
username={visitorName} |
||||
className='rcx-message__avatar' |
||||
size='x124' |
||||
/> |
||||
<Box color='white' fontSize={16} margin={15}> |
||||
{'Calling...'} |
||||
</Box> |
||||
<Box |
||||
style={{ |
||||
color: 'white', |
||||
fontSize: isLocalMobileDevice ? 15 : 22, |
||||
}} |
||||
> |
||||
{visitorName} |
||||
</Box> |
||||
</Box> |
||||
</Box> |
||||
</Flex.Container> |
||||
)} |
||||
{status === 'declined' && ( |
||||
<Box |
||||
minHeight='90%' |
||||
display='flex' |
||||
justifyContent='center' |
||||
alignItems='center' |
||||
color='white' |
||||
fontSize='s1' |
||||
> |
||||
{t('Call_declined')} |
||||
</Box> |
||||
)} |
||||
{status === 'inProgress' && ( |
||||
<Flex.Container direction='column' justifyContent='center'> |
||||
{visitorToken |
||||
? showCallPage(visitorName, agentName) |
||||
: showCallPage(agentName, visitorName)} |
||||
</Flex.Container> |
||||
)} |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
export default CallPage; |
@ -0,0 +1,159 @@ |
||||
import { Button, Box, Icon, Flex } from '@rocket.chat/fuselage'; |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import React, { useEffect, useState, useCallback, FC } from 'react'; |
||||
|
||||
import { APIClient } from '../../../app/utils/client'; |
||||
import UserAvatar from '../../components/avatar/UserAvatar'; |
||||
import { useRouteParameter, useQueryStringParameter } from '../../contexts/RouterContext'; |
||||
import NotFoundPage from '../notFound/NotFoundPage'; |
||||
import PageLoading from '../root/PageLoading'; |
||||
import CallPage from './CallPage'; |
||||
import './styles.css'; |
||||
|
||||
const MeetPage: FC = () => { |
||||
const [isRoomMember, setIsRoomMember] = useState(false); |
||||
const [status, setStatus] = useState(null); |
||||
const [visitorId, setVisitorId] = useState(null); |
||||
const roomId = useRouteParameter('rid'); |
||||
const visitorToken = useQueryStringParameter('token'); |
||||
const layout = useQueryStringParameter('layout'); |
||||
const [visitorName, setVisitorName] = useState(''); |
||||
const [agentName, setAgentName] = useState(''); |
||||
const [callStartTime, setCallStartTime] = useState(undefined); |
||||
|
||||
const isMobileDevice = (): boolean => window.innerWidth <= 450; |
||||
const closeCallTab = (): void => window.close(); |
||||
|
||||
const setupCallForVisitor = useCallback(async () => { |
||||
const room = await APIClient.v1.get(`/livechat/room?token=${visitorToken}&rid=${roomId}`); |
||||
if (room?.room?.v?.token === visitorToken) { |
||||
setVisitorId(room.room.v._id); |
||||
setVisitorName(room.room.fname); |
||||
room?.room?.responseBy?.username |
||||
? setAgentName(room.room.responseBy.username) |
||||
: setAgentName(room.room.servedBy.username); |
||||
setStatus(room?.room?.callStatus || 'ended'); |
||||
setCallStartTime(room.room.webRtcCallStartTime); |
||||
return setIsRoomMember(true); |
||||
} |
||||
}, [visitorToken, roomId]); |
||||
|
||||
const setupCallForAgent = useCallback(async () => { |
||||
const room = await APIClient.v1.get(`/rooms.info?roomId=${roomId}`); |
||||
if (room?.room?.servedBy?._id === Meteor.userId()) { |
||||
setVisitorName(room.room.fname); |
||||
room?.room?.responseBy?.username |
||||
? setAgentName(room.room.responseBy.username) |
||||
: setAgentName(room.room.servedBy.username); |
||||
setStatus(room?.room?.callStatus || 'ended'); |
||||
setCallStartTime(room.room.webRtcCallStartTime); |
||||
return setIsRoomMember(true); |
||||
} |
||||
}, [roomId]); |
||||
|
||||
useEffect(() => { |
||||
if (visitorToken) { |
||||
setupCallForVisitor(); |
||||
return; |
||||
} |
||||
setupCallForAgent(); |
||||
}, [setupCallForAgent, setupCallForVisitor, visitorToken]); |
||||
if (status === null) { |
||||
return <PageLoading></PageLoading>; |
||||
} |
||||
if (!isRoomMember) { |
||||
return <NotFoundPage></NotFoundPage>; |
||||
} |
||||
if (status === 'ended') { |
||||
return ( |
||||
<Flex.Container direction='column' justifyContent='center'> |
||||
<Box |
||||
width='full' |
||||
minHeight='sh' |
||||
alignItems='center' |
||||
backgroundColor='neutral-900' |
||||
overflow='hidden' |
||||
position='relative' |
||||
> |
||||
<Box |
||||
position='absolute' |
||||
style={{ |
||||
top: '5%', |
||||
right: '2%', |
||||
}} |
||||
className='Self_Video' |
||||
backgroundColor='#2F343D' |
||||
alignItems='center' |
||||
> |
||||
<UserAvatar |
||||
style={{ |
||||
display: 'block', |
||||
margin: 'auto', |
||||
}} |
||||
username={visitorToken ? visitorName : agentName} |
||||
className='rcx-message__avatar' |
||||
size={isMobileDevice() ? 'x32' : 'x48'} |
||||
/> |
||||
</Box> |
||||
<Box |
||||
position='absolute' |
||||
zIndex={1} |
||||
style={{ |
||||
top: isMobileDevice() ? '30%' : '20%', |
||||
display: 'flex', |
||||
justifyContent: 'center', |
||||
flexDirection: 'column', |
||||
}} |
||||
alignItems='center' |
||||
> |
||||
<UserAvatar |
||||
style={{ |
||||
display: 'block', |
||||
margin: 'auto', |
||||
}} |
||||
username={visitorToken ? agentName : visitorName} |
||||
className='rcx-message__avatar' |
||||
size='x124' |
||||
/> |
||||
<p style={{ color: 'white', fontSize: 16, margin: 15 }}>{'Call Ended!'}</p> |
||||
<p |
||||
style={{ |
||||
color: 'white', |
||||
fontSize: isMobileDevice() ? 15 : 22, |
||||
}} |
||||
> |
||||
{visitorToken ? agentName : visitorName} |
||||
</p> |
||||
</Box> |
||||
<Box position='absolute' alignItems='center' style={{ bottom: '20%' }}> |
||||
<Button |
||||
square |
||||
title='Close Window' |
||||
onClick={closeCallTab} |
||||
backgroundColor='#2F343D' |
||||
borderColor='#2F343D' |
||||
> |
||||
<Icon name='cross' size='x16' color='white' /> |
||||
</Button> |
||||
</Box> |
||||
</Box> |
||||
</Flex.Container> |
||||
); |
||||
} |
||||
|
||||
return ( |
||||
<CallPage |
||||
roomId={roomId} |
||||
status={status} |
||||
visitorToken={visitorToken} |
||||
visitorId={visitorId} |
||||
setStatus={setStatus} |
||||
visitorName={visitorName} |
||||
agentName={agentName} |
||||
layout={layout} |
||||
callStartTime={callStartTime} |
||||
/> |
||||
); |
||||
}; |
||||
|
||||
export default MeetPage; |
@ -0,0 +1,21 @@ |
||||
import { Box } from '@rocket.chat/fuselage'; |
||||
import React, { FC, useEffect, useState } from 'react'; |
||||
|
||||
type OngoingCallDurationProps = { |
||||
counter: number; |
||||
}; |
||||
|
||||
const OngoingCallDuration: FC<OngoingCallDurationProps> = ({ counter: defaultCounter = 0 }) => { |
||||
const [counter, setCounter] = useState(defaultCounter); |
||||
useEffect(() => { |
||||
setTimeout(() => setCounter(counter + 1), 1000); |
||||
}, [counter]); |
||||
|
||||
return ( |
||||
<Box color='#E4E7EA' textAlign='center'> |
||||
{new Date(counter * 1000).toISOString().substr(11, 8)} |
||||
</Box> |
||||
); |
||||
}; |
||||
|
||||
export default OngoingCallDuration; |
@ -0,0 +1,41 @@ |
||||
.Off { |
||||
color: #ffffff !important; |
||||
border-color: #2f343d !important; |
||||
background-color: #2f343d !important; |
||||
} |
||||
|
||||
.On { |
||||
color: #000000 !important; |
||||
border-color: #ffffff !important; |
||||
background-color: #ffffff !important; |
||||
} |
||||
|
||||
.Self_Video { |
||||
display: flex; |
||||
|
||||
width: 15%; |
||||
height: 17.5%; |
||||
|
||||
justify-content: center; |
||||
} |
||||
|
||||
@media (max-width: 900px) and (min-height: 500px) { |
||||
.Self_Video { |
||||
width: 30%; |
||||
height: 20%; |
||||
} |
||||
} |
||||
|
||||
@media (max-width: 900px) and (max-height: 500px) { |
||||
.Self_Video { |
||||
width: 30%; |
||||
height: 35%; |
||||
} |
||||
} |
||||
|
||||
@media (min-width: 901px) and (max-width: 1300px) and (max-height: 500px) { |
||||
.Self_Video { |
||||
width: 20%; |
||||
height: 40%; |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
import { addMigration } from '../../lib/migrations'; |
||||
import { Settings } from '../../../app/models/server'; |
||||
import { settings } from '../../../app/settings/server'; |
||||
|
||||
addMigration({ |
||||
version: 246, |
||||
up() { |
||||
const livechatVideoCallEnabled = settings.get('Livechat_videocall_enabled'); |
||||
if (livechatVideoCallEnabled) { |
||||
Settings.upsert({ _id: 'Omnichannel_call_provider' }, { |
||||
$set: { value: 'Jitsi' }, |
||||
}); |
||||
} |
||||
Settings.removeById('Livechat_videocall_enabled'); |
||||
|
||||
const webRTCEnableChannel = settings.get('WebRTC_Enable_Channel'); |
||||
const webRTCEnableDirect = settings.get('WebRTC_Enable_Direct'); |
||||
const webRTCEnablePrivate = settings.get('WebRTC_Enable_Private'); |
||||
if (webRTCEnableChannel || webRTCEnableDirect || webRTCEnablePrivate) { |
||||
Settings.upsert({ _id: 'WebRTC_Enabled' }, { |
||||
$set: { value: true }, |
||||
}); |
||||
} |
||||
}, |
||||
}); |
Loading…
Reference in new issue