import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, TextField, Grid, Paper, Typography } from '@mui/material'; import { encryptMessage, decryptMessage } from '../../../../pgp'; import { AuditPGPDialog } from '../../../Dialogs'; import { Robot } from '../../../../models'; // Icons import CircularProgress from '@mui/material/CircularProgress'; import KeyIcon from '@mui/icons-material/Key'; import { useTheme } from '@mui/system'; import MessageCard from '../MessageCard'; import ChatHeader from '../ChatHeader'; import { EncryptedChatMessage, ServerMessage } from '..'; import { apiClient } from '../../../../services/api'; import ChatBottom from '../ChatBottom'; interface Props { orderId: number; robot: Robot; userNick: string; takerNick: string; chatOffset: number; messages: EncryptedChatMessage[]; setMessages: (messages: EncryptedChatMessage[]) => void; baseUrl: string; turtleMode: boolean; setTurtleMode: (state: boolean) => void; } const EncryptedTurtleChat: React.FC = ({ orderId, robot, userNick, takerNick, chatOffset, messages, setMessages, baseUrl, setTurtleMode, turtleMode, }: Props): JSX.Element => { const { t } = useTranslation(); const theme = useTheme(); const [audio] = useState(() => new Audio(`/static/assets/sounds/chat-open.mp3`)); const [peerConnected, setPeerConnected] = useState(false); const [peerPubKey, setPeerPubKey] = useState(); const [value, setValue] = useState(''); const [audit, setAudit] = useState(false); const [waitingEcho, setWaitingEcho] = useState(false); const [lastSent, setLastSent] = useState('---BLANK---'); const [messageCount, setMessageCount] = useState(0); const [serverMessages, setServerMessages] = useState([]); const [lastIndex, setLastIndex] = useState(0); const [error, setError] = useState(''); useEffect(() => { if (messages.length > messageCount) { audio.play(); setMessageCount(messages.length); } }, [messages, messageCount]); useEffect(() => { if (serverMessages.length > 0 && peerPubKey) { serverMessages.forEach(onMessage); } }, [serverMessages, peerPubKey]); useEffect(() => { if (chatOffset === 0 || chatOffset > lastIndex) { loadMessages(); } }, [chatOffset]); const loadMessages: () => void = () => { apiClient .get(baseUrl, `/api/chat/?order_id=${orderId}&offset=${lastIndex}`, { tokenSHA256: robot.tokenSHA256, }) .then((results: any) => { if (results) { setPeerConnected(results.peer_connected); setPeerPubKey(results.peer_pubkey.split('\\').join('\n')); setServerMessages(results.messages); } }); }; const createJsonFile: () => object = () => { return { credentials: { own_public_key: robot.pubKey, peer_public_key: peerPubKey, encrypted_private_key: robot.encPrivKey, passphrase: robot.token, }, messages, }; }; const onMessage: (dataFromServer: ServerMessage) => void = (dataFromServer) => { if (dataFromServer) { // If we receive an encrypted message if (dataFromServer.message.substring(0, 27) == `-----BEGIN PGP MESSAGE-----`) { decryptMessage( dataFromServer.message.split('\\').join('\n'), dataFromServer.nick == userNick ? robot.pubKey : peerPubKey, robot.encPrivKey, robot.token, ).then((decryptedData) => { setLastSent(decryptedData.decryptedMessage === lastSent ? '----BLANK----' : lastSent); setLastIndex(lastIndex < dataFromServer.index ? dataFromServer.index : lastIndex); setMessages((prev: EncryptedChatMessage[]) => { const existingMessage = prev.find((item) => item.index === dataFromServer.index); if (existingMessage != null) { return prev; } else { return [ ...prev, { index: dataFromServer.index, encryptedMessage: dataFromServer.message.split('\\').join('\n'), plainTextMessage: decryptedData.decryptedMessage, validSignature: decryptedData.validSignature, userNick: dataFromServer.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.nick, time: new Date().toString(), } as EncryptedChatMessage, ].sort((a, b) => a.index - b.index); } }); } } }; const onButtonClicked = (e: any) => { if (robot.token && value.includes(robot.token)) { alert( `Aye! You just sent your own robot 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 (value.substring(0, 1) == '#') { apiClient .post( baseUrl, `/api/chat/`, { PGP_message: value, order_id: orderId, offset: lastIndex, }, { tokenSHA256: robot.tokenSHA256 }, ) .then((response) => { if (response != null) { if (response.messages) { setPeerConnected(response.peer_connected); setServerMessages(response.messages); } } }) .finally(() => { setWaitingEcho(false); setValue(''); }); } // Else if message is not empty send message else if (value != '') { setWaitingEcho(true); setLastSent(value); encryptMessage(value, robot.pubKey, peerPubKey, robot.encPrivKey, robot.token) .then((encryptedMessage) => { apiClient .post( baseUrl, `/api/chat/`, { PGP_message: encryptedMessage.toString().split('\n').join('\\'), order_id: orderId, offset: lastIndex, }, { tokenSHA256: robot.tokenSHA256 }, ) .then((response) => { if (response != null) { setPeerConnected(response.peer_connected); if (response.messages) { setServerMessages(response.messages); } } }) .finally(() => { setWaitingEcho(false); setValue(''); }); }) .catch((error) => setError(error.toString())); } e.preventDefault(); }; return ( setAudit(false)} orderId={Number(orderId)} messages={messages} own_pub_key={robot.pubKey || ''} own_enc_priv_key={robot.encPrivKey || ''} peer_pub_key={peerPubKey || 'Not received yet'} passphrase={robot.token || ''} onClickBack={() => setAudit(false)} /> {messages.map((message, index) => { const isTaker = takerNick === message.userNick; const userConnected = message.userNick === userNick ? true : peerConnected; return (
  • ); })}
    { if (messages.length > messageCount) el?.scrollIntoView(); }} />
    { setValue(e.target.value); }} fullWidth={true} /> {error}
    ); }; export default EncryptedTurtleChat;