개발/React

리액트, Node JS - Socket.IO로 로그인 유저 관리하기 (Managing Logged-in Users with Socket.IO)

피로물든딸기 2024. 3. 27. 23:32
반응형

리액트 전체 링크

Node JS 전체 링크

 

참고

- webSocket으로 로그인 유저 관리하기

- Socket.IO로 로그인 유저 관리하기

- Socket.IO로 채팅하기

- Socket.IO로 Toast UI Editor 동시 편집하기

 

이전 글에서 webSocket을 이용하여 로그인 유저를 처리하였다.

여기서는 socket.io를 사용해 보자.


Socket.IO Server

 

Node 서버에서 socket.io를 사용하기 위해 라이브러리를 설치한다.

npm install socket.io

 

예시 코드는 다음과 같다.

const { Server } = require("socket.io");

const io = new Server("3333", {
  cors: {
    origin: "http://localhost:3000",
  },
});

io.sockets.on("connection", (socket) => {

  socket.on("login", () => { // 사용자 정의
  });

  socket.on("logout", () => { // 사용자 정의
  });

  socket.on("disconnect", () => {
  });
});

 

참고로 corsorigin 설정 시, http://localhost:3000/ 끝에 "/"가 있는 경우 에러가 발생할 수 있다.

  cors: {
    origin: "http://localhost:3000/", // error
  },

 

리액트(클라이언트)에서 "login"을 emit 하고 parameteruser를 보낸다고 가정하자.

webSocket.emit("login", user);

 

webSocket에서는 type으로 case를 나눴지만,  socket.ioon에서 login을 정의해서 처리하면 된다.

  socket.on("login", (data) => {
    const clientId = socket.id;

    loginInfo.set(clientId, {
      userId: data,
      timestamp: new Date(),
    });

    io.sockets.emit("loginRespond", { // 모든 클라이언트에 전송
      loginInfo: Object.fromEntries([...loginInfo]),
    }); 
  });

 

또한 클라이언트 ID를 따로 만들 필요 없이 socket.io에서 직접 만들어준다.

clientId = socket.id;

 

리액트에서 서버와 연결될 때, id가 생성되는 것을 확인할 수 있다.

 

그리고 서버에서 emitloginRespondedata는 리액트에서 callback 함수로 처리할 수 있다.

socketIO.on("loginRespond", callback);

 

webSocket 서버에서 처리했던 로직은 socket.io에서 아래와 같이 구현될 수 있다.

로그아웃을 한 클라이언트에게는 메시지를 보낼 필요가 없으므로, broadcast.emit을 이용하였다.

io.sockets.on("connection", (socket) => {
  socket.on("login", (data) => {
    const clientId = socket.id;

    loginInfo.set(clientId, {
      userId: data,
      timestamp: new Date(),
    });

    io.sockets.emit("loginRespond", { // 모든 클라이언트에 전송
      loginInfo: Object.fromEntries([...loginInfo]),
    }); 
  });

  socket.on("logout", (clientId) => {
    loginInfo.delete(clientId);
    socket.broadcast.emit("logoutRespond", { // 자기 자신을 제외하고 전송
      loginInfo: Object.fromEntries([...loginInfo]),
    }); 
  });

  socket.on("disconnect", () => {
    const clientId = socket.id;

    loginInfo.delete(clientId);
    socket.broadcast.emit("logoutRespond", { // 자기 자신을 제외하고 전송
      loginInfo: Object.fromEntries([...loginInfo]),
    }); 
  });
});

Socket.IO Client

 

socket.io 라이브러리를 설치한다.

npm install socket.io-client

 

loginlogout 예시는 다음과 같다.

  useEffect(() => {
    if (!socketIO) return;

    socketIO.on("loginRespond", respondCallback);
    return () => {
      socketIO.off("loginRespond", respondCallback);      
    };
  }, []);

  useEffect(() => {
    if (!socketIO) return;

    socketIO.on("logoutRespond", respondCallback);
    return () => {
      socketIO.off("logoutRespond", respondCallback);
      socketIO.emit("logout", socketIO.id);
    };
  }, []);

 

로그인, 로그아웃으로 서버에서 로그인된 유저를 처리하므로 callback 함수는 두 경우 동일하다.

  const respondCallback = (message) => {
    let { loginInfo } = message;
    let value = Object.values(loginInfo);
    setUserList([...value]);
  }

전체 코드는 다음과 같다.

 

socketIOServer.js

const { Server } = require("socket.io");

const io = new Server("3333", {
  cors: {
    origin: "http://localhost:3000",
  },
});

const loginInfo = new Map();

io.sockets.on("connection", (socket) => {
  socket.on("login", (data) => {
    const clientId = socket.id;

    loginInfo.set(clientId, {
      userId: data,
      timestamp: new Date(),
    });

    io.sockets.emit("loginRespond", { // 모든 클라이언트에 전송
      loginInfo: Object.fromEntries([...loginInfo]),
    }); 
  });

  socket.on("logout", (clientId) => {
    loginInfo.delete(clientId);
    socket.broadcast.emit("logoutRespond", { // 자기 자신을 제외하고 전송
      loginInfo: Object.fromEntries([...loginInfo]),
    }); 
  });

  socket.on("disconnect", () => {
    const clientId = socket.id;

    loginInfo.delete(clientId);
    socket.broadcast.emit("logoutRespond", { // 자기 자신을 제외하고 전송
      loginInfo: Object.fromEntries([...loginInfo]),
    }); 
  });
});

 

SocketIOClient.js

import React, { useEffect, useState } from "react";
import { io } from "socket.io-client";

let socketIO = io("http://localhost:3333");

const SocketIOClient = () => {
  const [user, setUser] = useState("");
  const [userList, setUserList] = useState([]);
  const [loginCheck, setLoginCheck] = useState(false);

  const respondCallback = (message) => {
    let { loginInfo } = message;
    let value = Object.values(loginInfo);
    setUserList([...value]);
  }

  const login = () => {
    if (user === "") return;

    setLoginCheck(true);
    socketIO.emit("login", user);
  };

  useEffect(() => {
    if (!socketIO) return;

    console.log(socketIO);

    socketIO.on("loginRespond", respondCallback);
    return () => {
      socketIO.off("loginRespond", respondCallback);      
    };
  }, []);

  useEffect(() => {
    if (!socketIO) return;

    socketIO.on("logoutRespond", respondCallback);
    return () => {
      socketIO.off("logoutRespond", respondCallback);
      socketIO.emit("logout", socketIO.id);
    };
  }, []);

  return (
    <div style={{ margin: 10 }}>
      <input
        value={user}
        onChange={(e) => setUser(e.target.value)}
        disabled={loginCheck}
      />
      <button onClick={login} disabled={loginCheck}>
        login
      </button>
      <div>
        <p>현재 로그인한 사람</p>
        {userList.map((item, index) => (
          <p key={index}>
            {item.userId === user ? `${user} [ME]` : item.userId} (
            {item.timestamp}){" "}
          </p>
        ))}
      </div>
    </div>
  );
};

export default SocketIOClient;
반응형