2022-05-24 00:31:34 +00:00
import React , { Component } from 'react' ;
import { withTranslation } from "react-i18next" ;
2022-05-24 12:52:33 +00:00
import { Button , 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 12:16:50 +00:00
import { saveAsJson , saveAsTxt } from "../utils/saveFile" ;
import { AuditPGPDialog } from "./Dialogs"
// Icons
import CheckIcon from '@mui/icons-material/Check' ;
import CloseIcon from '@mui/icons-material/Close' ;
import FileDownloadIcon from '@mui/icons-material/FileDownload' ;
import VisibilityIcon from '@mui/icons-material/Visibility' ;
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 ,
} ;
rws = new ReconnectingWebSocket ( 'ws://' + window . location . host + '/ws/chat/' + this . props . orderId + '/' ) ;
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 ) =>
( {
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 != '' ) {
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 ,
} )
) & this . setState ( { value : "" } )
) ;
}
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 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' } } >
< Typography variant = 'caption' sx = { { color : '#111111' } } >
{ 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' } } >
< Typography variant = 'caption' sx = { { color : '#111111' } } >
{ 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 } >
< Card elevation = { 5 } align = "left" >
{ /* If message sender is not our nick, gray color, if it is our nick, green color */ }
{ message . userNick == this . props . ur _nick ?
< CardHeader sx = { { color : '#111111' } }
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' }
/ >
< / B a d g e >
}
style = { { backgroundColor : '#eeeeee' } }
2022-05-24 12:52:33 +00:00
title = { < div style = { { display : 'flex' , alignItems : 'center' , flexWrap : 'wrap' } } > { message . userNick } { message . validSignature ? < CheckIcon sx = { { height : 16 , display : "inline-block" } } color = "success" / > : < CloseIcon sx = { { height : 16 , display : "inline-block" } } color = "error" / > } < / d i v > }
2022-05-24 00:31:34 +00:00
subheader = { this . state . audit ? message . encryptedMessage : message . plainTextMessage }
subheaderTypographyProps = { { sx : { wordWrap : "break-word" , width : '200px' , color : '#444444' } } }
/ >
:
< CardHeader sx = { { color : '#111111' } }
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' }
/ >
< / B a d g e >
}
style = { { backgroundColor : '#fafafa' } }
2022-05-24 12:52:33 +00:00
title = {
< Tooltip placement = "top" enterTouchDelay = { 0 } enterDelay = { 500 } enterNextDelay = { 2000 } title = { t ( message . validSignature ? "Verified signature by {{sender}}" : "Invalid signature" , { "sender" : message . userNick } ) } >
< div style = { { display : 'flex' , alignItems : 'center' , flexWrap : 'wrap' } } >
{ message . userNick }
{ message . validSignature ?
< CheckIcon sx = { { height : 16 , display : "inline-block" } } color = "success" / >
:
< CloseIcon sx = { { height : 16 , display : "inline-block" } } color = "error" / >
}
< / d i v >
< / T o o l t i p > }
2022-05-24 12:16:50 +00:00
subheader = { this . state . audit ? message . encryptedMessage : message . plainTextMessage }
2022-05-24 00:31:34 +00:00
subheaderTypographyProps = { { sx : { wordWrap : "break-word" , width : '200px' , color : '#444444' } } }
/ > }
< / C a r d >
< / 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" } } >
< Button sx = { { 'width' : 68 } } disabled = { ! this . state . connected } type = "submit" variant = "contained" color = "primary" > { t ( "Send" ) } < / B u t t o n >
< / 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 12:52:33 +00:00
< Tooltip placement = "bottom" enterTouchDelay = { 0 } enterDelay = { 500 } enterNextDelay = { 2000 } title = { t ( "Save messages and credentials as a JSON file" ) } >
< 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 ) ;