
* refactor(bootstrap): add BootstrapShim * feat(security): [#209] generate public/private keys * refactor(encryption): move encryption utils to a service * feat(encryption): [wip] implement convertCryptoKeyToString * fix(user-settings): serialize crypto keys to strings * feat(user-settings): deserialize user settings from IndexedDB * feat(user-settings): upgrade persisted settings on boot * feat(user-settings): automatically migrate persisted user settings * refactor(encryption): simplify CryptoKey stringification * refactor(encryption): DRY up EncryptionService * feat(verification): send public key to new peers * refactor(encryption): use class instance * refactor(serialization): use class instance * refactor(verification): [wip] create usePeerVerification hook * feat(verification): encrypt verification token * feat(verification): send encrypted token to peer * feat(verification): verify peer * refactor(verification): use enum for verification state * feat(verification): expire verification requests * fix(updatePeer): update with fresh state data * feat(verification): display verification state * refactor(usePeerVerification): store verification timer in Peer * feat(verification): present tooltips explaining verification state * feat(ui): show full page loading indicator * feat(init): present bootup failure reasons * refactor(init): move init to its own file * feat(verification): show errors upon verification failure * refactor(verification): move workaround to usePeerVerification * feat(verification): present peer public keys * refactor(verification): move peer public key rendering to its own component * refactor(verification): only pass publicKey into renderer * feat(verification): show user's own public key * refactor(naming): rename Username to UserInfo * refactor(loading): encapsulate height styling * feat(verification): improve user messaging * refactor(style): improve formatting and variable names * feat(verification): add user info tooltip * docs(verification): explain verification
156 lines
5.2 KiB
TypeScript
156 lines
5.2 KiB
TypeScript
import { useState } from 'react'
|
|
import Box from '@mui/material/Box'
|
|
import ListItemText from '@mui/material/ListItemText'
|
|
import SyncAltIcon from '@mui/icons-material/SyncAlt'
|
|
import NetworkPingIcon from '@mui/icons-material/NetworkPing'
|
|
import ListItem from '@mui/material/ListItem'
|
|
import Tooltip from '@mui/material/Tooltip'
|
|
import CircularProgress from '@mui/material/CircularProgress'
|
|
import Button from '@mui/material/Button'
|
|
import Dialog from '@mui/material/Dialog'
|
|
import DialogActions from '@mui/material/DialogActions'
|
|
import DialogContent from '@mui/material/DialogContent'
|
|
import DialogContentText from '@mui/material/DialogContentText'
|
|
import DialogTitle from '@mui/material/DialogTitle'
|
|
import NoEncryptionIcon from '@mui/icons-material/NoEncryption'
|
|
import EnhancedEncryptionIcon from '@mui/icons-material/EnhancedEncryption'
|
|
|
|
import { AudioVolume } from 'components/AudioVolume'
|
|
import { PeerNameDisplay } from 'components/PeerNameDisplay'
|
|
import { PublicKey } from 'components/PublicKey'
|
|
import { Peer, PeerVerificationState } from 'models/chat'
|
|
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
|
|
|
|
import { PeerDownloadFileButton } from './PeerDownloadFileButton'
|
|
|
|
interface PeerListItemProps {
|
|
peer: Peer
|
|
peerConnectionTypes: Record<string, PeerConnectionType>
|
|
peerAudios: Record<string, HTMLAudioElement>
|
|
}
|
|
|
|
const verificationStateDisplayMap = {
|
|
[PeerVerificationState.UNVERIFIED]: (
|
|
<Tooltip title="This person could not be verified with public-key cryptography. They may be misrepresenting themself. Be careful with what you share with them.">
|
|
<NoEncryptionIcon color="error" />
|
|
</Tooltip>
|
|
),
|
|
[PeerVerificationState.VERIFIED]: (
|
|
<Tooltip title="This person has been verified with public-key cryptography">
|
|
<EnhancedEncryptionIcon color="success" />
|
|
</Tooltip>
|
|
),
|
|
[PeerVerificationState.VERIFYING]: (
|
|
<Tooltip title="Attempting to verify this person...">
|
|
<CircularProgress size={16} sx={{ position: 'relative', top: 3 }} />
|
|
</Tooltip>
|
|
),
|
|
}
|
|
|
|
const iconRightPadding = 1
|
|
|
|
export const PeerListItem = ({
|
|
peer,
|
|
peerConnectionTypes,
|
|
peerAudios,
|
|
}: PeerListItemProps): JSX.Element => {
|
|
const [showPeerDialog, setShowPeerDialog] = useState(false)
|
|
|
|
const hasPeerConnection = peer.peerId in peerConnectionTypes
|
|
|
|
const isPeerConnectionDirect =
|
|
peerConnectionTypes[peer.peerId] === PeerConnectionType.DIRECT
|
|
|
|
const handleListItemClick = () => {
|
|
setShowPeerDialog(true)
|
|
}
|
|
|
|
const handleDialogClose = () => {
|
|
setShowPeerDialog(false)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<ListItem
|
|
key={peer.peerId}
|
|
divider={true}
|
|
onClick={handleListItemClick}
|
|
sx={{ cursor: 'pointer' }}
|
|
>
|
|
<PeerDownloadFileButton peer={peer} />
|
|
<ListItemText
|
|
primaryTypographyProps={{
|
|
sx: { display: 'flex', alignContent: 'center' },
|
|
}}
|
|
>
|
|
{hasPeerConnection ? (
|
|
<Tooltip
|
|
title={
|
|
isPeerConnectionDirect ? (
|
|
<>
|
|
You are connected directly to{' '}
|
|
<PeerNameDisplay
|
|
sx={{ fontSize: 'inherit', fontWeight: 'inherit' }}
|
|
>
|
|
{peer.userId}
|
|
</PeerNameDisplay>
|
|
</>
|
|
) : (
|
|
<>
|
|
You are connected to{' '}
|
|
<PeerNameDisplay
|
|
sx={{ fontSize: 'inherit', fontWeight: 'inherit' }}
|
|
>
|
|
{peer.userId}
|
|
</PeerNameDisplay>{' '}
|
|
via a relay server. Your connection is still private and
|
|
encrypted, but performance may be degraded.
|
|
</>
|
|
)
|
|
}
|
|
>
|
|
<Box
|
|
component="span"
|
|
sx={{ pr: iconRightPadding, cursor: 'pointer' }}
|
|
>
|
|
{isPeerConnectionDirect ? (
|
|
<SyncAltIcon color="success" />
|
|
) : (
|
|
<NetworkPingIcon color="warning" />
|
|
)}
|
|
</Box>
|
|
</Tooltip>
|
|
) : null}
|
|
<Box
|
|
component="span"
|
|
sx={{ pr: iconRightPadding, cursor: 'pointer' }}
|
|
>
|
|
{verificationStateDisplayMap[peer.verificationState]}
|
|
</Box>
|
|
<PeerNameDisplay>{peer.userId}</PeerNameDisplay>
|
|
{peer.peerId in peerAudios && (
|
|
<AudioVolume audioEl={peerAudios[peer.peerId]} />
|
|
)}
|
|
</ListItemText>
|
|
</ListItem>
|
|
<Dialog open={showPeerDialog} onClose={handleDialogClose}>
|
|
<DialogTitle sx={{ display: 'flex', alignItems: 'center' }}>
|
|
{verificationStateDisplayMap[peer.verificationState]}
|
|
<Box component="span" sx={{ ml: 1 }}>
|
|
<PeerNameDisplay sx={{ fontSize: 'inherit' }}>
|
|
{peer.userId}
|
|
</PeerNameDisplay>
|
|
</Box>
|
|
</DialogTitle>
|
|
<DialogContent>
|
|
<DialogContentText>Their public key:</DialogContentText>
|
|
<PublicKey publicKey={peer.publicKey} />
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={handleDialogClose}>Close</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
</>
|
|
)
|
|
}
|