mirror of
https://github.com/darkwire/darkwire.io.git
synced 2025-07-27 13:49:46 +00:00
Switch to redux hooks
This commit is contained in:
parent
644f790ad8
commit
0df6a5097c
@ -31,10 +31,6 @@ export const toggleSocketConnected = payload => async dispatch => {
|
|||||||
dispatch({ type: 'TOGGLE_SOCKET_CONNECTED', payload });
|
dispatch({ type: 'TOGGLE_SOCKET_CONNECTED', payload });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createUser = payload => async dispatch => {
|
|
||||||
dispatch({ type: 'CREATE_USER', payload });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const clearActivities = () => async dispatch => {
|
export const clearActivities = () => async dispatch => {
|
||||||
dispatch({ type: 'CLEAR_ACTIVITIES' });
|
dispatch({ type: 'CLEAR_ACTIVITIES' });
|
||||||
};
|
};
|
||||||
|
@ -40,7 +40,6 @@ describe('App actions', () => {
|
|||||||
[actions.showNotice('test'), 'SHOW_NOTICE'],
|
[actions.showNotice('test'), 'SHOW_NOTICE'],
|
||||||
[actions.toggleSoundEnabled('test'), 'TOGGLE_SOUND_ENABLED'],
|
[actions.toggleSoundEnabled('test'), 'TOGGLE_SOUND_ENABLED'],
|
||||||
[actions.toggleSocketConnected('test'), 'TOGGLE_SOCKET_CONNECTED'],
|
[actions.toggleSocketConnected('test'), 'TOGGLE_SOCKET_CONNECTED'],
|
||||||
[actions.createUser('test'), 'CREATE_USER'],
|
|
||||||
[actions.setLanguage('test'), 'CHANGE_LANGUAGE'],
|
[actions.setLanguage('test'), 'CHANGE_LANGUAGE'],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
import { getSocket } from '@/utils/socket';
|
import { getSocket } from '@/utils/socket';
|
||||||
import { prepare as prepareMessage, process as processMessage } from '@/utils/message';
|
import { prepare as prepareMessage, process as processMessage } from '@/utils/message';
|
||||||
|
import { changeUsername } from '@/reducers/user';
|
||||||
|
|
||||||
export const sendEncryptedMessage = payload => async (dispatch, getState) => {
|
export const sendEncryptedMessage = payload => async (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const msg = await prepareMessage(payload, state);
|
const msg = await prepareMessage(payload, state);
|
||||||
|
switch(msg.original.type){
|
||||||
|
case "CHANGE_USERNAME":
|
||||||
|
dispatch(changeUsername(msg.original.payload));
|
||||||
dispatch({ type: `SEND_ENCRYPTED_MESSAGE_${msg.original.type}`, payload: 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);
|
getSocket().emit('ENCRYPTED_MESSAGE', msg.toSend);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
|
|||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { X, AlertCircle } from 'react-feather';
|
import { X, AlertCircle } from 'react-feather';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { createUser } from '@/reducers/user';
|
||||||
|
|
||||||
import Crypto from '@/utils/crypto';
|
import Crypto from '@/utils/crypto';
|
||||||
import { connect as connectSocket } from '@/utils/socket';
|
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 [loaded, setLoaded] = React.useState(false);
|
||||||
const loading = React.useRef(false);
|
const loading = React.useRef(false);
|
||||||
|
|
||||||
|
const user = useSelector(state => state.user);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let mounted = true;
|
let mounted = true;
|
||||||
|
|
||||||
const createUserLocal = async () => {
|
const createUserLocal = async () => {
|
||||||
const localUsername = username || nanoid();
|
const localUsername = user.username || nanoid();
|
||||||
|
|
||||||
const encryptDecryptKeys = await crypto.createEncryptDecryptKeys();
|
const encryptDecryptKeys = await crypto.createEncryptDecryptKeys();
|
||||||
const exportedEncryptDecryptPrivateKey = await crypto.exportKey(encryptDecryptKeys.privateKey);
|
const exportedEncryptDecryptPrivateKey = await crypto.exportKey(encryptDecryptKeys.privateKey);
|
||||||
@ -309,11 +314,14 @@ export const WithUser = ({ createUser, username, ...rest }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
createUser({
|
const payload = {
|
||||||
username: localUsername,
|
username: localUsername,
|
||||||
publicKey: exportedEncryptDecryptPublicKey,
|
publicKey: exportedEncryptDecryptPublicKey,
|
||||||
privateKey: exportedEncryptDecryptPrivateKey,
|
privateKey: exportedEncryptDecryptPrivateKey,
|
||||||
});
|
};
|
||||||
|
dispatch(createUser(payload));
|
||||||
|
|
||||||
|
dispatch({ type: 'CREATE_USER', payload });
|
||||||
|
|
||||||
loading.current = false;
|
loading.current = false;
|
||||||
setLoaded(true);
|
setLoaded(true);
|
||||||
@ -328,12 +336,13 @@ export const WithUser = ({ createUser, username, ...rest }) => {
|
|||||||
loading.current = false;
|
loading.current = false;
|
||||||
mounted = false;
|
mounted = false;
|
||||||
};
|
};
|
||||||
}, [createUser, loaded, username]);
|
}, [dispatch, loaded, user.username]);
|
||||||
|
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return <Home username={username} {...rest} />;
|
|
||||||
|
return <Home username={user.username} publicKey={user.publicKey} userId={user.id} {...rest} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
WithUser.defaultProps = {
|
WithUser.defaultProps = {
|
||||||
@ -343,10 +352,7 @@ WithUser.defaultProps = {
|
|||||||
WithUser.propTypes = {
|
WithUser.propTypes = {
|
||||||
receiveEncryptedMessage: PropTypes.func.isRequired,
|
receiveEncryptedMessage: PropTypes.func.isRequired,
|
||||||
receiveUnencryptedMessage: PropTypes.func.isRequired,
|
receiveUnencryptedMessage: PropTypes.func.isRequired,
|
||||||
createUser: PropTypes.func.isRequired,
|
|
||||||
activities: PropTypes.array.isRequired,
|
activities: PropTypes.array.isRequired,
|
||||||
username: PropTypes.string.isRequired,
|
|
||||||
publicKey: PropTypes.object.isRequired,
|
|
||||||
members: PropTypes.array.isRequired,
|
members: PropTypes.array.isRequired,
|
||||||
socketId: PropTypes.string.isRequired,
|
socketId: PropTypes.string.isRequired,
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
@ -355,7 +361,6 @@ WithUser.propTypes = {
|
|||||||
openModal: PropTypes.func.isRequired,
|
openModal: PropTypes.func.isRequired,
|
||||||
closeModal: PropTypes.func.isRequired,
|
closeModal: PropTypes.func.isRequired,
|
||||||
iAmOwner: PropTypes.bool.isRequired,
|
iAmOwner: PropTypes.bool.isRequired,
|
||||||
userId: PropTypes.string.isRequired,
|
|
||||||
toggleWindowFocus: PropTypes.func.isRequired,
|
toggleWindowFocus: PropTypes.func.isRequired,
|
||||||
soundIsEnabled: PropTypes.bool.isRequired,
|
soundIsEnabled: PropTypes.bool.isRequired,
|
||||||
persistenceIsEnabled: PropTypes.bool.isRequired,
|
persistenceIsEnabled: PropTypes.bool.isRequired,
|
||||||
|
@ -43,7 +43,6 @@ const WithNewMessageNotification = ({
|
|||||||
const { username, type, text, fileName, locked, newUsername, currentUsername, action } = currentLastMessage;
|
const { username, type, text, fileName, locked, newUsername, currentUsername, action } = currentLastMessage;
|
||||||
|
|
||||||
if (currentLastMessage !== lastMessage && !windowIsFocused) {
|
if (currentLastMessage !== lastMessage && !windowIsFocused) {
|
||||||
setLastMessage(currentLastMessage);
|
|
||||||
if (notificationIsAllowed && notificationIsEnabled) {
|
if (notificationIsAllowed && notificationIsEnabled) {
|
||||||
// Generate the proper notification according to the message type
|
// Generate the proper notification according to the message type
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -79,6 +78,8 @@ const WithNewMessageNotification = ({
|
|||||||
if (soundIsEnabled) beep.play();
|
if (soundIsEnabled) beep.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLastMessage(currentLastMessage);
|
||||||
|
|
||||||
if (unreadMessageCount !== lastUnreadMessageCount) {
|
if (unreadMessageCount !== lastUnreadMessageCount) {
|
||||||
setLastUnreadMessageCount(unreadMessageCount);
|
setLastUnreadMessageCount(unreadMessageCount);
|
||||||
Tinycon.setBubble(unreadMessageCount);
|
Tinycon.setBubble(unreadMessageCount);
|
||||||
|
@ -3,7 +3,6 @@ import { useLoaderData } from 'react-router-dom';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
receiveEncryptedMessage,
|
receiveEncryptedMessage,
|
||||||
createUser,
|
|
||||||
openModal,
|
openModal,
|
||||||
closeModal,
|
closeModal,
|
||||||
toggleWindowFocus,
|
toggleWindowFocus,
|
||||||
@ -26,8 +25,6 @@ const mapStateToProps = state => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
activities: state.activities.items,
|
activities: state.activities.items,
|
||||||
userId: state.user.id,
|
|
||||||
username: state.user.username,
|
|
||||||
publicKey: state.user.publicKey,
|
publicKey: state.user.publicKey,
|
||||||
privateKey: state.user.privateKey,
|
privateKey: state.user.privateKey,
|
||||||
members: state.room.members.filter(m => m.username && m.publicKey),
|
members: state.room.members.filter(m => m.username && m.publicKey),
|
||||||
@ -48,7 +45,6 @@ const mapStateToProps = state => {
|
|||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
receiveEncryptedMessage,
|
receiveEncryptedMessage,
|
||||||
createUser,
|
|
||||||
openModal,
|
openModal,
|
||||||
closeModal,
|
closeModal,
|
||||||
toggleWindowFocus,
|
toggleWindowFocus,
|
||||||
|
@ -206,10 +206,15 @@ describe('Connected Home component', () => {
|
|||||||
</Provider>,
|
</Provider>,
|
||||||
);
|
);
|
||||||
expect(store.getState().app.unreadMessageCount).toBe(1);
|
expect(store.getState().app.unreadMessageCount).toBe(1);
|
||||||
|
expect(notify).toHaveBeenCalledTimes(1);
|
||||||
expect(notify).toHaveBeenLastCalledWith('sender said:', 'new message');
|
expect(notify).toHaveBeenLastCalledWith('sender said:', 'new message');
|
||||||
|
expect(beep.play).toHaveBeenCalledTimes(1);
|
||||||
expect(beep.play).toHaveBeenLastCalledWith();
|
expect(beep.play).toHaveBeenLastCalledWith();
|
||||||
expect(Tinycon.setBubble).toHaveBeenLastCalledWith(1);
|
expect(Tinycon.setBubble).toHaveBeenLastCalledWith(1);
|
||||||
|
|
||||||
|
notify.mockClear();
|
||||||
|
beep.play.mockClear();
|
||||||
|
|
||||||
// Test with sound and notification disabled
|
// Test with sound and notification disabled
|
||||||
await act(() => toggleNotificationEnabled(false)(store.dispatch));
|
await act(() => toggleNotificationEnabled(false)(store.dispatch));
|
||||||
await act(() => toggleSoundEnabled(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(store.getState().app.unreadMessageCount).toBe(2);
|
||||||
expect(notify).toHaveBeenCalledTimes(2);
|
expect(notify).toHaveBeenCalledTimes(0);
|
||||||
expect(beep.play).toHaveBeenCalledTimes(2);
|
expect(beep.play).toHaveBeenCalledTimes(0);
|
||||||
expect(Tinycon.setBubble).toHaveBeenLastCalledWith(2);
|
expect(Tinycon.setBubble).toHaveBeenLastCalledWith(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,27 +1,28 @@
|
|||||||
const initialState = {
|
import { createSlice } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
export const userSlice = createSlice({
|
||||||
|
name: 'user',
|
||||||
|
initialState: {
|
||||||
privateKey: {},
|
privateKey: {},
|
||||||
publicKey: {},
|
publicKey: {},
|
||||||
username: '',
|
username: '',
|
||||||
id: '',
|
id: '',
|
||||||
};
|
},
|
||||||
|
reducers: {
|
||||||
const user = (receivedState, action) => {
|
createUser: (state, action) => {
|
||||||
const state = { ...initialState, ...receivedState };
|
const { privateKey, publicKey, username } = action.payload;
|
||||||
|
state.privateKey = privateKey;
|
||||||
switch (action.type) {
|
state.publicKey = publicKey;
|
||||||
case 'CREATE_USER':
|
state.username = username;
|
||||||
return {
|
state.id = publicKey.n;
|
||||||
...action.payload,
|
},
|
||||||
id: action.payload.publicKey.n,
|
changeUsername: (state, action) => {
|
||||||
};
|
const { newUsername } = action.payload;
|
||||||
case 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME':
|
state.username = newUsername;
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
username: action.payload.newUsername,
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export default user;
|
export const { createUser,changeUsername } = userSlice.actions
|
||||||
|
|
||||||
|
export default userSlice.reducer
|
@ -1,6 +1,6 @@
|
|||||||
import { describe, it, expect, vi } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
|
||||||
import reducer from './user';
|
import reducer, { createUser, changeUsername} from './user';
|
||||||
|
|
||||||
vi.mock('@/i18n', () => {
|
vi.mock('@/i18n', () => {
|
||||||
return {
|
return {
|
||||||
@ -15,7 +15,8 @@ describe('User reducer', () => {
|
|||||||
|
|
||||||
it('should handle CREATE_USER', () => {
|
it('should handle CREATE_USER', () => {
|
||||||
const payload = { publicKey: { n: 'alicekey' }, username: 'alice' };
|
const payload = { publicKey: { n: 'alicekey' }, username: 'alice' };
|
||||||
expect(reducer({}, { type: 'CREATE_USER', payload })).toEqual({
|
|
||||||
|
expect(reducer({},createUser(payload) )).toEqual({
|
||||||
id: 'alicekey',
|
id: 'alicekey',
|
||||||
publicKey: { n: 'alicekey' },
|
publicKey: { n: 'alicekey' },
|
||||||
username: 'alice',
|
username: 'alice',
|
||||||
@ -24,10 +25,8 @@ describe('User reducer', () => {
|
|||||||
|
|
||||||
it('should handle SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', () => {
|
it('should handle SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', () => {
|
||||||
const payload = { newUsername: 'alice' };
|
const payload = { newUsername: 'alice' };
|
||||||
expect(reducer({ username: 'polux' }, { type: 'SEND_ENCRYPTED_MESSAGE_CHANGE_USERNAME', payload })).toEqual({
|
|
||||||
id: '',
|
expect(reducer({ username: 'polux' }, changeUsername(payload))).toEqual({
|
||||||
privateKey: {},
|
|
||||||
publicKey: {},
|
|
||||||
username: 'alice',
|
username: 'alice',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -86,7 +86,7 @@ Then run it. Example:
|
|||||||
$ docker run --init --name darkwire.io --rm -p 3001:3001 darkwire.io
|
$ 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
|
### Security
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user