瀏覽代碼

socket connect and get users , some send functional

serg1557733 1 年之前
父節點
當前提交
1e9a03465f

+ 6 - 4
backend/app.js

@@ -26,7 +26,6 @@ const PORT = process.env.PORT || 5000;
 const TOKEN_KEY = process.env.TOKEN_KEY || 'rGH4r@3DKOg06hgj'; 
 const HASH_KEY = 7;
 
-
 const generateToken = (id, userName, isAdmin) => {
     const payload = {
         id,
@@ -85,6 +84,7 @@ app.post('/login', async (req, res) => {
         res.json({
             token:  generateToken(dbUser.id, dbUser.userName, dbUser.isAdmin),
         })
+        
 
     } catch (e) {
         console.log(e);
@@ -94,10 +94,11 @@ app.post('/login', async (req, res) => {
 
 
 io.use( async (socket, next) => {
-    const token = socket.handshake.auth.token;
+    const token = socket.handshake.auth.token; 
     const sockets = await io.fetchSockets();
-    
+   
     if(!token) {
+        console.log('socket')
         socket.disconnect();
         return;
     }
@@ -134,9 +135,10 @@ io.use( async (socket, next) => {
 
 io.on("connection", async (socket) => {
     const userName = socket.user.userName;
+
     const sockets = await io.fetchSockets();
     const dbUser = await getOneUser(userName);
-
+    
     io.emit('usersOnline', sockets.map((sock) => sock.user)); // send array online users  
     socket.emit('connected', dbUser); //socket.user
    

+ 45 - 0
frontend/package-lock.json

@@ -11,6 +11,7 @@
         "@emotion/react": "^11.7.1",
         "@emotion/styled": "^11.6.0",
         "@mui/material": "^5.2.7",
+        "@reduxjs/toolkit": "^1.8.3",
         "@testing-library/jest-dom": "^5.16.1",
         "@testing-library/react": "^12.1.2",
         "@testing-library/user-event": "^13.5.0",
@@ -3069,6 +3070,29 @@
         "url": "https://opencollective.com/popperjs"
       }
     },
+    "node_modules/@reduxjs/toolkit": {
+      "version": "1.8.3",
+      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.3.tgz",
+      "integrity": "sha512-lU/LDIfORmjBbyDLaqFN2JB9YmAT1BElET9y0ZszwhSBa5Ef3t6o5CrHupw5J1iOXwd+o92QfQZ8OJpwXvsssg==",
+      "dependencies": {
+        "immer": "^9.0.7",
+        "redux": "^4.1.2",
+        "redux-thunk": "^2.4.1",
+        "reselect": "^4.1.5"
+      },
+      "peerDependencies": {
+        "react": "^16.9.0 || ^17.0.0 || ^18",
+        "react-redux": "^7.2.1 || ^8.0.2"
+      },
+      "peerDependenciesMeta": {
+        "react": {
+          "optional": true
+        },
+        "react-redux": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@rollup/plugin-babel": {
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz",
@@ -14029,6 +14053,11 @@
       "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
       "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
     },
+    "node_modules/reselect": {
+      "version": "4.1.6",
+      "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz",
+      "integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ=="
+    },
     "node_modules/resolve": {
       "version": "1.21.0",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz",
@@ -18816,6 +18845,17 @@
       "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz",
       "integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA=="
     },
+    "@reduxjs/toolkit": {
+      "version": "1.8.3",
+      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.3.tgz",
+      "integrity": "sha512-lU/LDIfORmjBbyDLaqFN2JB9YmAT1BElET9y0ZszwhSBa5Ef3t6o5CrHupw5J1iOXwd+o92QfQZ8OJpwXvsssg==",
+      "requires": {
+        "immer": "^9.0.7",
+        "redux": "^4.1.2",
+        "redux-thunk": "^2.4.1",
+        "reselect": "^4.1.5"
+      }
+    },
     "@rollup/plugin-babel": {
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz",
@@ -26727,6 +26767,11 @@
       "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
       "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
     },
+    "reselect": {
+      "version": "4.1.6",
+      "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz",
+      "integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ=="
+    },
     "resolve": {
       "version": "1.21.0",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz",

+ 1 - 0
frontend/package.json

@@ -6,6 +6,7 @@
     "@emotion/react": "^11.7.1",
     "@emotion/styled": "^11.6.0",
     "@mui/material": "^5.2.7",
+    "@reduxjs/toolkit": "^1.8.3",
     "@testing-library/jest-dom": "^5.16.1",
     "@testing-library/react": "^12.1.2",
     "@testing-library/user-event": "^13.5.0",

+ 1 - 3
frontend/src/App.js

@@ -1,12 +1,10 @@
 import { LoginForm } from './components/loginForm/LoginForm';
 import { ChatPage } from './components/chatPage/ChatPage';
-import { useState } from 'react';
 import { useSelector } from 'react-redux';
-import { store } from './store';
 
 export default function App() {
 
-    const token = useSelector(state => state.userDataReducer.token) 
+    const token = useSelector(state => localStorage.getItem('token') || state.userDataReducer.token);
     
     return token ? <ChatPage /> : <LoginForm/>
 };

+ 26 - 82
frontend/src/components/chatPage/ChatPage.js

@@ -1,9 +1,8 @@
-import { useEffect, useState, useMemo, useRef, Fragment} from 'react';
+import { useEffect, useMemo, useRef, Fragment} from 'react';
 import { MessageForm } from './messageForm/MessageForm';
 import {Button,Avatar, Box} from '@mui/material';
 import { UserInfo } from './userInfo/UserInfo';
 import { dateFormat } from './utils/dateFormat';
-import {io} from 'socket.io-client';
 import './chatPage.scss';
 import { scrollToBottom } from './utils/scrollToBottom';
 import { banUser } from './service/banUser';
@@ -11,84 +10,32 @@ import { muteUser } from './service/muteUser';
 import {sendMessage} from './service/sendMessage';
 import { store } from '../../store';
 import { removeToken} from '../../reducers/userDataReducer'
+import { useDispatch, useSelector } from 'react-redux';
+import {getSocket} from'../../reducers/socketReducer';
 
+export const ChatPage = () => {
 
-import { useDispatch } from 'react-redux';
+    const dispatch = useDispatch();
+    const token = useSelector(state => localStorage.getItem('token') || state.userDataReducer.token);
+
+    const startMessages = useSelector(state => state.getUserSocketReducer.startMessages)
+    const user = useSelector(state => state.getUserSocketReducer.socketUserData)
+    const usersOnline = useSelector(state => state.getUserSocketReducer.usersOnline)
+    const allUsers = useSelector(state => state.getUserSocketReducer.allUsers)
 
-export const ChatPage = () => {
-    
-    const [socket, setSocket] = useState(null);
-    const [messages, setMessages] = useState([])
-    const [user, setUser] = useState({})
-    const [usersOnline, setUsersOnline] = useState([])
-    const [allUsers, setAllUsers] = useState([])
     const randomColor = require('randomcolor'); 
     const endMessages = useRef(null);
-    const token = localStorage.getItem('token');
-    const dispatch = useDispatch();
-
+    
     useEffect(() => {
         if(token){
-            
-            try {
-                setSocket(io.connect( 
-                        process.env.REACT_APP_SERVER_URL || 'http://localhost:5000', 
-                        {auth: {token}})
-                        )
-            } catch (error) {
-                console.log(error)
-            } 
-        }
-    }, [token])
-
-    useEffect(() => {
-
-        if(socket){
-            socket.on('connected', (data) => {
-                setUser(data);
-                }).on('error', (e) => {
-                console.log('On connected', e)
-            }); 
-            socket.on('allmessages', (data) => {
-                    setMessages(data)
-                    }).on('error', (e) => {
-                    console.log('allmessages', e)
-            }); 
-            socket.on('usersOnline', (data) => {
-                setUsersOnline(data)
-                }).on('error', (e) => {
-                console.log(e)
-            });  
-            socket.on('allDbUsers', (data) => {
-                setAllUsers(data);
-                }).on('error', (e) => {
-                console.log(e)
-            }); 
-            socket.on('disconnect', (data) => {
-                if(data === 'io server disconnect') {
-                   socket.disconnect();
-                   store.dispatch(removeToken()); 
-                } 
-                }).on('error', (e) => {
-                console.log('error token', e)
-            });  
-            socket.on('message', (data) => {
-                setMessages((messages) => [...messages, data] )
-                }).on('error', (e) => {
-                console.log(e)
-            }); 
-             
+            dispatch(getSocket())
         }
+    }, [])
 
-        // return () => {
-        //     socket.off('connected');
-        //     socket.off('allmessages');
-        // }
-    }, [socket])
 
     useEffect(() => {
         scrollToBottom(endMessages)
-      }, [messages]);
+      }, [startMessages]);
 
     let userColor = useMemo(() => randomColor(),[]);//color for myavatar
 
@@ -108,7 +55,7 @@ export const ChatPage = () => {
                 }}>
                     <Box className='messageBox'>                     
                         {
-                        messages.map((item, i) =>
+                        startMessages.map((item, i) =>
                         <Fragment key={i} >
                             <Avatar 
                                 sx={
@@ -174,10 +121,7 @@ export const ChatPage = () => {
                         <div ref={endMessages}></div>
     
                         </Box>
-                            <MessageForm 
-                                    data = {user} 
-                                    sendMessage = {data => sendMessage(data, socket)}>
-                            </MessageForm>
+                            <MessageForm />
                         </Box>
 
                         <Box
@@ -188,10 +132,10 @@ export const ChatPage = () => {
                         <Button 
                                 sx={{margin:'10px 5px'}}
                                 variant="outlined"
-                                onClick={(e)=> {
-                                            socket.disconnect();
-                                            localStorage.removeItem('token');
-                                            dispatch(removeToken());
+                                onClick={()=> {
+                                        localStorage.removeItem('token');
+                                       // socket.disconnect(); 
+                                        dispatch(removeToken());
                                             }}>
                                 Logout
                         </Button>
@@ -217,28 +161,28 @@ export const ChatPage = () => {
                                             <Button
                                                 variant="contained"
                                                 onClick={()=>{
-                                                    muteUser(item.userName, item.isMutted, socket)
+                                                   //muteUser(item.userName, item?.isMutted, socket)
                                                     }}
                                                 sx={{
                                                     margin:'3px',
                                                     height: '25px'
                                                 }}>
-                                                    {item.isMutted
+                                                    {/* {item.isMutted
                                                     ? 
                                                     'unmute'
-                                                    : 'mute'}
+                                                    : 'mute'} */}
                                             </Button>
 
                                             <Button
                                                 variant="contained"
                                                 onClick={()=>{
-                                                    banUser(item.userName, item.isBanned, socket)
+                                                //banUser(item.userName, item.isBanned, socket)
                                                 }}
                                                 sx={{
                                                     margin:'3px',
                                                     height: '25px'
                                                 }}>
-                                                    {item.isBanned
+                                                    {item?.isBanned
                                                 ? 'unban'
                                                 : 'ban'}
                                             </Button>

+ 23 - 16
frontend/src/components/chatPage/messageForm/MessageForm.js

@@ -1,20 +1,27 @@
 import TextareaAutosize from '@mui/material/TextareaAutosize';
 import Button from '@mui/material/Button';
-import { useState } from 'react';
 import Box from '@mui/material/Box';
+import { useDispatch, useSelector } from 'react-redux';
+import { setMessage } from '../../../reducers/messageReducer';
+import { sendMessage } from '../../../reducers/messageReducer';
 
-export const MessageForm = ({sendMessage, data}) => {
 
-    const [message, setMessage] = useState({message: ''});
+export const MessageForm = () => {
+
+    const dispatch = useDispatch();    
+    const message = useSelector(state => state.userName);
+    const user = useSelector(state => state.getUserSocketReducer.socketUserData)
+    const socket = useSelector(state => state.getUserSocketReducer.socket)
+    
 
     return (
         <Box 
             component="form" 
-            onSubmit = {(e) =>
+            onSubmit = {e  =>
                 {
                     e.preventDefault()
-                    sendMessage(message);
-                    setMessage({message: ''});
+                     dispatch(sendMessage({user, socket}))
+                    
                 }}
                 
                 sx={{
@@ -26,18 +33,18 @@ export const MessageForm = ({sendMessage, data}) => {
                         id="outlined-basic" 
                         label="Type a message..." 
                         variant="outlined" 
-                        value={message.message}
+                        value={message}
                         placeholder='type you message...'
                         minRows={3}
                         maxRows={4}
-                        onKeyPress={(e) => {
-                            if (e.key === "Enter")   {
-                                e.preventDefault();
-                                sendMessage(message);
-                                setMessage({message: ''});
-                            }
-                        }}
-                        onChange={e => setMessage({...message, message: e.target.value})}
+                        // onKeyPress={(e) => {
+                        //     if (e.key === "Enter")   {
+                        //         e.preventDefault();
+                        //         dispatch(sendStoreMessage())
+                        //         dispatch(setMessage({message: ''}));// add localstorage save message later
+                        //     }
+                        // }}
+                        onChange={e => dispatch(setMessage({message: e.target.value}))} 
                         style={{
                             width: '80%',
                             resize: 'none',
@@ -47,7 +54,7 @@ export const MessageForm = ({sendMessage, data}) => {
                     <Button 
                         variant="contained" 
                         type='submit'
-                        disabled={data.isMutted}
+                        disabled={user?.isMutted}
                         style={{
                             width: '20%',
                         }}

+ 2 - 1
frontend/src/components/loginForm/LoginForm.js

@@ -4,13 +4,14 @@ import Container from '@mui/material/Container';
 import Box from '@mui/material/Box';
 import { Modal } from '../modalMessage/Modal';
 import { useDispatch, useSelector } from 'react-redux';
-import  {setUserName, setUserPassword, getUserData} from '../../reducers/userDataReducer'
+import  {setUserName, setUserPassword, getUserData} from '../../reducers/userDataReducer';
 
 export const LoginForm = () => {
 
     const dispatch = useDispatch();
     const userName = useSelector(state => state.userName);
     const password = useSelector(state => state.password);
+
     return (
         <Container maxWidth="xs">
             <Box

+ 1 - 1
frontend/src/components/modalMessage/Modal.js

@@ -15,6 +15,6 @@ export const Modal = () => {
     return <Alert 
                 severity="error"
                 sx={{display: (text ? 'block':'none' )}}>
-            {text}
+                {text}
             </Alert>
 };

+ 0 - 0
frontend/src/reducers/adminSocketReducer.js


+ 33 - 0
frontend/src/reducers/messageReducer.js

@@ -0,0 +1,33 @@
+import { createSlice} from '@reduxjs/toolkit';
+
+const initialState = {
+    message:'',
+    time: ''
+}
+
+export const sendMessageToSocket = (state, data) => {
+             if (state.message && state.message.length < 200) {    //remove to other file
+                data.socket.emit('message', {...data.user, message: state.message}); 
+            } 
+    };
+
+const messageReducerSlice = createSlice({
+    name: 'messageReducer',
+    initialState,
+    reducers: {
+        setMessage: (state, action) => {state.message = action.payload.message},
+        sendMessage: (state, action) => sendMessageToSocket(state, action.payload),
+        clearMessage: (state) => {state.message = ''}
+    },
+});
+
+const {actions, reducer} = messageReducerSlice;
+const messageReducer = reducer;
+
+export default messageReducer;
+
+export const {
+    setMessage, 
+   sendMessage,
+    clearMessage
+    } = actions;

+ 83 - 22
frontend/src/reducers/socketReducer.js

@@ -1,22 +1,83 @@
-// const initialState = {
-//     socket: null
-//   }
-  
-// const socketReducer = (state = initialState, action) => {
-//     switch (action.type){
-//     case 'SET_SOCKET':  
-//         return {...state, 
-//             socket:
-//               action.socket
-//             };
-//     case 'REMOVE_SOCKET':  
-//         return {...state, 
-//             socket: 
-//               initialState.socket
-//             };
-//     default:
-//       return state
-//     }
-//   };
-
-//  export default socketReducer;
+import {createSlice } from '@reduxjs/toolkit';
+import {io} from 'socket.io-client';
+import { store } from '../store';
+import { removeToken } from './userDataReducer';
+
+
+const initialState = {
+    socketStatus: 'idle',
+    socket: null,
+    socketUserData: {},
+    usersOnline: [],
+    startMessages: [],
+    allUsers: []
+}
+
+const SOCKET_URL =  process.env.REACT_APP_SERVER_URL || 'http://localhost:5000'; 
+
+const connectToSocket = () => {
+        try {
+            const token = localStorage.getItem('token');
+            if(token){
+                const socket = io.connect( 
+                    SOCKET_URL, 
+                    {auth: {token}})
+                    socket.on('connected', data => {
+                                store.dispatch(getUser(data));
+                            })
+                            .on('allmessages', (data) => {
+                                store.dispatch(getAllMessages(data));
+                            })
+                            .on('usersOnline', (data) => {
+                                store.dispatch(getUsersOnline(data));
+                            })
+                            .on('allDbUsers', (data) => {
+                                store.dispatch(getAllUsers(data));
+                            })
+                            .on('disconnect', (data) => {
+                                if(data === 'io server disconnect') {
+                                    socket.disconnect();
+                                    store.dispatch(removeToken()); 
+                                }
+                            })
+                            .on('error', e => {console.log('On connected', e)}); 
+                return socket;       
+            }   
+        } catch (error) {
+            console.log('error connecting to socket ', error)
+        } 
+    };
+
+    
+export const getUserSocketSlice = createSlice({
+    name: 'userSocket',
+    initialState,
+    reducers: {
+        removeSocket: state => {
+            state.socket = null
+            state.socketStatus = 'disconnected'},
+        getSocket: state => {
+            state.socket = connectToSocket();
+            state.socketStatus = 'connected';
+        },
+        getUser: (state, action) => {state.socketUserData = action.payload},
+        getAllMessages: (state, action) => {state.startMessages = action.payload},
+        getUsersOnline: (state, action) => {state.usersOnline = action.payload},
+        getAllUsers: (state, action) => {state.allUsers = action.payload}
+        }
+    }
+);
+
+
+const {actions, reducer} = getUserSocketSlice;
+const getUserSocketReducer = reducer;
+
+export default getUserSocketReducer;
+export const {
+    removeSocket,
+    getSocket, 
+    getUser,
+    getAllMessages,
+    getUsersOnline,
+    getAllUsers
+} = actions;

+ 23 - 19
frontend/src/reducers/userDataReducer.js

@@ -8,7 +8,7 @@ const initialState = {
     password: '',
     userLoadingStatus: 'idle',
     token: '',
-    socket: null,
+    socket: null, 
     responseMessage: ''
 }
 
@@ -18,18 +18,19 @@ export const getUserData = createAsyncThunk(
     'userData/getUser',
      ( t , thunkAPI) => {
       const userData = thunkAPI.getState().userDataReducer;
-          if(userData.userName){
-        if(isValidPayload({...userData}) && isValidUserName({...userData}))
-            console.log('getUserData', userData)
-            try {
-                const response =  sendForm(POST_URL, userData);
-                return response;
-            }catch (err) {
-                console.log('err', err)
-                return err?.message
+        if(userData.userName){
+            if(isValidPayload({...userData}) && isValidUserName({...userData}))
+                try {
+                    const response =  sendForm(POST_URL, userData);
+                    return response;
+                }catch (err) {
+                    console.log('error getUserData thunk', err)
+                    return err?.message
+                }
             }
-        }
+        
     });
+                    
 
 const getUserDataSlice = createSlice({
     name: 'userData',
@@ -37,30 +38,33 @@ const getUserDataSlice = createSlice({
     reducers: {
         setUserName: (state, action) => {state.userName = action.payload.userName},
         setUserPassword: (state, action) => {state.password = action.payload.password},
-        removeToken: state => {state.token = ''},
-        deleteResponseMessage: state => {state.responseMessage = ''}
+        removeToken: state => {
+            state.token = ''
+        },
+        deleteResponseMessage: state => {state.responseMessage = ''},
     },
     extraReducers: (builder) => { 
        builder
           .addCase(getUserData.fulfilled, (state, action) => {
-            state.userLoadingStatus = 'fulfilled'
-            if(action.payload.token){
+            if(action.payload?.token){
                 state.token = action.payload.token
+                state.userLoadingStatus = 'success'
                 localStorage.setItem('token', action.payload.token)
             }
             if(action.payload?.message){
                 state.responseMessage = action.payload.message
-                localStorage.setItem('token', action.payload.token)
+                state.userLoadingStatus = 'error';
             }
           })
            .addCase(getUserData.rejected, (state, action) => {
                state.userLoadingStatus = 'error';
                if(action.payload?.message){
                 state.responseMessage = action.payload.message
-            } 
+            }
             state.responseMessage = 'Something went wrong...'
           })
-        }
+        },
+        
 });
 
 const {actions, reducer} = getUserDataSlice;
@@ -71,5 +75,5 @@ export const {
     setUserName,
     setUserPassword,
     removeToken,
-    deleteResponseMessage
+    deleteResponseMessage,
 } = actions;

+ 8 - 4
frontend/src/store.js

@@ -1,12 +1,16 @@
-import socketReducer from './reducers/socketReducer';
+//import getUserSocketReducer from './reducers/socketReducer'
 import userDataReducer from './reducers/userDataReducer';
+import getUserSocketReducer from './reducers/socketReducer';
+import messageReducer from './reducers/messageReducer';
+import { configureStore } from '@reduxjs/toolkit';
 
-import { configureStore } from '@reduxjs/toolkit'
 
 
 export  const store = configureStore({
-    reducer: {socketReducer, userDataReducer},
-    middleware: getDefaultMiddleware => getDefaultMiddleware(),
+    reducer: {userDataReducer, getUserSocketReducer, messageReducer},
+    middleware: getDefaultMiddleware => getDefaultMiddleware({
+        serializableCheck: false
+      }),
     devTools: process.env.NODE_ENV !== 'production',
 })