diff --git a/client/src/actions/app.js b/client/src/actions/app.js index 3aac089..8a5ca15 100644 --- a/client/src/actions/app.js +++ b/client/src/actions/app.js @@ -31,10 +31,6 @@ export const toggleSocketConnected = payload => async dispatch => { dispatch({ type: 'TOGGLE_SOCKET_CONNECTED', payload }); }; -export const createUser = payload => async dispatch => { - dispatch({ type: 'CREATE_USER', payload }); -}; - export const clearActivities = () => async dispatch => { dispatch({ type: 'CLEAR_ACTIVITIES' }); }; diff --git a/client/src/actions/app.test.js b/client/src/actions/app.test.js index 9ba2a35..30c9ad2 100644 --- a/client/src/actions/app.test.js +++ b/client/src/actions/app.test.js @@ -40,7 +40,6 @@ describe('App actions', () => { [actions.showNotice('test'), 'SHOW_NOTICE'], [actions.toggleSoundEnabled('test'), 'TOGGLE_SOUND_ENABLED'], [actions.toggleSocketConnected('test'), 'TOGGLE_SOCKET_CONNECTED'], - [actions.createUser('test'), 'CREATE_USER'], [actions.setLanguage('test'), 'CHANGE_LANGUAGE'], ]; diff --git a/client/src/actions/encrypted_messages.js b/client/src/actions/encrypted_messages.js index 9500042..50f50ae 100644 --- a/client/src/actions/encrypted_messages.js +++ b/client/src/actions/encrypted_messages.js @@ -1,10 +1,18 @@ import { getSocket } from '@/utils/socket'; import { prepare as prepareMessage, process as processMessage } from '@/utils/message'; +import { changeUsername } from '@/reducers/user'; export const sendEncryptedMessage = payload => async (dispatch, getState) => { const state = getState(); const msg = await prepareMessage(payload, state); - dispatch({ type: `SEND_ENCRYPTED_MESSAGE_${msg.original.type}`, payload: msg.original.payload }); + switch(msg.original.type){ + case "CHANGE_USERNAME": + dispatch(changeUsername(msg.original.payload)); + dispatch({ type: `SEND_ENCRYPTED_MESSAGE_${msg.original.type}`, payload: msg.original.payload }); + break; + default: + dispatch({ type: `SEND_ENCRYPTED_MESSAGE_${msg.original.type}`, payload: msg.original.payload }); + } getSocket().emit('ENCRYPTED_MESSAGE', msg.toSend); }; diff --git a/client/src/components/Home/Home.jsx b/client/src/components/Home/Home.jsx index b3f7970..6a24f2c 100644 --- a/client/src/components/Home/Home.jsx +++ b/client/src/components/Home/Home.jsx @@ -4,6 +4,8 @@ import PropTypes from 'prop-types'; import { nanoid } from 'nanoid'; import { X, AlertCircle } from 'react-feather'; import classNames from 'classnames'; +import { useSelector, useDispatch } from 'react-redux'; +import { createUser } from '@/reducers/user'; import Crypto from '@/utils/crypto'; import { connect as connectSocket } from '@/utils/socket'; @@ -290,15 +292,18 @@ const Home = ({ ); }; -export const WithUser = ({ createUser, username, ...rest }) => { +export const WithUser = ({ ...rest }) => { const [loaded, setLoaded] = React.useState(false); const loading = React.useRef(false); + const user = useSelector(state => state.user); + const dispatch = useDispatch(); + React.useEffect(() => { let mounted = true; const createUserLocal = async () => { - const localUsername = username || nanoid(); + const localUsername = user.username || nanoid(); const encryptDecryptKeys = await crypto.createEncryptDecryptKeys(); const exportedEncryptDecryptPrivateKey = await crypto.exportKey(encryptDecryptKeys.privateKey); @@ -309,11 +314,14 @@ export const WithUser = ({ createUser, username, ...rest }) => { return; } - createUser({ + const payload = { username: localUsername, publicKey: exportedEncryptDecryptPublicKey, privateKey: exportedEncryptDecryptPrivateKey, - }); + }; + dispatch(createUser(payload)); + + dispatch({ type: 'CREATE_USER', payload }); loading.current = false; setLoaded(true); @@ -328,12 +336,13 @@ export const WithUser = ({ createUser, username, ...rest }) => { loading.current = false; mounted = false; }; - }, [createUser, loaded, username]); + }, [dispatch, loaded, user.username]); if (!loaded) { return null; } - return ; + + return ; }; WithUser.defaultProps = { @@ -343,10 +352,7 @@ WithUser.defaultProps = { WithUser.propTypes = { receiveEncryptedMessage: PropTypes.func.isRequired, receiveUnencryptedMessage: PropTypes.func.isRequired, - createUser: PropTypes.func.isRequired, activities: PropTypes.array.isRequired, - username: PropTypes.string.isRequired, - publicKey: PropTypes.object.isRequired, members: PropTypes.array.isRequired, socketId: PropTypes.string.isRequired, roomId: PropTypes.string.isRequired, @@ -355,7 +361,6 @@ WithUser.propTypes = { openModal: PropTypes.func.isRequired, closeModal: PropTypes.func.isRequired, iAmOwner: PropTypes.bool.isRequired, - userId: PropTypes.string.isRequired, toggleWindowFocus: PropTypes.func.isRequired, soundIsEnabled: PropTypes.bool.isRequired, persistenceIsEnabled: PropTypes.bool.isRequired, diff --git a/client/src/components/Home/WithNewMessageNotification.jsx b/client/src/components/Home/WithNewMessageNotification.jsx index 1e3fbc4..3661928 100644 --- a/client/src/components/Home/WithNewMessageNotification.jsx +++ b/client/src/components/Home/WithNewMessageNotification.jsx @@ -43,7 +43,6 @@ const WithNewMessageNotification = ({ const { username, type, text, fileName, locked, newUsername, currentUsername, action } = currentLastMessage; if (currentLastMessage !== lastMessage && !windowIsFocused) { - setLastMessage(currentLastMessage); if (notificationIsAllowed && notificationIsEnabled) { // Generate the proper notification according to the message type switch (type) { @@ -79,6 +78,8 @@ const WithNewMessageNotification = ({ if (soundIsEnabled) beep.play(); } + setLastMessage(currentLastMessage); + if (unreadMessageCount !== lastUnreadMessageCount) { setLastUnreadMessageCount(unreadMessageCount); Tinycon.setBubble(unreadMessageCount); diff --git a/client/src/components/Home/index.jsx b/client/src/components/Home/index.jsx index c78ff94..74341cb 100644 --- a/client/src/components/Home/index.jsx +++ b/client/src/components/Home/index.jsx @@ -3,7 +3,6 @@ import { useLoaderData } from 'react-router-dom'; import { receiveEncryptedMessage, - createUser, openModal, closeModal, toggleWindowFocus, @@ -26,8 +25,6 @@ const mapStateToProps = state => { return { activities: state.activities.items, - userId: state.user.id, - username: state.user.username, publicKey: state.user.publicKey, privateKey: state.user.privateKey, members: state.room.members.filter(m => m.username && m.publicKey), @@ -48,7 +45,6 @@ const mapStateToProps = state => { const mapDispatchToProps = { receiveEncryptedMessage, - createUser, openModal, closeModal, toggleWindowFocus, diff --git a/client/src/components/Home/index.test.jsx b/client/src/components/Home/index.test.jsx index a548ce3..0100106 100644 --- a/client/src/components/Home/index.test.jsx +++ b/client/src/components/Home/index.test.jsx @@ -206,10 +206,15 @@ describe('Connected Home component', () => { , ); expect(store.getState().app.unreadMessageCount).toBe(1); + expect(notify).toHaveBeenCalledTimes(1); expect(notify).toHaveBeenLastCalledWith('sender said:', 'new message'); + expect(beep.play).toHaveBeenCalledTimes(1); expect(beep.play).toHaveBeenLastCalledWith(); expect(Tinycon.setBubble).toHaveBeenLastCalledWith(1); + notify.mockClear(); + beep.play.mockClear(); + // Test with sound and notification disabled await act(() => toggleNotificationEnabled(false)(store.dispatch)); await act(() => toggleSoundEnabled(false)(store.dispatch)); @@ -227,8 +232,8 @@ describe('Connected Home component', () => { ); expect(store.getState().app.unreadMessageCount).toBe(2); - expect(notify).toHaveBeenCalledTimes(2); - expect(beep.play).toHaveBeenCalledTimes(2); + expect(notify).toHaveBeenCalledTimes(0); + expect(beep.play).toHaveBeenCalledTimes(0); expect(Tinycon.setBubble).toHaveBeenLastCalledWith(2); }); }); diff --git a/client/src/reducers/user.js b/client/src/reducers/user.js index 0c20766..a9f47e4 100644 --- a/client/src/reducers/user.js +++ b/client/src/reducers/user.js @@ -1,27 +1,28 @@ -const initialState = { - privateKey: {}, - publicKey: {}, - username: '', - id: '', -}; +import { createSlice } from '@reduxjs/toolkit' -const user = (receivedState, action) => { - const state = { ...initialState, ...receivedState }; +export const userSlice = createSlice({ + name: 'user', + initialState: { + privateKey: {}, + publicKey: {}, + username: '', + id: '', + }, + reducers: { + createUser: (state, action) => { + const { privateKey, publicKey, username } = action.payload; + state.privateKey = privateKey; + state.publicKey = publicKey; + state.username = username; + state.id = publicKey.n; + }, + changeUsername: (state, action) => { + const { newUsername } = action.payload; + state.username = newUsername; + } + }, +}) - switch (action.type) { - case 'CREATE_USER': - return { - ...action.payload, - id: action.payload.publicKey.n, - }; - case 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME': - return { - ...state, - username: action.payload.newUsername, - }; - default: - return state; - } -}; +export const { createUser,changeUsername } = userSlice.actions -export default user; +export default userSlice.reducer \ No newline at end of file diff --git a/client/src/reducers/user.test.js b/client/src/reducers/user.test.js index 5cb07a8..057b78d 100644 --- a/client/src/reducers/user.test.js +++ b/client/src/reducers/user.test.js @@ -1,6 +1,6 @@ import { describe, it, expect, vi } from 'vitest'; -import reducer from './user'; +import reducer, { createUser, changeUsername} from './user'; vi.mock('@/i18n', () => { return { @@ -15,7 +15,8 @@ describe('User reducer', () => { it('should handle CREATE_USER', () => { const payload = { publicKey: { n: 'alicekey' }, username: 'alice' }; - expect(reducer({}, { type: 'CREATE_USER', payload })).toEqual({ + + expect(reducer({},createUser(payload) )).toEqual({ id: 'alicekey', publicKey: { n: 'alicekey' }, username: 'alice', @@ -24,10 +25,8 @@ describe('User reducer', () => { it('should handle SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', () => { const payload = { newUsername: 'alice' }; - expect(reducer({ username: 'polux' }, { type: 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', payload })).toEqual({ - id: '', - privateKey: {}, - publicKey: {}, + + expect(reducer({ username: 'polux' }, changeUsername(payload))).toEqual({ username: 'alice', }); }); diff --git a/readme.md b/readme.md index c170df0..5d27c82 100644 --- a/readme.md +++ b/readme.md @@ -86,7 +86,7 @@ Then run it. Example: $ docker run --init --name darkwire.io --rm -p 3001:3001 darkwire.io ``` -You are able to use any of the enviroment variables available in `server/.env.dist` and `client/.env.dist`. The defaults are available in [Dockerfile](Dockerfile) +You are able to use any of the environment variables available in `server/.env.dist` and `client/.env.dist`. The defaults are available in [Dockerfile](Dockerfile) ### Security