mirror of
https://github.com/Reckless-Satoshi/robosats.git
synced 2024-09-11 11:55:26 +03:00
Prepare for v0.2.3 candidates
This commit is contained in:
parent
227610c84a
commit
046898deda
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -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 '../../pgp';
|
||||
import { saveAsJson } from '../../utils';
|
||||
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);
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Button,
|
||||
@ -12,8 +12,8 @@ import {
|
||||
Paper,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { encryptMessage, decryptMessage } from '../../../utils/pgp';
|
||||
import { saveAsJson } from '../../../utils/saveFile';
|
||||
import { encryptMessage, decryptMessage } from '../../../pgp';
|
||||
import { saveAsJson } from '../../../utils';
|
||||
import { AuditPGPDialog } from '../../Dialogs';
|
||||
import RobotAvatar from '../../RobotAvatar';
|
||||
import { systemClient } from '../../../services/System';
|
||||
@ -520,4 +520,4 @@ const EncryptedChat: React.FC<Props> = ({ orderId, userNick }: Props): JSX.Eleme
|
||||
);
|
||||
};
|
||||
|
||||
export default EncryptedChat;
|
||||
export default EncryptedChat;
|
@ -149,7 +149,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "0.2.2-alpha"
|
||||
versionName "0.2.3-alpha"
|
||||
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
||||
|
||||
if (isNewArchitectureEnabled()) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "robosats",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"major": 0,
|
||||
"minor": 2,
|
||||
"patch": 2
|
||||
"patch": 3
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user