ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Social Media 만들기 - 19) socket io 사용하기 ( App.js 문제 해결!)
    Spring 2021. 8. 2. 22:48

     

    이번엔 socket io를 사용해서 알림을 가도록 설정해보자. 

    먼저 server에 socket 사용을 위한 세팅을한다. 

     

    서버쪽에 npm i socket.io,

    클라이언트 쪽에 npm i socket.io-client해준다. 

     

     

    const SocketServer = require('./SocketServer')
    
    
    //Socket
    const http = require('http').createServer(app)
    const io = require('socket.io')(http)
    
     
    io.on('connection', socket=>{
        console.log(socket.id + ' Connected')
        SocketServer(socket)
    })
    
    const port = process.env.port || 5000
    http.listen(port, ()=> console.log(`Server is listening on port ${port}.`))

    원래라면 socket io 설정을 app.js에서 하고 

    state을 이용해서 socketclient가 처리해야 하는데, 

    이상하게 app.js에서 state 처리를 하면, 

    socket.emit is not a function 오류가 난다. ㅠㅠ

     

    ---------------------------------------

    그래서 socketClient에 그냥 socket을 열었다. 

    app.js

          <div className="App">
            <div className="main">
              {userInfo && <Header/>}
              {status && <StatusModal/>}
              {userInfo && <SocketClient/>}

    --------------------------------------

    문제를 해결했다. !!!!

     

    소켓을 App.js에서 설정해줄때, 

    dispatch인자에서, socket을 넣어줬기 때문에 emit할 때마다 자꾸 새로운 소켓을 생성했던 것이다!!!!!

    (행복...)

     

    소켓.on, emit이 없다고 하는 것은 소켓이 생성되기 전에 socket client 가실행되었기 때문이다. 

    다음과 같은 조건을 넣어서 해결했다. 

      useEffect(() => {
        const socket = io();
    
          dispatch({
            type:SOCKET,
            payload:socket
          })
          return ()=>socket.close()
        }, [dispatch])
    
    
              {(userInfo && socket.io) && <SocketClient/>}

     

     

    globalConstants

    export const SOCKET='SOCKET'

     

    socketReducers.js

    const { SOCKET } = require("../_constants/globalConstants")
    
    
    const socketReducer = (state = [], action)=>{
        switch(action.type){
            case SOCKET:
                return action.payload;
            default:
                return state;
        }
    }
    
    
    export default socketReducer

    store.js

     

        socket:socketReducer,

     

    socketClient.js

    
    function SocketClient() {
    
        const userLogin = useSelector(state => state.userLogin)
        const {userInfo} = userLogin
    
        // const socket = useSelector(state => state.socket)
        const dispatch = useDispatch()
    
        const socket = io();
    
        useEffect(() => {
            dispatch({
                type:SOCKET,
                payload:socket
            })
    
            return () =>socket.close()
        }, [socket, dispatch])
        
        
        
        return <> </>
    
    }
    
    export default SocketClient

     

    socketServer.js

    let users = []
    
    const SocketServer = (socket) =>{
    
    module.exports=SocketServer

     

    먼저 화면이 켜지게 되면 (connect)

    서로의 server와 client가 주고받을 수 있도록 설정해보자. 

     

     

    클라이언트에서는 socket을 발생시켜서 (dispatch)

    socket정보를 저장해놓았다. 

    socketClient

        //JoinUser
        useEffect(() => {
            socket.emit('joinUser', userInfo.user)
          }, [socket, userInfo.user])

     

    socketServer.js

    유저목록(활성화된상태, 연결된 상태)에 저장한다. 

        //Connect -Disconnect
        socket.on('joinUser', user=>{
            users.push({id:user._id, socketId:socket.id})
            console.log({users})
        } )
    
        socket.on('disconnect', ()=>{
            users = users.filter(user=>user.socketId !== socket.id)
            console.log({users})
        })

     

     

     

    이제 likes를 누를때, 누른 post의 정보가 socket을 통해서 서버로, 서버는 다시

    관련된 유저에게로 전달하도록 하자. 

     

    먼저, like버튼을 눌렀을떄(Post)

    정확한 정보들(following, folllower까지)을 전달하기 위해서 

    라우터에 populate해준다. 

    postRouter.patch('/:id/like',auth, async(req,res)=>{
        try{
            const post = await Post.find({_id:req.params.id, likes:req.user.id})
            if(post.length>0) return res.status(400).json({msg:'You already liked this photo.'})
    
            const likedpost = await Post.findOneAndUpdate({_id:req.params.id},{
                $push:{likes:req.user.id}
            },{new:true})   .sort('-createdAt')
                            .populate("user likes", "avatar username fullname followers following")
                            .populate({
                                path:"comments",
                                populate:{
                                    path:"user likes",
                                    select:"-password"
                                },
                                sort:'-createdAt'
                            })
    
    
            if(!likedpost) return res.status(400).json({msg:'This post does not exist.'})
    
            res.json({
                likedpost
            })
    
        }catch(err){
            return res.status(500).json({message:err.message})
        }
    } )
    
    postRouter.patch('/:id/unlike',auth, async(req,res)=>{
        try{
            const unlikedpost = await Post.findOneAndUpdate({_id:req.params.id},{
                $pull:{likes:req.user.id}
            },{new:true})   .sort('-createdAt')
                            .populate("user likes", "avatar username fullname followers following")
                            .populate({
                                path:"comments",
                                populate:{
                                    path:"user likes",
                                    select:"-password"
                                },
                                sort:'-createdAt'
                            })
    
            if(!unlikedpost) return res.status(400).json({msg:'This post does not exist.'})
    
            res.json({
                unlikedpost
            })
    
        }catch(err){
            return res.status(500).json({message:err.message})
        }
    } )

     

     

    like, unlike를 누를때 Action을 취할때, 

    socket 정보도 함께 넘기도록 하자. 

    socket정보는 state에 저장되어 있다. 

     

    CardFooter.js

                dispatch(likePost(post,socket))
                dispatch(unlikePost(post, socket))

    postAction에 가서 

    socket을 일으켜서 서버에 버튼 누른걸 알린다. 

        try{
            const res = await axios.patch(`/api/post/${post._id}/like`,null, {
                headers:{authorization:`Bearer ${userInfo.token}`}
            })
            
            socket.emit('likePost', res.data.likedpost)
    
    
    try{
            const res = await axios.patch(`/api/post/${post._id}/unlike`,null, {
                headers:{authorization:`Bearer ${userInfo.token}`}
            })
    
            socket.emit('unlikePost', res.data.unlikedpost)

     

     

    socketServer는 일으켜진 정보를 받고, 

    관련 유저에게 다시 그 정보를 넘긴다. 

        //likes
    
        socket.on('likePost', likedpost =>{
            const ids = [...likedpost.user.followers, likedpost.user._id]
            const clients = users.filter(user=> ids.includes(user.id))
    
            if(clients.length>0){
                clients.forEach(client=>{
                    socket.to(`${client.socketId}`).emit('likeToClient', likedpost)
                })
            }
        })
    
        socket.on('unlikePost', unlikedpost =>{
            const ids = [...unlikedpost.user.followers, unlikedpost.user._id]
            const clients = users.filter(user=>ids.includes(user.id))
    
            if(clients.length>0){
                clients.forEach(client=>{
                    socket.to(`${client.socketId}`).emit('unlikeToClient', unlikedpost)
                })
            }
        })

    socketClient

    관련 유저들은 정보를 받고, 

    정보를 업데이트 하도록 한다. 

        //likes
        useEffect(() => {
            socket.on('likeToClient', likedpost=>{
                dispatch({
                    type:UPDATE_POST_SUCCESS,
                    payload:likedpost
                })
            })
    
            return () =>socket.off('likeToClient')
          }, [socket, dispatch])
    
        useEffect(() => {
            socket.on('unlikeToClient', unlikedpost=>{
                console.log(unlikedpost)
                dispatch({
                    type:UPDATE_POST_SUCCESS,
                    payload:unlikedpost
                })
            })
            }, [socket,dispatch])

     

    같은 방식으로 comment create, delete도 작성하자. 

    InputComment.js

            dispatch(createComment({newcomment, post, socket}))

    commentMenu.js

                dispatch(deleteComment({post, comment,socket}))

     

    commentAction.js

        try{
            const res = await axios.post('/api/comment', {...newcomment, postId: post._id, postUserId: post.user._id},{
                headers:{authorization:`Bearer ${userInfo.token}`}
            })
            const newpost = {...post, comments:[...post.comments, newcomment]}
    
            socket.emit('createComment', newpost)
    
    
    
            deleteArr.forEach(async(deletecm)=>{
                 await axios.delete(`/api/comment/${deletecm._id}`, {
                    headers:{authorization:`Bearer ${userInfo.token}`}
                })
    
            })
    
            socket.emit('deleteComment', newpost)

     

     

    socketServer.js

        //Comments
    
        socket.on('createComment', newpost=>{
            const ids = [...newpost.user.followers, newpost.user._id]
            const clients = users.filter(user=>ids.includes(user.id))
    
            if(clients.length>0){
                clients.forEach(client=>{
                    socket.to(`${client.socketId}`).emit('createCommentToClient', newpost)
                })
            }
        })
        socket.on('deleteComment', newpost=>{
            const ids = [...newpost.user.followers, newpost.user._id]
            const clients = users.filter(user=>ids.includes(user.id))
    
            if(clients.length>0){
                clients.forEach(client=>{
                    socket.to(`${client.socketId}`).emit('deleteCommentToClient', newpost)
                })
            }
        })

     

     

    socketClient.js

    
        //Comments
        useEffect(() => {
            socket.on('createCommentToClient', newpost=>{
                console.log(newpost)
                dispatch({
                    type:UPDATE_POST_SUCCESS,
                    payload:newpost
                })
            })
            }, [socket,dispatch])
        
        useEffect(() => {
            socket.on('deleteCommentToClient', newpost=>{
                console.log(newpost)
                dispatch({
                    type:UPDATE_POST_SUCCESS,
                    payload:newpost
                })
            })
            }, [socket,dispatch])

     

     

     

     




    follow기능도 같은 방식으로 구현해보자. 

    먼저 유저를 반환할때, (follow, unfollow에서 )

    userLogin할때와 같은 상태로 팔로우당한 유저를 반환해주도록 설정한다. 

            const followedUser = await User.findOneAndUpdate({_id: req.params.id}, { 
                $push: {'followers':req.body._id}
            }, {new: true}).populate("followers following", "avatar username fullname followers following")
    
            res.send({
                newUser,
                followedUser
            })
            
            
            const unfollowedUser =  await User.findOneAndUpdate({_id:req.params.id}, {
                $pull:{'followers':req.body._id}
            }, {new:true}).populate("followers following", "avatar username fullname followers following")
    
            res.json({
                newUser,
                unfollowedUser
            })

     

    FollowBtn

            await dispatch(followUserProfile(user,socket))
            await dispatch(unfollowUserProfile(user,socket))

     

    profileAction

            const res = await axios.patch(`/api/users/${user._id}/follow`,userInfo.user,{
                headers:{authorization:`Bearer ${userInfo?.token}`}
            } )
    
            socket.emit('follow', res.data.followedUser)
    
    
            const res = await axios.patch(`/api/users/${user._id}/unfollow`,userInfo.user,{
                headers:{authorization:`Bearer ${userInfo?.token}`}
            } )
    
            socket.emit('unfollow', res.data.unfollowedUser)

    socketServer

        // Follow
        socket.on('follow', data=>{
            const user = users.find(user=>user.id ===data._id)
            // console.log(user)
            // console.log(data)
            user &&  socket.to(`${user.socketId}`).emit('followToClient', data)
        })
    
        socket.on('unfollow', data=>{
            const user = users.find(user=>user.id ===data._id)
            // console.log(data)
            user &&  socket.to(`${user.socketId}`).emit('unfollowToClient', data)
        })

     

    socketClient에서 

    유저를 다시 로그인 시킴으로서, 유저의 정보를 바꿔준다. 

     

    
        // Follow
    
        useEffect(() => {
            socket.on('followToClient', data =>{
                dispatch({
                    type:USER_LOGIN_SUCCESS,
                    payload:{
                        user:data,
                        token:userInfo.token
                    }
                })
                localStorage.setItem('userInfo',JSON.stringify({token:userInfo.token, user:data}))
    
            })
            return () => socket.off('followToClient')
        }, [socket, dispatch, userInfo])
    
        useEffect(() => {
            socket.on('unfollowToClient', data =>{
    
                dispatch({
                    type:USER_LOGIN_SUCCESS,
                    payload:{
                        user:data,
                        token:userInfo.token
                    }
                })
    
                localStorage.setItem('userInfo',JSON.stringify({token:userInfo.token, user:data}))
    
            })
            return () =>socket.off('unfollowToClient')
        }, [socket, dispatch, userInfo])

     

    팔로우나 언팔로우 당하면, 당한사람의 정보도 새로 업데이트 된다. 

     

     

     

     

Designed by Tistory.