Prepare for v0.2.3 candidates

This commit is contained in:
Reckless_Satoshi 2022-10-30 12:28:20 -07:00
parent 227610c84a
commit 046898deda
No known key found for this signature in database
GPG Key ID: 9C4585B561315571
6 changed files with 8 additions and 573 deletions

View File

@ -1,6 +1,6 @@
{
"name": "frontend",
"version": "0.2.2",
"version": "0.2.3",
"description": "",
"main": "index.js",
"scripts": {

View File

@ -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);

View File

@ -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';

View File

@ -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()) {

View File

@ -1,6 +1,6 @@
{
"name": "robosats",
"version": "0.2.2",
"version": "0.2.3",
"private": true,
"scripts": {
"android": "react-native run-android",

View File

@ -1,5 +1,5 @@
{
"major": 0,
"minor": 2,
"patch": 2
"patch": 3
}