2022-05-24 00:31:34 +00:00
import React , { Component } from 'react' ;
import { withTranslation } from "react-i18next" ;
2022-05-24 13:33:55 +00:00
import { Button , IconButton , Badge , Tooltip , TextField , Grid , Container , Card , CardHeader , Paper , Avatar , Typography } from "@mui/material" ;
2022-05-24 00:31:34 +00:00
import ReconnectingWebSocket from 'reconnecting-websocket' ;
import { encryptMessage , decryptMessage } from "../utils/pgp" ;
import { getCookie } from "../utils/cookies" ;
2022-05-24 13:33:55 +00:00
import { saveAsJson } from "../utils/saveFile" ;
2022-05-24 12:16:50 +00:00
import { AuditPGPDialog } from "./Dialogs"
// Icons
import CheckIcon from '@mui/icons-material/Check' ;
import CloseIcon from '@mui/icons-material/Close' ;
2022-05-24 13:33:55 +00:00
import ContentCopy from "@mui/icons-material/ContentCopy" ;
2022-05-24 12:16:50 +00:00
import VisibilityIcon from '@mui/icons-material/Visibility' ;
2022-05-24 21:36:21 +00:00
import CircularProgress from '@mui/material/CircularProgress' ;
2022-05-24 12:16:50 +00:00
import KeyIcon from '@mui/icons-material/Key' ;
import { ExportIcon } from './Icons' ;
2022-05-24 00:31:34 +00:00
class Chat extends Component {
constructor ( props ) {
super ( props ) ;
}
state = {
own _pub _key : getCookie ( 'pub_key' ) . split ( '\\' ) . join ( '\n' ) ,
own _enc _priv _key : getCookie ( 'enc_priv_key' ) . split ( '\\' ) . join ( '\n' ) ,
peer _pub _key : null ,
token : getCookie ( 'robot_token' ) ,
messages : [ ] ,
value : '' ,
connected : false ,
peer _connected : false ,
audit : false ,
2022-05-24 21:36:21 +00:00
showPGP : new Array ,
waitingEcho : false ,
lastSent : '---BLANK---' ,
2022-05-24 00:31:34 +00:00
} ;
rws = new ReconnectingWebSocket ( 'ws://' + window . location . host + '/ws/chat/' + this . props . orderId + '/' ) ;
2022-05-24 21:36:21 +00:00
2022-05-24 00:31:34 +00:00
componentDidMount ( ) {
this . rws . addEventListener ( 'open' , ( ) => {
console . log ( 'Connected!' ) ;
this . setState ( { connected : true } ) ;
if ( this . state . peer _pub _key == null ) {
this . rws . send ( JSON . stringify ( {
type : "message" ,
message : "----PLEASE SEND YOUR PUBKEY----" ,
nick : this . props . ur _nick ,
} ) ) ;
}
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 ) ;
if ( dataFromServer ) {
console . log ( dataFromServer )
// If we receive our own key on a message
if ( dataFromServer . message == this . state . own _pub _key ) { console . log ( "ECHO OF OWN PUB KEY RECEIVED!!" ) }
// If we receive a request to send our public key
if ( dataFromServer . message == ` ----PLEASE SEND YOUR PUBKEY---- ` ) {
this . rws . send ( JSON . stringify ( {
type : "message" ,
message : this . state . own _pub _key ,
nick : this . props . ur _nick ,
} ) ) ;
} else
// 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 ) {
2022-05-24 12:16:50 +00:00
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 KEY PUBKEY RECEIVED!!" )
2022-05-24 00:31:34 +00:00
this . setState ( { peer _pub _key : dataFromServer . message } )
} else
// If we receive an encrypted message
if ( dataFromServer . message . substring ( 0 , 27 ) == ` -----BEGIN PGP MESSAGE----- ` ) {
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 ) =>
( {
2022-05-24 21:36:21 +00:00
waitingEcho : this . state . waitingEcho == true ? ( decryptedData . decryptedMessage == this . state . lastSent ? false : true ) : false ,
lastSent : decryptedData . decryptedMessage == this . state . lastSent ? '----BLANK----' : this . state . lastSent ,
2022-05-24 00:31:34 +00:00
messages : [ ... state . messages ,
{
encryptedMessage : dataFromServer . message . split ( '\\' ) . join ( '\n' ) ,
plainTextMessage : decryptedData . decryptedMessage ,
validSignature : decryptedData . validSignature ,
userNick : dataFromServer . user _nick ,
2022-05-24 12:16:50 +00:00
showPGP : false ,
2022-05-24 00:31:34 +00:00
time : dataFromServer . time
} ] ,
} )
) ) ;
}
this . setState ( { peer _connected : dataFromServer . peer _connected } )
}
} ) ;
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 ( ) {
this . scrollToBottom ( ) ;
}
scrollToBottom = ( ) => {
this . messagesEnd . scrollIntoView ( { behavior : "smooth" } ) ;
}
onButtonClicked = ( e ) => {
if ( this . state . value != '' ) {
2022-05-24 21:36:21 +00:00
this . setState ( { waitingEcho : true , lastSent : this . state . value } ) ;
2022-05-24 00:31:34 +00:00
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 ,
} )
2022-05-24 21:36:21 +00:00
) & this . setState ( { value : "" , waitingEcho : false } )
2022-05-24 00:31:34 +00:00
) ;
}
e . preventDefault ( ) ;
}
2022-05-24 12:16:50 +00:00
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 ,
} )
}
2022-05-24 21:36:21 +00:00
messageCard = ( props ) => {
const { t } = this . props ;
return (
< Card elevation = { 5 } align = "left" >
< CardHeader sx = { { color : '#333333' } }
avatar = {
< Badge variant = "dot" overlap = "circular" badgeContent = "" color = { props . userConnected ? "success" : "error" } >
< Avatar className = "flippedSmallAvatar"
alt = { props . message . userNick }
src = { window . location . origin + '/static/assets/avatars/' + props . message . userNick + '.png' }
/ >
< / B a d g e >
}
style = { { backgroundColor : props . cardColor } }
title = {
< Tooltip placement = "top" enterTouchDelay = { 0 } enterDelay = { 500 } enterNextDelay = { 2000 } title = { t ( props . message . validSignature ? "Verified signature by {{nickname}}" : "Invalid signature! Not sent by {{nickname}}" , { "nickname" : props . message . userNick } ) } >
< div style = { { display : 'flex' , alignItems : 'center' , flexWrap : 'wrap' , position : 'relative' , left : - 5 , width : 210 } } >
< 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" / >
}
< / d i v >
< 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" } } / >
< / I c o n B u t t o n >
< / d i v >
< div style = { { width : 20 } } >
< Tooltip disableHoverListener enterTouchDelay = { 0 } title = { t ( "Copied!" ) } >
< IconButton sx = { { height : 18 , width : 18 } }
onClick = { ( ) => navigator . clipboard . writeText ( this . state . showPGP [ props . index ] ? props . message . encryptedMessage : props . message . plainTextMessage ) } >
< ContentCopy sx = { { height : 16 , width : 16 , color : '#333333' } } / >
< / I c o n B u t t o n >
< / T o o l t i p >
< / d i v >
< / d i v >
< / T o o l t i p >
}
subheader = { this . state . showPGP [ props . index ] ? props . message . encryptedMessage : props . message . plainTextMessage }
subheaderTypographyProps = { { sx : { wordWrap : "break-word" , width : '200px' , color : '#444444' , fontSize : this . state . showPGP [ props . index ] ? 11 : null } } }
/ >
< / C a r d >
)
}
2022-05-24 00:31:34 +00:00
render ( ) {
const { t } = this . props ;
return (
< Container component = "main" maxWidth = "xs" >
< 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' } } >
2022-05-24 21:36:21 +00:00
< Typography variant = 'caption' sx = { { color : '#333333' } } >
2022-05-24 00:31:34 +00:00
{ t ( "You" ) + ": " } { this . state . connected ? t ( "connected" ) : t ( "disconnected" ) }
< / T y p o g r a p h y >
< / P a p e r >
< / G r i d >
< Grid item xs = { 0.4 } / >
< Grid item xs = { 5.5 } >
< Paper elevation = { 1 } style = { this . state . peer _connected ? { backgroundColor : '#e8ffe6' } : { backgroundColor : '#FFF1C5' } } >
2022-05-24 21:36:21 +00:00
< Typography variant = 'caption' sx = { { color : '#333333' } } >
2022-05-24 00:31:34 +00:00
{ t ( "Peer" ) + ": " } { this . state . peer _connected ? t ( "connected" ) : t ( "disconnected" ) }
< / T y p o g r a p h y >
< / P a p e r >
< / G r i d >
< Grid item xs = { 0.3 } / >
< / G r i d >
< Paper elevation = { 1 } style = { { height : '300px' , maxHeight : '300px' , width : '280px' , overflow : 'auto' , backgroundColor : '#F7F7F7' } } >
{ this . state . messages . map ( ( message , index ) =>
< li style = { { listStyleType : "none" } } key = { index } >
{ /* If message sender is not our nick, gray color, if it is our nick, green color */ }
{ message . userNick == this . props . ur _nick ?
2022-05-24 21:36:21 +00:00
< this . messageCard message = { message } index = { index } cardColor = { '#eeeeee' } userConnected = { this . state . connected } / >
2022-05-24 00:31:34 +00:00
:
2022-05-24 21:36:21 +00:00
< this . messageCard message = { message } index = { index } cardColor = { '#fafafa' } userConnected = { this . state . peer _connected } / >
}
{ /* / / < CardHeader sx = { { color : '#333333' } }
// avatar={
// <Badge variant="dot" overlap="circular" badgeContent="" color={this.state.connected ? "success" : "error"}>
// <Avatar className="flippedSmallAvatar"
// alt={message.userNick}
// src={window.location.origin +'/static/assets/avatars/' + message.userNick + '.png'}
// />
// </Badge>
// }
// style={{backgroundColor: '#eeeeee'}}
// title={
// <Tooltip placement="top" enterTouchDelay={0} enterDelay={500} enterNextDelay={2000} title={t(message.validSignature ? "Verified signature by {{nickname}}": "Invalid signature! Not sent by {{nickname}}",{"nickname": message.userNick})}>
// <div style={{display:'flex',alignItems:'center', flexWrap:'wrap', position:'relative',left:-5, width:210}}>
// <div style={{width:168,display:'flex',alignItems:'center', flexWrap:'wrap'}}>
// {message.userNick}
// {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[index] = !newShowPGP[index];
// return {showPGP: newShowPGP};
// })}>
// <VisibilityIcon color={this.state.showPGP[index]? "primary":"inherit"} sx={{height:16,width:16,color:this.state.showPGP[index]? "primary":"#333333"}}/>
// </IconButton>
// </div>
// <div style={{width:20}}>
// <Tooltip disableHoverListener enterTouchDelay={0} title={t("Copied!")}>
// <IconButton sx={{height:18,width:18}}
// onClick={()=> navigator.clipboard.writeText(this.state.showPGP[index] ? message.encryptedMessage : message.plainTextMessage)}>
// <ContentCopy sx={{height:16,width:16,color:'#333333'}}/>
// </IconButton>
// </Tooltip>
// </div>
// </div>
// </Tooltip>
// }
// subheader={this.state.showPGP[index] ? message.encryptedMessage : message.plainTextMessage}
// subheaderTypographyProps={{sx: {wordWrap: "break-word", width: '200px', color: '#444444', fontSize: this.state.showPGP[index]? 11 : null }}}
// />
// :
// <CardHeader sx={{color: '#333333'}}
// avatar={
// <Badge variant="dot" overlap="circular" badgeContent="" color={this.state.peer_connected ? "success" : "error"}>
// <Avatar className="flippedSmallAvatar"
// alt={message.userNick}
// src={window.location.origin +'/static/assets/avatars/' + message.userNick + '.png'}
// />
// </Badge>
// }
// style={{backgroundColor: '#fafafa'}}
// title={
// <Tooltip placement="top" enterTouchDelay={0} enterDelay={500} enterNextDelay={2000} title={t(message.validSignature ? "Verified signature by {{nickname}}": "Invalid signature! Not sent by {{nickname}}",{"nickname": message.userNick})}>
// <div style={{display:'flex',alignItems:'center', flexWrap:'wrap', position:'relative',left:-5, width:210}}>
// <div style={{width:168,display:'flex',alignItems:'center', flexWrap:'wrap'}}>
// {message.userNick}
// {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[index] = !newShowPGP[index];
// return {showPGP: newShowPGP};
// })}>
// <VisibilityIcon color={this.state.showPGP[index]? "primary":"inherit"} sx={{height:16,width:16,color:this.state.showPGP[index]? "primary":"#333333"}}/>
// </IconButton>
// </div>
// <div style={{width:20}}>
// <Tooltip disableHoverListener enterTouchDelay={0} title={t("Copied!")}>
// <IconButton sx={{height:18,width:18}}
// onClick={()=> navigator.clipboard.writeText(this.state.showPGP[index] ? message.encryptedMessage : message.plainTextMessage)}>
// <ContentCopy sx={{height:16,width:16,color:'#333333'}}/>
// </IconButton>
// </Tooltip>
// </div>
// </div>
// </Tooltip> */}
2022-05-24 00:31:34 +00:00
< / l i > ) }
< div style = { { float : "left" , clear : "both" } } ref = { ( el ) => { this . messagesEnd = el ; } } > < / d i v >
< / P a p e r >
< 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 ? null : t ( "Connecting..." ) }
value = { this . state . value }
onChange = { e => {
this . setState ( { value : e . target . value } ) ;
this . value = this . state . value ;
} }
sx = { { width : 214 } }
/ >
< / G r i d >
< Grid item alignItems = "stretch" style = { { display : "flex" } } >
2022-05-24 21:36:21 +00:00
< Button sx = { { 'width' : 68 } } disabled = { ! this . state . connected || this . state . waitingEcho } 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 } } / > < / d i v >
< div style = { { width : 18 } } > < CircularProgress size = { 16 } thickness = { 5 } / > < / d i v >
< / d i v >
:
t ( "Send" )
}
< / B u t t o n >
2022-05-24 00:31:34 +00:00
< / G r i d >
< / G r i d >
< / f o r m >
2022-05-24 12:16:50 +00:00
< 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 } ) }
/ >
2022-05-24 12:52:33 +00:00
2022-05-24 12:16:50 +00:00
< Grid item xs = { 6 } >
2022-05-24 12:52:33 +00:00
< 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" ) } < / B u t t o n >
< / T o o l t i p >
2022-05-24 12:16:50 +00:00
< / G r i d >
< Grid item xs = { 6 } >
2022-05-24 13:33:55 +00:00
< Tooltip placement = "bottom" enterTouchDelay = { 0 } enterDelay = { 500 } enterNextDelay = { 2000 } title = { t ( "Save full log as a JSON file (messages and credentials)" ) } >
2022-05-24 12:52:33 +00:00
< Button size = "small" color = "primary" variant = "outlined" onClick = { ( ) => saveAsJson ( 'chat_' + this . props . orderId + '.json' , this . createJsonFile ( ) ) } > < div style = { { width : 28 , height : 20 } } > < ExportIcon sx = { { width : 20 , height : 20 } } / > < / d i v > { t ( " E x p o r t " ) } < / B u t t o n >
< / T o o l t i p >
2022-05-24 12:16:50 +00:00
< / G r i d >
2022-05-24 00:31:34 +00:00
< / G r i d >
2022-05-24 12:16:50 +00:00
2022-05-24 00:31:34 +00:00
< / C o n t a i n e r >
)
}
}
export default withTranslation ( ) ( Chat ) ;