Refactor Encrypted chat to component (#297)

* Refactor Encrypted chat to component

* Rebase

* CR

* Better performance

* Bad performance

* Fix styles

Co-authored-by: Reckless_Satoshi <reckless.satoshi@protonmail.com>
This commit is contained in:
KoalaSat 2022-10-30 19:02:27 +00:00 committed by GitHub
parent 1502f9555b
commit 5e6f7165d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 527 additions and 569 deletions

View File

@ -1,565 +0,0 @@
import React, { Component } from 'react';
import { withTranslation } from 'react-i18next';
import {
Button,
IconButton,
Badge,
Tooltip,
TextField,
Grid,
Container,
Card,
CardHeader,
Paper,
Avatar,
Typography,
} from '@mui/material';
import ReconnectingWebSocket from 'reconnecting-websocket';
import { encryptMessage, decryptMessage } from '../../utils/pgp';
import { saveAsJson } from '../../utils/saveFile';
import { AuditPGPDialog } from '../Dialogs';
import RobotAvatar from '../RobotAvatar';
import { systemClient } from '../../services/System';
import { websocketClient } from '../../services/Websocket';
// Icons
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
import ContentCopy from '@mui/icons-material/ContentCopy';
import VisibilityIcon from '@mui/icons-material/Visibility';
import CircularProgress from '@mui/material/CircularProgress';
import KeyIcon from '@mui/icons-material/Key';
import { ExportIcon } from '../Icons';
class Chat extends Component {
constructor(props) {
super(props);
}
state = {
own_pub_key: systemClient.getCookie('pub_key').split('\\').join('\n'),
own_enc_priv_key: systemClient.getCookie('enc_priv_key').split('\\').join('\n'),
peer_pub_key: null,
token: systemClient.getCookie('robot_token'),
messages: [],
value: '',
connected: false,
connection: null,
peer_connected: false,
audit: false,
showPGP: new Array(),
waitingEcho: false,
lastSent: '---BLANK---',
latestIndex: 0,
scrollNow: false,
};
componentDidMount() {
websocketClient
.open(`ws://${window.location.host}/ws/chat/${this.props.orderId}/`)
.then((connection) => {
console.log('Connected!');
connection.send({
message: this.state.own_pub_key,
nick: this.props.ur_nick,
});
connection.onMessage(this.onMessage);
connection.onClose(() => {
console.log('Socket is closed. Reconnect will be attempted');
this.setState({ connected: false });
});
connection.onError(() => {
console.error('Socket encountered error: Closing socket');
this.setState({ connected: false });
});
this.setState({ connected: true, connection });
});
}
componentDidUpdate() {
// Only fire the scroll and audio when the reason for Update is a new message
if (this.state.scrollNow) {
const audio = new Audio(`/static/assets/sounds/chat-open.mp3`);
audio.play();
this.scrollToBottom();
this.setState({ scrollNow: false });
}
}
onMessage = (message) => {
const dataFromServer = JSON.parse(message.data);
console.log('Got reply!', dataFromServer.type);
console.log('PGP message index', dataFromServer.index, ' latestIndex ', this.state.latestIndex);
if (dataFromServer) {
console.log(dataFromServer);
this.setState({ peer_connected: dataFromServer.peer_connected });
// If we receive our own key on a message
if (dataFromServer.message == this.state.own_pub_key) {
console.log('OWN PUB KEY RECEIVED!!');
}
// If we receive a public key other than ours (our peer key!)
if (
dataFromServer.message.substring(0, 36) == `-----BEGIN PGP PUBLIC KEY BLOCK-----` &&
dataFromServer.message != this.state.own_pub_key
) {
if (dataFromServer.message == this.state.peer_pub_key) {
console.log('PEER HAS RECONNECTED USING HIS PREVIOUSLY KNOWN PUBKEY');
} else if (
(dataFromServer.message != this.state.peer_pub_key) &
(this.state.peer_pub_key != null)
) {
console.log('PEER PUBKEY HAS CHANGED');
}
console.log('PEER PUBKEY RECEIVED!!');
this.setState({ peer_pub_key: dataFromServer.message });
// After receiving the peer pubkey we ask the server for the historic messages if any
this.state.connection.send({
message: `-----SERVE HISTORY-----`,
nick: this.props.ur_nick,
});
}
// If we receive an encrypted message
else if (
dataFromServer.message.substring(0, 27) == `-----BEGIN PGP MESSAGE-----` &&
dataFromServer.index > this.state.latestIndex
) {
decryptMessage(
dataFromServer.message.split('\\').join('\n'),
dataFromServer.user_nick == this.props.ur_nick
? this.state.own_pub_key
: this.state.peer_pub_key,
this.state.own_enc_priv_key,
this.state.token,
).then((decryptedData) =>
this.setState((state) => ({
scrollNow: true,
waitingEcho:
this.state.waitingEcho == true
? decryptedData.decryptedMessage != this.state.lastSent
: false,
lastSent:
decryptedData.decryptedMessage == this.state.lastSent
? '----BLANK----'
: this.state.lastSent,
latestIndex:
dataFromServer.index > this.state.latestIndex
? dataFromServer.index
: this.state.latestIndex,
messages: [
...state.messages,
{
index: dataFromServer.index,
encryptedMessage: dataFromServer.message.split('\\').join('\n'),
plainTextMessage: decryptedData.decryptedMessage,
validSignature: decryptedData.validSignature,
userNick: dataFromServer.user_nick,
time: dataFromServer.time,
},
].sort(function (a, b) {
// order the message array by their index (increasing)
return a.index - b.index;
}),
})),
);
}
// We allow plaintext communication. The user must write # to start
// If we receive an plaintext message
else if (dataFromServer.message.substring(0, 1) == '#') {
console.log('Got plaintext message', dataFromServer.message);
this.setState((state) => ({
scrollNow: true,
messages: [
...state.messages,
{
index: this.state.latestIndex + 0.001,
encryptedMessage: dataFromServer.message,
plainTextMessage: dataFromServer.message,
validSignature: false,
userNick: dataFromServer.user_nick,
time: new Date().toString(),
},
],
}));
}
}
};
scrollToBottom = () => {
this.messagesEnd.scrollIntoView({ behavior: 'smooth' });
};
onButtonClicked = (e) => {
// If input string contains token. Do not set message
if (this.state.value.indexOf(this.state.token) !== -1) {
alert(
`Aye! You just sent your own robot token to your peer in chat, that's a catastrophic idea! So bad your message was blocked.`,
);
this.setState({ value: '' });
}
// If input string contains '#' send unencrypted and unlogged message
else if (this.state.value.substring(0, 1) == '#') {
this.state.connection.send({
message: this.state.value,
nick: this.props.ur_nick,
});
this.setState({ value: '' });
}
// Else if message is not empty send message
else if (this.state.value != '') {
this.setState({ value: '', waitingEcho: true, lastSent: this.state.value });
encryptMessage(
this.state.value,
this.state.own_pub_key,
this.state.peer_pub_key,
this.state.own_enc_priv_key,
this.state.token,
).then(
(encryptedMessage) =>
console.log('Sending Encrypted MESSAGE', encryptedMessage) &
this.state.connection.send({
message: encryptedMessage.split('\n').join('\\'),
nick: this.props.ur_nick,
}),
);
}
e.preventDefault();
};
createJsonFile = () => {
return {
credentials: {
own_public_key: this.state.own_pub_key,
peer_public_key: this.state.peer_pub_key,
encrypted_private_key: this.state.own_enc_priv_key,
passphrase: this.state.token,
},
messages: this.state.messages,
};
};
messageCard = (props) => {
const { t } = this.props;
return (
<Card elevation={5} align='left'>
<CardHeader
sx={{ color: '#333333' }}
avatar={
<RobotAvatar
statusColor={props.userConnected ? 'success' : 'error'}
nickname={props.message.userNick}
/>
}
style={{ backgroundColor: props.cardColor }}
title={
<Tooltip
placement='top'
enterTouchDelay={0}
enterDelay={500}
enterNextDelay={2000}
title={t(
props.message.validSignature
? 'Verified signature by {{nickname}}'
: 'Cannot verify signature of {{nickname}}',
{ nickname: props.message.userNick },
)}
>
<div
style={{
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
position: 'relative',
left: -5,
width: 240,
}}
>
<div
style={{ width: 168, display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}
>
{props.message.userNick}
{props.message.validSignature ? (
<CheckIcon sx={{ height: 16 }} color='success' />
) : (
<CloseIcon sx={{ height: 16 }} color='error' />
)}
</div>
<div style={{ width: 20 }}>
<IconButton
sx={{ height: 18, width: 18 }}
onClick={() =>
this.setState((prevState) => {
const newShowPGP = [...prevState.showPGP];
newShowPGP[props.index] = !newShowPGP[props.index];
return { showPGP: newShowPGP };
})
}
>
<VisibilityIcon
color={this.state.showPGP[props.index] ? 'primary' : 'inherit'}
sx={{
height: 16,
width: 16,
color: this.state.showPGP[props.index] ? 'primary' : '#333333',
}}
/>
</IconButton>
</div>
<div style={{ width: 20 }}>
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
<IconButton
sx={{ height: 18, width: 18 }}
onClick={() =>
systemClient.copyToClipboard(
this.state.showPGP[props.index]
? props.message.encryptedMessage
: props.message.plainTextMessage,
)
}
>
<ContentCopy sx={{ height: 16, width: 16, color: '#333333' }} />
</IconButton>
</Tooltip>
</div>
</div>
</Tooltip>
}
subheader={
this.state.showPGP[props.index] ? (
<a>
{' '}
{props.message.time} <br /> {'Valid signature: ' + props.message.validSignature}{' '}
<br /> {props.message.encryptedMessage}{' '}
</a>
) : (
props.message.plainTextMessage
)
}
subheaderTypographyProps={{
sx: {
wordWrap: 'break-word',
width: '200px',
color: '#444444',
fontSize: this.state.showPGP[props.index] ? 11 : null,
},
}}
/>
</Card>
);
};
render() {
const { t } = this.props;
return (
<Container component='main'>
<Grid container spacing={0.5}>
<Grid item xs={0.3} />
<Grid item xs={5.5}>
<Paper
elevation={1}
style={
this.state.connected
? { backgroundColor: '#e8ffe6' }
: { backgroundColor: '#FFF1C5' }
}
>
<Typography variant='caption' sx={{ color: '#333333' }}>
{t('You') + ': '}
{this.state.connected ? t('connected') : t('disconnected')}
</Typography>
</Paper>
</Grid>
<Grid item xs={0.4} />
<Grid item xs={5.5}>
<Paper
elevation={1}
style={
this.state.peer_connected
? { backgroundColor: '#e8ffe6' }
: { backgroundColor: '#FFF1C5' }
}
>
<Typography variant='caption' sx={{ color: '#333333' }}>
{t('Peer') + ': '}
{this.state.peer_connected ? t('connected') : t('disconnected')}
</Typography>
</Paper>
</Grid>
<Grid item xs={0.3} />
</Grid>
<div style={{ position: 'relative', left: '-2px', margin: '0 auto', width: '285px' }}>
<Paper
elevation={1}
style={{
height: '300px',
maxHeight: '300px',
width: '285px',
overflow: 'auto',
backgroundColor: '#F7F7F7',
}}
>
{this.state.messages.map((message, index) => (
<li style={{ listStyleType: 'none' }} key={index}>
{message.userNick == this.props.ur_nick ? (
<this.messageCard
message={message}
index={index}
cardColor={'#eeeeee'}
userConnected={this.state.connected}
/>
) : (
<this.messageCard
message={message}
index={index}
cardColor={'#fafafa'}
userConnected={this.state.peer_connected}
/>
)}
</li>
))}
<div
style={{ float: 'left', clear: 'both' }}
ref={(el) => {
this.messagesEnd = el;
}}
></div>
</Paper>
<form noValidate onSubmit={this.onButtonClicked}>
<Grid alignItems='stretch' style={{ display: 'flex' }}>
<Grid item alignItems='stretch' style={{ display: 'flex' }}>
<TextField
label={t('Type a message')}
variant='standard'
size='small'
helperText={
this.state.connected
? this.state.peer_pub_key
? null
: t('Waiting for peer public key...')
: t('Connecting...')
}
value={this.state.value}
onChange={(e) => {
this.setState({ value: e.target.value });
this.value = this.state.value;
}}
sx={{ width: 219 }}
/>
</Grid>
<Grid item alignItems='stretch' style={{ display: 'flex' }}>
<Button
sx={{ width: 68 }}
disabled={
!this.state.connected ||
this.state.waitingEcho ||
this.state.peer_pub_key == null
}
type='submit'
variant='contained'
color='primary'
>
{this.state.waitingEcho ? (
<div
style={{
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
minWidth: 68,
width: 68,
position: 'relative',
left: 15,
}}
>
<div style={{ width: 20 }}>
<KeyIcon sx={{ width: 18 }} />
</div>
<div style={{ width: 18 }}>
<CircularProgress size={16} thickness={5} />
</div>
</div>
) : (
t('Send')
)}
</Button>
</Grid>
</Grid>
</form>
</div>
<div style={{ height: 4 }} />
<Grid container spacing={0}>
<AuditPGPDialog
open={this.state.audit}
onClose={() => this.setState({ audit: false })}
orderId={Number(this.props.orderId)}
messages={this.state.messages}
own_pub_key={this.state.own_pub_key}
own_enc_priv_key={this.state.own_enc_priv_key}
peer_pub_key={this.state.peer_pub_key ? this.state.peer_pub_key : 'Not received yet'}
passphrase={this.state.token}
onClickBack={() => this.setState({ audit: false })}
/>
<Grid item xs={6}>
<Tooltip
placement='bottom'
enterTouchDelay={0}
enterDelay={500}
enterNextDelay={2000}
title={t('Verify your privacy')}
>
<Button
size='small'
color='primary'
variant='outlined'
onClick={() => this.setState({ audit: !this.state.audit })}
>
<KeyIcon />
{t('Audit PGP')}{' '}
</Button>
</Tooltip>
</Grid>
<Grid item xs={6}>
<Tooltip
placement='bottom'
enterTouchDelay={0}
enterDelay={500}
enterNextDelay={2000}
title={t('Save full log as a JSON file (messages and credentials)')}
>
<Button
size='small'
color='primary'
variant='outlined'
onClick={() =>
saveAsJson(
'complete_log_chat_' + this.props.orderId + '.json',
this.createJsonFile(),
)
}
>
<div style={{ width: 28, height: 20 }}>
<ExportIcon sx={{ width: 20, height: 20 }} />
</div>{' '}
{t('Export')}{' '}
</Button>
</Tooltip>
</Grid>
</Grid>
</Container>
);
}
}
export default withTranslation()(Chat);

View File

@ -0,0 +1,523 @@
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
Button,
IconButton,
Tooltip,
TextField,
Grid,
Container,
Card,
CardHeader,
Paper,
Typography,
} from '@mui/material';
import { encryptMessage, decryptMessage } from '../../../utils/pgp';
import { saveAsJson } from '../../../utils/saveFile';
import { AuditPGPDialog } from '../../Dialogs';
import RobotAvatar from '../../RobotAvatar';
import { systemClient } from '../../../services/System';
import { websocketClient, WebsocketConnection } from '../../../services/Websocket';
// Icons
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
import ContentCopy from '@mui/icons-material/ContentCopy';
import VisibilityIcon from '@mui/icons-material/Visibility';
import CircularProgress from '@mui/material/CircularProgress';
import KeyIcon from '@mui/icons-material/Key';
import { ExportIcon } from '../../Icons';
import { useTheme } from '@mui/system';
interface Props {
orderId: number;
userNick: string;
}
interface EncryptedChatMessage {
userNick: string;
validSignature: boolean;
plainTextMessage: string;
encryptedMessage: string;
time: string;
index: number;
}
const EncryptedChat: React.FC<Props> = ({ orderId, userNick }: Props): JSX.Element => {
const { t } = useTranslation();
const theme = useTheme();
const audio = new Audio(`/static/assets/sounds/chat-open.mp3`);
const [connected, setConnected] = useState<boolean>(false);
const [peerConnected, setPeerConnected] = useState<boolean>(false);
const [ownPubKey] = useState<string>(
(systemClient.getCookie('pub_key') ?? '').split('\\').join('\n'),
);
const [ownEncPrivKey] = useState<string>(
(systemClient.getCookie('enc_priv_key') ?? '').split('\\').join('\n'),
);
const [peerPubKey, setPeerPubKey] = useState<string>();
const [token] = useState<string>(systemClient.getCookie('robot_token') || '');
const [messages, setMessages] = useState<EncryptedChatMessage[]>([]);
const [serverMessages, setServerMessages] = useState<any[]>([]);
const [value, setValue] = useState<string>('');
const [connection, setConnection] = useState<WebsocketConnection>();
const [audit, setAudit] = useState<boolean>(false);
const [showPGP, setShowPGP] = useState<boolean[]>([]);
const [waitingEcho, setWaitingEcho] = useState<boolean>(false);
const [lastSent, setLastSent] = useState<string>('---BLANK---');
const [messageCount, setMessageCount] = useState<number>(0);
const [receivedIndexes, setReceivedIndexes] = useState<number[]>([]);
useEffect(() => {
if (!connected) {
connectWebsocket();
}
}, [connected]);
useEffect(() => {
if (messages.length > messageCount) {
audio.play();
setMessageCount(messages.length);
}
}, [messages, messageCount]);
useEffect(() => {
if (serverMessages) {
serverMessages.forEach(onMessage);
}
}, [serverMessages]);
const connectWebsocket = () => {
websocketClient.open(`ws://${window.location.host}/ws/chat/${orderId}/`).then((connection) => {
setConnection(connection);
setConnected(true);
connection.send({
message: ownPubKey,
nick: userNick,
});
connection.onMessage((message) => setServerMessages((prev) => [...prev, message]));
connection.onClose(() => setConnected(false));
connection.onError(() => setConnected(false));
});
};
const createJsonFile: () => object = () => {
return {
credentials: {
own_public_key: ownPubKey,
peer_public_key: peerPubKey,
encrypted_private_key: ownEncPrivKey,
passphrase: token,
},
messages: messages,
};
};
const onMessage: (message: any) => void = (message) => {
const dataFromServer = JSON.parse(message.data);
if (dataFromServer && !receivedIndexes.includes(dataFromServer.index)) {
setReceivedIndexes((prev) => [...prev, dataFromServer.index]);
setPeerConnected(dataFromServer.peer_connected);
// If we receive a public key other than ours (our peer key!)
if (
connection &&
dataFromServer.message.substring(0, 36) == `-----BEGIN PGP PUBLIC KEY BLOCK-----` &&
dataFromServer.message != ownPubKey
) {
setPeerPubKey(dataFromServer.message);
connection.send({
message: `-----SERVE HISTORY-----`,
nick: userNick,
});
}
// If we receive an encrypted message
else if (dataFromServer.message.substring(0, 27) == `-----BEGIN PGP MESSAGE-----`) {
decryptMessage(
dataFromServer.message.split('\\').join('\n'),
dataFromServer.user_nick == userNick ? ownPubKey : peerPubKey,
ownEncPrivKey,
token,
).then((decryptedData) => {
setWaitingEcho(waitingEcho ? decryptedData.decryptedMessage !== lastSent : false);
setLastSent(decryptedData.decryptedMessage === lastSent ? '----BLANK----' : lastSent);
setMessages((prev) => {
const existingMessage = prev.find((item) => item.index === dataFromServer.index);
if (existingMessage) {
return prev;
} else {
return [
...prev,
{
index: dataFromServer.index,
encryptedMessage: dataFromServer.message.split('\\').join('\n'),
plainTextMessage: decryptedData.decryptedMessage,
validSignature: decryptedData.validSignature,
userNick: dataFromServer.user_nick,
time: dataFromServer.time,
} as EncryptedChatMessage,
].sort((a, b) => a.index - b.index);
}
});
});
}
// We allow plaintext communication. The user must write # to start
// If we receive an plaintext message
else if (dataFromServer.message.substring(0, 1) == '#') {
setMessages((prev) => {
const existingMessage = prev.find(
(item) => item.plainTextMessage === dataFromServer.message,
);
if (existingMessage) {
return prev;
} else {
return [
...prev,
{
index: prev.length + 0.001,
encryptedMessage: dataFromServer.message,
plainTextMessage: dataFromServer.message,
validSignature: false,
userNick: dataFromServer.user_nick,
time: new Date().toString(),
} as EncryptedChatMessage,
].sort((a, b) => a.index - b.index);
}
});
}
}
};
const onButtonClicked = (e: any) => {
if (token && value.indexOf(token) !== -1) {
alert(
`Aye! You just sent your own robot token to your peer in chat, that's a catastrophic idea! So bad your message was blocked.`,
);
setValue('');
}
// If input string contains '#' send unencrypted and unlogged message
else if (connection && value.substring(0, 1) == '#') {
connection.send({
message: value,
nick: userNick,
});
setValue('');
}
// Else if message is not empty send message
else if (value != '') {
setValue('');
setWaitingEcho(true);
setLastSent(value);
encryptMessage(value, ownPubKey, peerPubKey, ownEncPrivKey, token).then(
(encryptedMessage) => {
if (connection) {
connection.send({
message: encryptedMessage.toString().split('\n').join('\\'),
nick: userNick,
});
}
},
);
}
e.preventDefault();
};
const messageCard: (
message: EncryptedChatMessage,
index: number,
cardColor: string,
userConnected: boolean,
) => JSX.Element = (message, index, cardColor, userConnected) => {
return (
<Card elevation={5}>
<CardHeader
sx={{ color: theme.palette.text.secondary }}
avatar={
<RobotAvatar
statusColor={userConnected ? 'success' : 'error'}
nickname={message.userNick}
/>
}
style={{ backgroundColor: cardColor }}
title={
<Tooltip
placement='top'
enterTouchDelay={0}
enterDelay={500}
enterNextDelay={2000}
title={t(
message.validSignature
? 'Verified signature by {{nickname}}'
: 'Cannot verify signature of {{nickname}}',
{ nickname: message.userNick },
)}
>
<div
style={{
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
position: 'relative',
left: '-0.35em',
width: '17.14em',
}}
>
<div
style={{
width: '11.78em',
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
}}
>
{message.userNick}
{message.validSignature ? (
<CheckIcon sx={{ height: '0.8em' }} color='success' />
) : (
<CloseIcon sx={{ height: '0.8em' }} color='error' />
)}
</div>
<div style={{ width: '1.4em' }}>
<IconButton
sx={{ height: '1.2em', width: '1.2em', position: 'relative', right: '0.15em' }}
onClick={() => {
const newShowPGP = [...showPGP];
newShowPGP[index] = !newShowPGP[index];
setShowPGP(newShowPGP);
}}
>
<VisibilityIcon
color={showPGP[index] ? 'primary' : 'inherit'}
sx={{
height: '0.6em',
width: '0.6em',
color: showPGP[index] ? 'primary' : theme.palette.text.secondary,
}}
/>
</IconButton>
</div>
<div style={{ width: '1.4em' }}>
<Tooltip disableHoverListener enterTouchDelay={0} title={t('Copied!')}>
<IconButton
sx={{ height: '0.8em', width: '0.8em' }}
onClick={() =>
systemClient.copyToClipboard(
showPGP[index] ? message.encryptedMessage : message.plainTextMessage,
)
}
>
<ContentCopy
sx={{
height: '0.7em',
width: '0.7em',
color: theme.palette.text.secondary,
}}
/>
</IconButton>
</Tooltip>
</div>
</div>
</Tooltip>
}
subheader={
showPGP[index] ? (
<a>
{' '}
{message.time} <br /> {'Valid signature: ' + message.validSignature} <br />{' '}
{message.encryptedMessage}{' '}
</a>
) : (
message.plainTextMessage
)
}
subheaderTypographyProps={{
sx: {
wordWrap: 'break-word',
width: '14.3em',
position: 'relative',
right: '1.5em',
textAlign: 'left',
fontSize: showPGP[index] ? theme.typography.fontSize * 0.78 : null,
},
}}
/>
</Card>
);
};
const connectedColor = theme.palette.mode === 'light' ? '#b5e3b7' : '#153717';
const connectedTextColor = theme.palette.getContrastText(connectedColor);
const ownCardColor = theme.palette.mode === 'light' ? '#d1e6fa' : '#082745';
const peerCardColor = theme.palette.mode === 'light' ? '#f2d5f6' : '#380d3f';
return (
<Container component='main'>
<Grid container spacing={0.5}>
<Grid item xs={0.3} />
<Grid item xs={5.5}>
<Paper elevation={1} sx={connected ? { backgroundColor: connectedColor } : {}}>
<Typography variant='caption' sx={{ color: connectedTextColor }}>
{t('You') + ': '}
{connected ? t('connected') : t('disconnected')}
</Typography>
</Paper>
</Grid>
<Grid item xs={0.4} />
<Grid item xs={5.5}>
<Paper elevation={1} sx={peerConnected ? { backgroundColor: connectedColor } : {}}>
<Typography variant='caption' sx={{ color: connectedTextColor }}>
{t('Peer') + ': '}
{peerConnected ? t('connected') : t('disconnected')}
</Typography>
</Paper>
</Grid>
<Grid item xs={0.3} />
</Grid>
<div style={{ position: 'relative', left: '-0.14em', margin: '0 auto', width: '17.7em' }}>
<Paper
elevation={1}
style={{
height: '21.42em',
maxHeight: '21.42em',
width: '17.7em',
overflow: 'auto',
backgroundColor: theme.palette.background.paper,
}}
>
{messages.map((message, index) => (
<li style={{ listStyleType: 'none' }} key={index}>
{message.userNick == userNick
? messageCard(message, index, ownCardColor, connected)
: messageCard(message, index, peerCardColor, peerConnected)}
</li>
))}
<div
style={{ float: 'left', clear: 'both' }}
ref={(el) => {
if (messages.length > messageCount) el?.scrollIntoView();
}}
/>
</Paper>
<form noValidate onSubmit={onButtonClicked}>
<Grid alignItems='stretch' style={{ display: 'flex' }}>
<Grid item alignItems='stretch' style={{ display: 'flex' }}>
<TextField
label={t('Type a message')}
variant='standard'
size='small'
helperText={
connected
? peerPubKey
? null
: t('Waiting for peer public key...')
: t('Connecting...')
}
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
sx={{ width: '13.7em' }}
/>
</Grid>
<Grid item alignItems='stretch' style={{ display: 'flex' }}>
<Button
sx={{ width: '4.68em' }}
disabled={!connected || waitingEcho || !peerPubKey}
type='submit'
variant='contained'
color='primary'
>
{waitingEcho ? (
<div
style={{
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
minWidth: '4.68em',
width: '4.68em',
position: 'relative',
left: '1em',
}}
>
<div style={{ width: '1.2em' }}>
<KeyIcon sx={{ width: '1em' }} />
</div>
<div style={{ width: '1em', position: 'relative', left: '0.5em' }}>
<CircularProgress size={1.1 * theme.typography.fontSize} thickness={5} />
</div>
</div>
) : (
t('Send')
)}
</Button>
</Grid>
</Grid>
</form>
</div>
<div style={{ height: '0.3em' }} />
<Grid container spacing={0}>
<AuditPGPDialog
open={audit}
onClose={() => setAudit(false)}
orderId={Number(orderId)}
messages={messages}
own_pub_key={ownPubKey || ''}
own_enc_priv_key={ownEncPrivKey || ''}
peer_pub_key={peerPubKey || 'Not received yet'}
passphrase={token || ''}
onClickBack={() => setAudit(false)}
/>
<Grid item xs={6}>
<Tooltip
placement='bottom'
enterTouchDelay={0}
enterDelay={500}
enterNextDelay={2000}
title={t('Verify your privacy')}
>
<Button
size='small'
color='primary'
variant='outlined'
onClick={() => setAudit(!audit)}
>
<KeyIcon />
{t('Audit PGP')}{' '}
</Button>
</Tooltip>
</Grid>
<Grid item xs={6}>
<Tooltip
placement='bottom'
enterTouchDelay={0}
enterDelay={500}
enterNextDelay={2000}
title={t('Save full log as a JSON file (messages and credentials)')}
>
<Button
size='small'
color='primary'
variant='outlined'
onClick={() => saveAsJson('complete_log_chat_' + orderId + '.json', createJsonFile())}
>
<div style={{ width: '1.4em', height: '1.4em' }}>
<ExportIcon sx={{ width: '0.8em', height: '0.8em' }} />
</div>{' '}
{t('Export')}{' '}
</Button>
</Tooltip>
</Grid>
</Grid>
</Container>
);
};
export default EncryptedChat;

View File

@ -29,7 +29,7 @@ import {
import { LoadingButton } from '@mui/lab';
import QRCode from 'react-qr-code';
import Countdown, { zeroPad } from 'react-countdown';
import Chat from './EncryptedChat';
import EncryptedChat from './EncryptedChat';
import TradeSummary from './TradeSummary';
import MediaQuery from 'react-responsive';
import { systemClient } from '../../services/System';
@ -1469,7 +1469,7 @@ class TradeBox extends Component {
)}
</Grid>
<Chat orderId={this.props.data.id} ur_nick={this.props.data.ur_nick} />
<EncryptedChat orderId={this.props.data.id} userNick={this.props.data.ur_nick} />
<Grid item xs={12} align='center'>
{showDisputeButton ? this.showOpenDisputeButton() : ''}
{showSendButton ? this.showFiatSentButton() : ''}

View File

@ -17,7 +17,7 @@ class WebsocketConnectionWeb implements WebsocketConnection {
);
};
public onMessage: (event: (message: object) => void) => void = (event) => {
public onMessage: (event: (message: any) => void) => void = (event) => {
this.rws.addEventListener('message', event);
};

View File

@ -2,7 +2,7 @@ import WebsocketWebClient from './WebsocketWebClient';
export interface WebsocketConnection {
send: (message: object) => void;
onMessage: (event: (message: object) => void) => void;
onMessage: (event: (message: any) => void) => void;
onClose: (event: () => void) => void;
onError: (event: () => void) => void;
}