참고
- chat-ui-kit-react로 채팅창 UI 만들기
- 로그인 + 채팅방 UI 만들기
- 채팅방 변경하기
- 채팅방 들어오고 나갈 때 표시하기
- Socket.IO로 채팅하기
- Socket.IO Room으로 채팅방 관리하기
- Socket.IO namespace로 채팅방 관리하기
- Socket.IO로 중복 로그인 제한하기
- Socket.IO Middleware로 중복 로그인 방지하기
Socket.IO의 room 기능을 이용해서, 선택한 채팅방에서만 메시지가 전달되도록 구현해 보자.
위의 구현은 선택한 채팅방에 대해서만 메시지를 받는다.
구현
Socket 서버에는 enter와 leave 이벤트를 등록한다.
join 메서드로 해당 socket에 방 번호를 배정하고, leave 메서드로 방을 떠날 수 있다.
(disconnect가 발생하면 자동으로 leave 된다.)
socket.on("enter", (roomID) => {
socket.join(roomID);
console.log("enter", socket.rooms);
});
socket.on("leave", (roomID) => {
socket.leave(roomID);
console.log("leave", socket.rooms);
});
sendMessage는 in을 이용해서 해당되는 room에 있는 클라이언트만 메시지를 보낸다.
socket.on("sendMessage", (data) => {
let { message, roomID } = data;
console.log(socket.rooms);
socket.broadcast.in(roomID).emit("respondMessage", { message, roomID });
});
socket.rooms로 클라이언트가 어느 방에 속해있는지 알 수 있다.
Set(2) { 'k0i0ucPmzdMS_c3mAAAB', 0 }
Set(2) { 'Kn-mD877CVeLMl_AAAAD', 0 }
Set(2) { 'OAjA-NT21XzZZWEvAAAF', 1 }
이제 클라이언트(리액트) 코드에 room과 관련된 코드를 추가하자.
먼저 init은 roomID에 대한 정보를 추가로 보내면 된다.
const init = () => {
socketIO.connect();
...
socketIO.emit("enter", activeID);
socketIO.emit("sendMessage", { message: enterSeparator, roomID: activeID });
};
그리고 응답을 받을 때, 다시 방 번호를 확인해서 메시지를 추가한다.
const respondMessageCallback = (data) => {
let { message, roomID } = data;
let temp = [...totalMessages];
temp[roomID].push(message);
setMessages(temp);
}
메시지를 보낼 때도, 현재 방 번호를 넘겨준다.
const handleSend = (input) => {
...
socketIO.emit("sendMessage", { message: newMessage, roomID: activeID });
};
방을 변경할 때는 separator(leave) → room(leave) → room(enter) → separator(enter) 순서대로 진행하면 된다.
const changeRoom = (index) => {
if(activeID === index) return;
...
socketIO.emit("sendMessage", { message: leftSeparator, roomID: activeID });
socketIO.emit("leave", activeID);
socketIO.emit("enter", index);
socketIO.emit("sendMessage", { message: enterSeparator, roomID: index });
setMessages(temp);
setActiveID(index);
}
전체 코드는 다음과 같다.
socketIOServer.js
const { Server } = require("socket.io");
const io = new Server("3333", {
cors: {
origin: "http://localhost:3000",
},
});
io.sockets.on("connection", (socket) => {
socket.on("enter", (roomID) => {
socket.join(roomID);
console.log("enter", socket.rooms);
});
socket.on("leave", (roomID) => {
socket.leave(roomID);
console.log("leave", socket.rooms);
});
socket.on("sendMessage", (data) => {
let { message, roomID } = data;
console.log(socket.rooms);
socket.broadcast.in(roomID).emit("respondMessage", { message, roomID });
});
socket.on("disconnect", () => {
});
});
ChatUI.js (Client)
import React, { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { io } from "socket.io-client";
import styles from "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
import {
MainContainer,
ChatContainer,
MessageList,
Message,
MessageInput,
Avatar,
Sidebar,
ConversationList,
Conversation,
ConversationHeader,
VoiceCallButton,
VideoCallButton,
InfoButton,
MessageSeparator,
} from "@chatscope/chat-ui-kit-react";
let socketIO = io("http://localhost:3333", { autoConnect: false });
const AVATAR_IMAGE =
"https://img1.daumcdn.net/thumb/C428x428/?scode=mtistory2&fname=https%3A%2F%2Ftistory3.daumcdn.net%2Ftistory%2F4431109%2Fattach%2F3af65be1d8b64ece859b8f6d07fafadc";
const AVATAR_MAP = {
Lilly: "https://chatscope.io/storybook/react/assets/lilly-aj6lnGPk.svg",
Joe: "https://chatscope.io/storybook/react/assets/joe-v8Vy3KOS.svg",
Emily: "https://chatscope.io/storybook/react/assets/emily-xzL8sDL2.svg",
Akane: "https://chatscope.io/storybook/react/assets/akane-MXhWvx63.svg",
Eliot: "https://chatscope.io/storybook/react/assets/eliot-JNkqSAth.svg",
Zoe: "https://chatscope.io/storybook/react/assets/zoe-E7ZdmXF0.svg",
};
const defaultMessage = [];
const defaultMessagePatrik = [];
const totalMessages = [defaultMessage, defaultMessagePatrik];
const defaultConversation = [
{
info: "",
lastSenderName: "bloodstrawberry",
name: "bloodstrawberry",
src: AVATAR_IMAGE,
status: "available",
},
{
info: "",
lastSenderName: "Patrik",
name: "Patrik",
src: "https://chatscope.io/storybook/react/assets/patrik-yC7svbAR.svg",
status: "invisible",
},
];
const ChatUI = () => {
const location = useLocation();
const [loginID, setLoginID] = useState("");
const [activeID, setActiveID] = useState(0);
const [messages, setMessages] = useState(totalMessages);
const getMessageComponent = (totalMessages) => {
let data = totalMessages[activeID];
return data.map((item, index) => {
if(item.type !== "separator") {
item.model.direction = item.avatar.name === loginID ? "outgoing" : "incoming";
}
return item.type === "separator" ? (
<MessageSeparator key={index} content={item.content} />
) : (
<Message key={index} model={item.model}>
{item.avatar && item.model.direction === "incoming" ? (
<Avatar src={item.avatar.src} name={item.avatar.name} />
) : null}
</Message>
);
});
};
const changeRoom = (index) => {
if(activeID === index) return;
let leftSeparator = {
type : "separator",
content : `${loginID} has left the chatroom.`,
}
let enterSeparator = {
type : "separator",
content : `${loginID} has entered the chatroom.`,
}
let temp = [...totalMessages];
temp[activeID].push(leftSeparator);
temp[index].push(enterSeparator);
socketIO.emit("sendMessage", { message: leftSeparator, roomID: activeID });
socketIO.emit("leave", activeID);
socketIO.emit("enter", index);
socketIO.emit("sendMessage", { message: enterSeparator, roomID: index });
setMessages(temp);
setActiveID(index);
}
const getConversationComponent = (data) => {
return data.map((item, index) => {
return (
<Conversation
key={index}
active={index === activeID}
info={item.info}
lastSenderName={item.lastSenderName}
name={item.name}
onClick={() => changeRoom(index)}
>
<Avatar name={item.name} src={item.src} status={item.status} />
</Conversation>
);
});
};
const handleSend = (input) => {
let newMessage = {
model: {
message: input,
// direction: "outgoing",
},
avatar: {
src: AVATAR_MAP[loginID],
name: loginID,
},
};
let temp = [...totalMessages];
temp[activeID].push(newMessage);
setMessages(temp);
socketIO.emit("sendMessage", { message: newMessage, roomID: activeID });
};
const init = () => {
socketIO.connect();
setLoginID(location.state.loginID);
let enterSeparator = {
type : "separator",
content : `${location.state.loginID} has entered the chatroom.`,
}
let temp = [...totalMessages];
temp[activeID].push(enterSeparator);
setMessages(temp);
socketIO.emit("enter", activeID);
socketIO.emit("sendMessage", { message: enterSeparator, roomID: activeID });
};
const respondMessageCallback = (data) => {
let { message, roomID } = data;
let temp = [...totalMessages];
temp[roomID].push(message);
setMessages(temp);
}
useEffect(() => {
if (!socketIO) return;
socketIO.on("respondMessage", respondMessageCallback);
return () => {
socketIO.off("respondMessage", respondMessageCallback);
};
}, []);
useEffect(init, []);
return (
<div>
<Conversation
info="I'm fine, thank you, and you?"
lastSenderName={loginID}
name={loginID}
>
<Avatar name={loginID} src={AVATAR_MAP[loginID]} status="available" />
</Conversation>
<MessageSeparator style={{ marginTop: 5, marginBottom: 5 }} />
<MainContainer
responsive
style={{
height: "300px",
}}
>
<Sidebar position="left">
<ConversationList>
{getConversationComponent(defaultConversation)}
</ConversationList>
</Sidebar>
<ChatContainer>
<ConversationHeader>
<ConversationHeader.Back />
<Avatar
name={defaultConversation[activeID].name}
src={defaultConversation[activeID].src}
/>
{activeID === 0 ? (
<ConversationHeader.Content
info="Active 10 mins ago"
userName="bloodstrawberry"
/>
) : (
<ConversationHeader.Content
info="Active 7 hours ago"
userName="Patrik"
/>
)}
<ConversationHeader.Actions>
<VoiceCallButton />
<VideoCallButton />
<InfoButton />
</ConversationHeader.Actions>
</ConversationHeader>
<MessageList>
{getMessageComponent(messages)}
</MessageList>
<MessageInput placeholder="Type message here" onSend={handleSend} />
</ChatContainer>
</MainContainer>
</div>
);
};
export default ChatUI;
'개발 > React' 카테고리의 다른 글
리액트, Node JS - Socket.IO로 중복 로그인 제한하기 (Limiting Connections with the Same ID) (0) | 2024.04.03 |
---|---|
리액트, Node JS - Socket.IO namespace로 채팅방 관리하기 (Managing Chat Room using namespace) (0) | 2024.04.01 |
리액트, Node JS - Socket.IO로 채팅하기 (Chatting with Socket.IO) (0) | 2024.03.31 |
리액트 - 채팅방 들어오고 나갈 때 표시하기 (Message Separator) (0) | 2024.03.31 |
리액트 - 채팅방 변경하기 (Change Chat Room) (0) | 2024.03.29 |
댓글