From b2ee6a5380d302c9b1112d913a51e22a806c519d Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Wed, 12 Oct 2022 11:21:15 +0000 Subject: [PATCH] Create WebsocketWebClient (#277) * Create WebsocketWebClient * Remove unused lines * Code review --- frontend/src/components/EncryptedChat.js | 278 +++++++++--------- .../Websocket/WebsocketConnectionWeb/index.ts | 33 +++ .../Websocket/WebsocketWebClient/index.ts | 19 ++ frontend/src/services/Websocket/index.ts | 14 + 4 files changed, 197 insertions(+), 147 deletions(-) create mode 100644 frontend/src/services/Websocket/WebsocketConnectionWeb/index.ts create mode 100644 frontend/src/services/Websocket/WebsocketWebClient/index.ts create mode 100644 frontend/src/services/Websocket/index.ts diff --git a/frontend/src/components/EncryptedChat.js b/frontend/src/components/EncryptedChat.js index 3244a16c..028d028a 100644 --- a/frontend/src/components/EncryptedChat.js +++ b/frontend/src/components/EncryptedChat.js @@ -20,6 +20,7 @@ import { saveAsJson } from '../utils/saveFile'; import { AuditPGPDialog } from './Dialogs'; import RobotAvatar from './Robots/RobotAvatar'; import { systemClient } from '../services/System'; +import { websocketClient } from '../services/Websocket'; // Icons import CheckIcon from '@mui/icons-material/Check'; @@ -43,6 +44,7 @@ class Chat extends Component { messages: [], value: '', connected: false, + connection: null, peer_connected: false, audit: false, showPGP: new Array(), @@ -52,144 +54,29 @@ class Chat extends Component { scrollNow: false, }; - rws = new ReconnectingWebSocket( - 'ws://' + window.location.host + '/ws/chat/' + this.props.orderId + '/', - [], - { connectionTimeout: 15000 }, - ); - componentDidMount() { - this.rws.addEventListener('open', () => { - console.log('Connected!'); - this.setState({ connected: true }); - this.rws.send( - JSON.stringify({ - type: 'message', + 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, - }), - ); - }); + }); - this.rws.addEventListener('message', (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 }); + 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 }); + }); - // 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.rws.send( - JSON.stringify({ - type: 'message', - 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(), - }, - ], - })); - } - } - }); - - 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'); - }); + this.setState({ connected: true, connection: connection }); + }); } componentDidUpdate() { @@ -202,6 +89,109 @@ class Chat extends Component { } } + 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' }); }; @@ -217,13 +207,10 @@ class Chat extends Component { // If input string contains '#' send unencrypted and unlogged message else if (this.state.value.substring(0, 1) == '#') { - this.rws.send( - JSON.stringify({ - type: 'message', - message: this.state.value, - nick: this.props.ur_nick, - }), - ); + this.state.connection.send({ + message: this.state.value, + nick: this.props.ur_nick, + }); this.setState({ value: '' }); } @@ -239,13 +226,10 @@ class Chat extends Component { ).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.state.connection.send({ + message: encryptedMessage.split('\n').join('\\'), + nick: this.props.ur_nick, + }), ); } e.preventDefault(); diff --git a/frontend/src/services/Websocket/WebsocketConnectionWeb/index.ts b/frontend/src/services/Websocket/WebsocketConnectionWeb/index.ts new file mode 100644 index 00000000..500f67ec --- /dev/null +++ b/frontend/src/services/Websocket/WebsocketConnectionWeb/index.ts @@ -0,0 +1,33 @@ +import ReconnectingWebSocket from 'reconnecting-websocket'; +import { WebsocketConnection } from '..'; + +class WebsocketConnectionWeb implements WebsocketConnection { + constructor(path: string) { + this.rws = new ReconnectingWebSocket(path, [], { connectionTimeout: 15000 }); + } + + public rws: ReconnectingWebSocket; + + public send: (message: object) => void = (message: object) => { + this.rws.send( + JSON.stringify({ + type: 'message', + ...message, + }), + ); + }; + + public onMessage: (event: (message: object) => void) => void = (event) => { + this.rws.addEventListener('message', event); + }; + + public onClose: (event: () => void) => void = (event) => { + this.rws.addEventListener('close', event); + }; + + public onError: (event: () => void) => void = (event) => { + this.rws.addEventListener('error', event); + }; +} + +export default WebsocketConnectionWeb; diff --git a/frontend/src/services/Websocket/WebsocketWebClient/index.ts b/frontend/src/services/Websocket/WebsocketWebClient/index.ts new file mode 100644 index 00000000..87f09857 --- /dev/null +++ b/frontend/src/services/Websocket/WebsocketWebClient/index.ts @@ -0,0 +1,19 @@ +import { WebsocketClient, WebsocketConnection } from '..'; +import WebsocketConnectionWeb from '../WebsocketConnectionWeb'; + +class WebsocketWebClient implements WebsocketClient { + public open: (path: string) => Promise = (path) => { + return new Promise((resolve, reject) => { + try { + const connection = new WebsocketConnectionWeb(path); + connection.rws.addEventListener('open', () => { + resolve(connection); + }); + } catch { + reject(); + } + }); + }; +} + +export default WebsocketWebClient; diff --git a/frontend/src/services/Websocket/index.ts b/frontend/src/services/Websocket/index.ts new file mode 100644 index 00000000..0e30c764 --- /dev/null +++ b/frontend/src/services/Websocket/index.ts @@ -0,0 +1,14 @@ +import WebsocketWebClient from './WebsocketWebClient'; + +export interface WebsocketConnection { + send: (message: object) => void; + onMessage: (event: (message: object) => void) => void; + onClose: (event: () => void) => void; + onError: (event: () => void) => void; +} + +export interface WebsocketClient { + open: (path: string) => Promise; +} + +export const websocketClient: WebsocketClient = new WebsocketWebClient();