[IMPROVE] Add OTR Room States (#24565)
* WIP: OTR Room States * lint * remove logs * new OTR components, remove modals * updating stories * convert js files to ts * correct a type * add missing translation * fix review * chore: remove OTRModal * fix: review Co-authored-by: dougfabris <devfabris@gmail.com>pull/24515/head^2
parent
d1318e272f
commit
9e712ea8cc
@ -0,0 +1,9 @@ |
||||
export enum OtrRoomState { |
||||
DISABLED = 'DISABLED', |
||||
NOT_STARTED = 'NOT_STARTED', |
||||
ESTABLISHING = 'ESTABLISHING', |
||||
ESTABLISHED = 'ESTABLISHED', |
||||
ERROR = 'ERROR', |
||||
TIMEOUT = 'TIMEOUT', |
||||
DECLINED = 'DECLINED', |
||||
} |
||||
@ -1,53 +0,0 @@ |
||||
import { Box, Button, ButtonGroup, Throbber } from '@rocket.chat/fuselage'; |
||||
import React from 'react'; |
||||
|
||||
import VerticalBar from '../../../../components/VerticalBar'; |
||||
import { useTranslation } from '../../../../contexts/TranslationContext'; |
||||
|
||||
const OTR = ({ isEstablishing, isEstablished, isOnline, onClickClose, onClickStart, onClickEnd, onClickRefresh }) => { |
||||
const t = useTranslation(); |
||||
|
||||
return ( |
||||
<> |
||||
<VerticalBar.Header> |
||||
<VerticalBar.Icon name='stopwatch' /> |
||||
<VerticalBar.Text>{t('OTR')}</VerticalBar.Text> |
||||
{onClickClose && <VerticalBar.Close onClick={onClickClose} />} |
||||
</VerticalBar.Header> |
||||
|
||||
<VerticalBar.ScrollableContent p='x24'> |
||||
<Box fontScale='h4'>{t('Off_the_record_conversation')}</Box> |
||||
|
||||
{!isEstablishing && !isEstablished && isOnline && ( |
||||
<Button onClick={onClickStart} primary> |
||||
{t('Start_OTR')} |
||||
</Button> |
||||
)} |
||||
{isEstablishing && !isEstablished && isOnline && ( |
||||
<> |
||||
{' '} |
||||
<Box fontScale='p2'>{t('Please_wait_while_OTR_is_being_established')}</Box> <Throbber inheritColor />{' '} |
||||
</> |
||||
)} |
||||
{isEstablished && isOnline && ( |
||||
<ButtonGroup stretch> |
||||
{onClickRefresh && ( |
||||
<Button width='50%' onClick={onClickRefresh}> |
||||
{t('Refresh_keys')} |
||||
</Button> |
||||
)} |
||||
{onClickEnd && ( |
||||
<Button width='50%' danger onClick={onClickEnd}> |
||||
{t('End_OTR')} |
||||
</Button> |
||||
)} |
||||
</ButtonGroup> |
||||
)} |
||||
|
||||
{!isOnline && <Box fontScale='p2m'>{t('OTR_is_only_available_when_both_users_are_online')}</Box>} |
||||
</VerticalBar.ScrollableContent> |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
export default OTR; |
||||
@ -0,0 +1,89 @@ |
||||
import type { IUser } from '@rocket.chat/core-typings'; |
||||
import { Box, Button, Throbber } from '@rocket.chat/fuselage'; |
||||
import React, { MouseEventHandler, ReactElement } from 'react'; |
||||
|
||||
import { OtrRoomState } from '../../../../../app/otr/client/OtrRoomState'; |
||||
import VerticalBar from '../../../../components/VerticalBar'; |
||||
import { useTranslation } from '../../../../contexts/TranslationContext'; |
||||
import OTREstablished from './components/OTREstablished'; |
||||
import OTRStates from './components/OTRStates'; |
||||
|
||||
type OTRProps = { |
||||
isOnline: boolean; |
||||
onClickClose: MouseEventHandler<HTMLOrSVGElement>; |
||||
onClickStart: () => void; |
||||
onClickEnd: () => void; |
||||
onClickRefresh: () => void; |
||||
otrState: string; |
||||
peerUsername: IUser['username']; |
||||
}; |
||||
|
||||
const OTR = ({ isOnline, onClickClose, onClickStart, onClickEnd, onClickRefresh, otrState, peerUsername }: OTRProps): ReactElement => { |
||||
const t = useTranslation(); |
||||
|
||||
const renderOTRState = (): ReactElement => { |
||||
switch (otrState) { |
||||
case OtrRoomState.NOT_STARTED: |
||||
return ( |
||||
<Button onClick={onClickStart} primary> |
||||
{t('Start_OTR')} |
||||
</Button> |
||||
); |
||||
case OtrRoomState.ESTABLISHING: |
||||
return ( |
||||
<Box> |
||||
<Box fontScale='p2'>{t('Please_wait_while_OTR_is_being_established')}</Box> |
||||
<Box mb='x16'> |
||||
<Throbber /> |
||||
</Box> |
||||
</Box> |
||||
); |
||||
case OtrRoomState.ESTABLISHED: |
||||
return <OTREstablished onClickEnd={onClickEnd} onClickRefresh={onClickRefresh} />; |
||||
case OtrRoomState.DECLINED: |
||||
return ( |
||||
<OTRStates |
||||
title={t('OTR_Chat_Declined_Title')} |
||||
description={t('OTR_Chat_Declined_Description', peerUsername || '')} |
||||
icon='cross' |
||||
onClickStart={onClickStart} |
||||
/> |
||||
); |
||||
case OtrRoomState.TIMEOUT: |
||||
return ( |
||||
<OTRStates |
||||
title={t('OTR_Chat_Timeout_Title')} |
||||
description={t('OTR_Chat_Timeout_Description', peerUsername || '')} |
||||
icon='clock' |
||||
onClickStart={onClickStart} |
||||
/> |
||||
); |
||||
default: |
||||
return ( |
||||
<OTRStates |
||||
title={t('OTR_Chat_Error_Title')} |
||||
description={t('OTR_Chat_Error_Description')} |
||||
icon='warning' |
||||
onClickStart={onClickStart} |
||||
/> |
||||
); |
||||
} |
||||
}; |
||||
|
||||
return ( |
||||
<> |
||||
<VerticalBar.Header> |
||||
<VerticalBar.Icon name='stopwatch' /> |
||||
<VerticalBar.Text>{t('OTR')}</VerticalBar.Text> |
||||
{onClickClose && <VerticalBar.Close onClick={onClickClose} />} |
||||
</VerticalBar.Header> |
||||
|
||||
<VerticalBar.ScrollableContent p='x24'> |
||||
<Box fontScale='h4'>{t('Off_the_record_conversation')}</Box> |
||||
{isOnline ? renderOTRState() : <Box fontScale='p2m'>{t('OTR_is_only_available_when_both_users_are_online')}</Box>} |
||||
</VerticalBar.ScrollableContent> |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
export default OTR; |
||||
@ -1,30 +0,0 @@ |
||||
import { Button, Box, ButtonGroup, Icon, Modal } from '@rocket.chat/fuselage'; |
||||
import React from 'react'; |
||||
|
||||
import { useTranslation } from '../../../../contexts/TranslationContext'; |
||||
|
||||
const OTRModal = ({ onCancel, onConfirm, confirmLabel = 'Ok', ...props }) => { |
||||
const t = useTranslation(); |
||||
return ( |
||||
<Modal {...props}> |
||||
<Modal.Header> |
||||
<Modal.Title>{t('Timeout')}</Modal.Title> |
||||
<Modal.Close onClick={onCancel} /> |
||||
</Modal.Header> |
||||
<Modal.Content> |
||||
<Box textAlign='center' color='danger-500'> |
||||
<Icon size='x82' name='circle-cross' /> |
||||
</Box> |
||||
</Modal.Content> |
||||
<Modal.Footer> |
||||
<ButtonGroup align='end'> |
||||
<Button primary onClick={onConfirm}> |
||||
{confirmLabel} |
||||
</Button> |
||||
</ButtonGroup> |
||||
</Modal.Footer> |
||||
</Modal> |
||||
); |
||||
}; |
||||
|
||||
export default OTRModal; |
||||
@ -1,67 +0,0 @@ |
||||
import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; |
||||
import React, { useEffect, useMemo, useCallback } from 'react'; |
||||
|
||||
import { OTR as ORTInstance } from '../../../../../app/otr/client/rocketchat.otr'; |
||||
import { useSetModal } from '../../../../contexts/ModalContext'; |
||||
import { usePresence } from '../../../../hooks/usePresence'; |
||||
import { useReactiveValue } from '../../../../hooks/useReactiveValue'; |
||||
import OTR from './OTR'; |
||||
import OTRModal from './OTRModal'; |
||||
|
||||
const OTRWithData = ({ rid, tabBar }) => { |
||||
const onClickClose = useMutableCallback(() => tabBar && tabBar.close()); |
||||
|
||||
const setModal = useSetModal(); |
||||
const closeModal = useMutableCallback(() => setModal()); |
||||
const otr = useMemo(() => ORTInstance.getInstanceByRoomId(rid), [rid]); |
||||
|
||||
const [isEstablished, isEstablishing] = useReactiveValue( |
||||
useCallback(() => (otr ? [otr.established.get(), otr.establishing.get()] : [false, false]), [otr]), |
||||
); |
||||
|
||||
const userStatus = usePresence(otr.peerId)?.status; |
||||
|
||||
const isOnline = !['offline', 'loading'].includes(userStatus); |
||||
|
||||
const handleStart = () => { |
||||
otr.handshake(); |
||||
}; |
||||
|
||||
const handleEnd = () => otr?.end(); |
||||
|
||||
const handleReset = () => { |
||||
otr.reset(); |
||||
otr.handshake(true); |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
if (isEstablished) { |
||||
return closeModal(); |
||||
} |
||||
|
||||
if (!isEstablishing) { |
||||
return; |
||||
} |
||||
|
||||
const timeout = setTimeout(() => { |
||||
otr.establishing.set(false); |
||||
setModal(<OTRModal onConfirm={closeModal} onCancel={closeModal} />); |
||||
}, 10000); |
||||
|
||||
return () => clearTimeout(timeout); |
||||
}, [closeModal, isEstablished, isEstablishing, setModal, otr]); |
||||
|
||||
return ( |
||||
<OTR |
||||
isOnline={isOnline} |
||||
isEstablishing={isEstablishing} |
||||
isEstablished={isEstablished} |
||||
onClickClose={onClickClose} |
||||
onClickStart={handleStart} |
||||
onClickEnd={handleEnd} |
||||
onClickRefresh={handleReset} |
||||
/> |
||||
); |
||||
}; |
||||
|
||||
export default OTRWithData; |
||||
@ -0,0 +1,60 @@ |
||||
import { IRoom } from '@rocket.chat/core-typings'; |
||||
import React, { useEffect, useMemo, useCallback, ReactElement } from 'react'; |
||||
|
||||
import { OtrRoomState } from '../../../../../app/otr/client/OtrRoomState'; |
||||
import { OTR as ORTInstance } from '../../../../../app/otr/client/rocketchat.otr'; |
||||
import { usePresence } from '../../../../hooks/usePresence'; |
||||
import { useReactiveValue } from '../../../../hooks/useReactiveValue'; |
||||
import { useTabBarClose } from '../../providers/ToolboxProvider'; |
||||
import OTR from './OTR'; |
||||
|
||||
const OTRWithData = ({ rid }: { rid: IRoom['_id'] }): ReactElement => { |
||||
const closeTabBar = useTabBarClose(); |
||||
const otr = useMemo(() => ORTInstance.getInstanceByRoomId(rid), [rid]); |
||||
const otrState = useReactiveValue(useCallback(() => (otr ? otr.state.get() : OtrRoomState.ERROR), [otr])); |
||||
const peerUserPresence = usePresence(otr.peerId); |
||||
const userStatus = peerUserPresence?.status; |
||||
const peerUsername = peerUserPresence?.username; |
||||
const isOnline = !['offline', 'loading'].includes(userStatus || ''); |
||||
|
||||
const handleStart = (): void => { |
||||
otr.handshake(); |
||||
}; |
||||
|
||||
const handleEnd = (): void => { |
||||
otr?.end(); |
||||
}; |
||||
|
||||
const handleReset = (): void => { |
||||
otr.reset(); |
||||
otr.handshake(true); |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
if (otrState !== OtrRoomState.ESTABLISHING) { |
||||
return; |
||||
} |
||||
|
||||
const timeout = setTimeout(() => { |
||||
otr.state.set(OtrRoomState.TIMEOUT); |
||||
}, 10000); |
||||
|
||||
return (): void => { |
||||
clearTimeout(timeout); |
||||
}; |
||||
}, [otr, otrState]); |
||||
|
||||
return ( |
||||
<OTR |
||||
isOnline={isOnline} |
||||
onClickClose={closeTabBar} |
||||
onClickStart={handleStart} |
||||
onClickEnd={handleEnd} |
||||
onClickRefresh={handleReset} |
||||
otrState={otrState} |
||||
peerUsername={peerUsername} |
||||
/> |
||||
); |
||||
}; |
||||
|
||||
export default OTRWithData; |
||||
@ -0,0 +1,24 @@ |
||||
import { Button, ButtonGroup } from '@rocket.chat/fuselage'; |
||||
import React, { ReactElement } from 'react'; |
||||
|
||||
import { useTranslation } from '../../../../../contexts/TranslationContext'; |
||||
|
||||
type OTREstablishedProps = { |
||||
onClickRefresh: () => void; |
||||
onClickEnd: () => void; |
||||
}; |
||||
|
||||
const OTREstablished = ({ onClickRefresh, onClickEnd }: OTREstablishedProps): ReactElement => { |
||||
const t = useTranslation(); |
||||
|
||||
return ( |
||||
<ButtonGroup stretch> |
||||
<Button onClick={onClickRefresh}>{t('Refresh_keys')}</Button> |
||||
<Button danger onClick={onClickEnd}> |
||||
{t('End_OTR')} |
||||
</Button> |
||||
</ButtonGroup> |
||||
); |
||||
}; |
||||
|
||||
export default OTREstablished; |
||||
@ -0,0 +1,28 @@ |
||||
import { Icon, States, StatesAction, StatesActions, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage'; |
||||
import React, { ReactElement, ComponentProps } from 'react'; |
||||
|
||||
import { useTranslation } from '../../../../../contexts/TranslationContext'; |
||||
|
||||
type OTRStatesProps = { |
||||
title: string; |
||||
description: string; |
||||
icon: ComponentProps<typeof Icon>['name']; |
||||
onClickStart: () => void; |
||||
}; |
||||
|
||||
const OTRStates = ({ title, description, icon, onClickStart }: OTRStatesProps): ReactElement => { |
||||
const t = useTranslation(); |
||||
|
||||
return ( |
||||
<States> |
||||
<StatesIcon name={icon} /> |
||||
<StatesTitle>{title}</StatesTitle> |
||||
<StatesSubtitle>{description}</StatesSubtitle> |
||||
<StatesActions> |
||||
<StatesAction onClick={onClickStart}>{t('New_OTR_Chat')}</StatesAction> |
||||
</StatesActions> |
||||
</States> |
||||
); |
||||
}; |
||||
|
||||
export default OTRStates; |
||||
Loading…
Reference in new issue