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 './Robots/RobotAvatar'; import { systemClient } from '../services/System'; // 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, peer_connected: false, audit: false, showPGP: new Array(), waitingEcho: false, lastSent: '---BLANK---', latestIndex: 0, scrollNow: false, }; rws = new ReconnectingWebSocket( 'ws://' + window.location.host + '/ws/chat/' + this.props.orderId + '/', [], { connectionTimeout: 15000 }, ); componentDidMount() { this.rws.addEventListener('open', () => { console.log('Connected!'); this.setState({ connected: true }); this.rws.send( JSON.stringify({ type: 'message', message: this.state.own_pub_key, nick: this.props.ur_nick, }), ); }); this.rws.addEventListener('message', (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.rws.send( JSON.stringify({ type: 'message', 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(), }, ], })); } } }); this.rws.addEventListener('close', () => { console.log('Socket is closed. Reconnect will be attempted'); this.setState({ connected: false }); }); this.rws.addEventListener('error', () => { console.error('Socket encountered error: Closing socket'); }); } 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 }); } } 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.rws.send( JSON.stringify({ type: 'message', 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.rws.send( JSON.stringify({ type: 'message', 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 ( } style={{ backgroundColor: props.cardColor }} title={
{props.message.userNick} {props.message.validSignature ? ( ) : ( )}
this.setState((prevState) => { const newShowPGP = [...prevState.showPGP]; newShowPGP[props.index] = !newShowPGP[props.index]; return { showPGP: newShowPGP }; }) } >
systemClient.copyToClipboard( this.state.showPGP[props.index] ? props.message.encryptedMessage : props.message.plainTextMessage, ) } >
} subheader={ this.state.showPGP[props.index] ? ( {' '} {props.message.time}
{'Valid signature: ' + props.message.validSignature}{' '}
{props.message.encryptedMessage}{' '}
) : ( props.message.plainTextMessage ) } subheaderTypographyProps={{ sx: { wordWrap: 'break-word', width: '200px', color: '#444444', fontSize: this.state.showPGP[props.index] ? 11 : null, }, }} />
); }; render() { const { t } = this.props; return ( {t('You') + ': '} {this.state.connected ? t('connected') : t('disconnected')} {t('Peer') + ': '} {this.state.peer_connected ? t('connected') : t('disconnected')}
{this.state.messages.map((message, index) => (
  • {message.userNick == this.props.ur_nick ? ( ) : ( )}
  • ))}
    { this.messagesEnd = el; }} >
    { this.setState({ value: e.target.value }); this.value = this.state.value; }} sx={{ width: 219 }} />
    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 })} /> ); } } export default withTranslation()(Chat);