chitchatter/src/components/Shell/PeerListItem.tsx
Jeremy Kahn 6cbfaacf1a
feat(verification): [closes #209] Verified peers (#216)
* 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
2023-12-09 17:47:05 -06:00

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>
</>
)
}