유저 실시간 접속상태
socketio로 유저 접속 시 백에서 배열에 저장해놨다가 중복검사를 해서 돌려주는 코드를 작성했었다.
그 배열을 돌려주는 객체에서 토큰과 유저명을 가져와 로그인중인지 확인 하는 방법을 선택했지만, 그러려면 useQuery랑 같이 사용해야하고 그 과정이 너무 복잡했다.
socket.io만 사용해서 실시간으로 접속상태를 바꾸고 싶어서 소켓을 통해 데이터를 주고 받고 접속하면 온라인 상태인 것 까지는 성공했다.
하지만 브라우저를 종료할 경우 실시간으로 접속상태가 바뀌지도 않고 로그아웃이 되지도 않았다.
하루종일 너무 과몰입해서 머리가 조금 꽉 막힌 상태여서 조금 쉬었다가 다시 시도해봤다.
// 서버
const SocketIO = require('socket.io');
module.exports = (server, app) => {
const io = new SocketIO.Server(server, {
path: '/socket.io',
cors: {
origin: '*',
methods: ['GET', 'POST'],
credentials: true,
},
});
소켓 서버를 생성 할 때 cors문제가 생기지 않으려면 origin주소를 설정해야한다. 이건 물론 배포할때 URL을 다시 설정해야한다.
// 클라이언트
const BACK_URL = 'http://localhost:8000';
useEffect(() => {
const socketIo = io.connect(`${BACK_URL}/online`, {
path: '/socket.io',
cors: { origin: '*', credentials: true },
transports: ['websocket'],
});
})
서버를 이렇게 생성해서 연결을 시켜놨다.
const onlineMap = {};
module.exports = (server, app) => {
const io = new SocketIO.Server(server, {
path: '/socket.io',
cors: {
origin: '*',
methods: ['GET', 'POST'],
credentials: true,
},
});
app.set('io', io);
app.set('onlineMap', onlineMap);
io.of('/online').on('connection', (socket) => {
const newNamespace = socket.nsp;
// 온라인 유저 저장할 빈 객체 생성
if (!onlineMap[socket.nsp.name]) {
onlineMap[socket.nsp.name] = {};
}
// 첫 연결 시에도 온라인 유저리스트 보내기
newNamespace.emit('onlineList', Object.values(onlineMap[socket.nsp.name]));
// 클라이언트에 유저 정보 요청
socket.emit('userConnect', socket.nsp.name);
// 유저정보 받아서 객체에 저장
socket.on('login', (data) => {
onlineMap[socket.nsp.name][socket.id] = data.userName;
// 클라이언트로 온라인 유저리스트 보내기
newNamespace.emit('onlineList', Object.values(onlineMap[socket.nsp.name]));
});
});
};
접속한 유저와 소켓id를 객체로 저장하길 위해 빈 객체를 생성해놓고, 서버에 클라이언트가 접속 하면 유저에게 유저명을 요청한다.
// 클라이언트
setSocket(socketIo); // 소켓 연결되면 따로 socket에 다시 저장
if (userloginStatus && userName) {
socketIo?.on('userConnect', (data) => {
socketIo?.emit('login', { userName: userName }); // 유저명 보냄
});
}
받은 유저명과 소켓id를 객체에 저장하고 온라인 유저리스트를 클라이언트에 돌려준다.
서버로 요청을 계속 보내지 않기 위해 한 번 연결이 되면 따로 state에 저장해 연결을 끊도록 해놨다.
useEffect(() => {
// 소켓 한 번 연결 됐으면 연결 끊기
return () => {
if (socket) {
socket.disconnect();
}
};
}, [socket, userName]);
서버로부터 온라인 유저를 받으면 여러 유저명이 중복된 배열로 들어온다.
배열에서 중복된 유저명을 지우기 위해 filter와 indexOf메소드로 새로운 배열을 온라인 리스트에 저장했다.
useEffect(() => {
// 서버에서 온라인 리스트 배열로 받음
socket?.on('onlineList', (data) => {
// 배열에서 중복요소 제거해서 새로운 배열 생성
const userArray = data.filter((ele, i) => {
return data.indexOf(ele) === i;
});
// 새로운 배열 온라인리스트 state에 저장
setOnlineList(userArray);
});
return () => {
socket?.off('onlineList');
};
}, [socket, onlineList]);
그리고 온라인 리스트를 보여주는 컴포넌트에 props로 보내서 렌더링 할 수 있게 한다.
<LiveUsers onlineList={onlineList} />
온라인리스트 props가 바뀔때마다 해당 컴포넌트만 리렌더링 되도록 useEffect의 뎁스에 onlineList 배열을 넣어줬다.
const LiveUsers = ({ onlineList }) => {
useEffect(() => {}, [onlineList]);
return (
<>
<Container>
접속중인 유저
{onlineList.map((user, i) => (
<div key={i}>{user}</div>
))}
</Container>
</>
);
};
유저가 접속종료하면 서버에서 클라이언트의 소켓id와 맞는 객체를 지워서 다시 돌려준다.
// 연결 종료 시 온라인 유저 객체 삭제
socket.on('disconnect', () => {
delete onlineMap[socket.nsp.name][socket.id];
newNamespace.emit('onlineList', Object.values(onlineMap[socket.nsp.name]));
});
성공! 새로운 유저가 접속하거나 종료할 때마다 유저리스트 컴포넌트가 새로 리렌더링 된다.