사용 기술
프론트엔드는 Next.js / 백엔드는 스프링을 이용하여 채팅 기능을 구현해볼 것이다.
채팅 기능에만 집중해서 만들것이며 기본적인 CORS같은건 굳이 설명하지 않는다.
이 글에는 프론트엔드 사이드 내용만 적고 백엔드 쪽은 이 글로 분리하려고 한다.
sockjs-client는 npm에서 관리가 안된지 오래 돼서 타입스크립트 사용 시 타입도 따로 추가해야한다.
따라서 types/sockjs-client 라이브러리를 추가로 넣어주도록 한다.
- Next.js 14 / App router
- TypeScript
- @types/sockjs-client, sockjs-client, stompjs
채팅 기능 구현
원래는 STOMP Client를 brokerUrl을 설정해서 만들어보려다가 포기하고 SockJS를 구현체로 사용했다.
이건 백엔드 도메인을 http로 받는듯.brokerUrl은 ws://localhost:8080/ws 의 형식이다.대략적인 순서는 아래와 같다.
( + 수정 - 백엔드 쪽에서 withSockJs 설정을 제거하고 프론트 쪽에서 Stomp Client 로 만들면 된다.)
- useEffect로 마운트 시 STOMP Client를 만들고 소켓 연결
- Client 생성 시 설정해둔 onConnect 콜백이 동작하여 설정해둔 topic 구독
- 구독 시 설정해둔 onMessageReceived 콜백이 동작하여 구독중인 topic에서 메시지를 수신
- 메시지 종류에 따라 switch 문으로 분기하여 원하는 동작 실행
본인은 메시지 출력을 위해 messages 배열에 push 하도록 함 - 메시지를 전송하고 싶으면 client.publish 함수를 이용하여 백엔드 엔드포인트로 메시지 전송
백엔드 코드에서 MessageMapping("url")에 해당.
prefix(본인은 "/app"으로 설정) + url 형식으로 엔드포인트를 설정해주면 됨
참고로 한 페이지에 여러 채팅방을 연결해야할 경우 소켓 연결은 부모 컴포넌트에서 한 번만 해도 된다.
자식 컴포넌트에 STOMP Client 를 전달하여 자식 컴포넌트에서 topic을 구독하는 식으로 구현한다.
"use client";
import { Client } from "@stomp/stompjs";
import { useEffect, useState } from "react";
import SockJS from "sockjs-client";
const BASE_URL = "http://localhost:8080";
type Message = { content: string; sender: string; type: string };
type SocketUnitProps = {
id: number;
};
export default function OldSocketUnit({ id }: SocketUnitProps) {
const [client, setClient] = useState<Client | undefined>(undefined);
const [connected, setConnected] = useState(false);
const [messages, setMessages] = useState<string[]>([]);
const onMessageReceived = (res: any) => {
const data = JSON.parse(res.body);
const { type, content, sender } = data;
switch (type) {
case "JOIN":
messages.push(`${sender}님 입장`);
break;
case "CHAT":
messages.push(content);
break;
case "LEAVE":
messages.push(`${sender} 퇴장`);
break;
}
setMessages([...messages]);
};
const sendMessage = () => {
const chatMEssage = {
sender: "mi",
content: "내용",
type: "CHAT",
};
if (client) {
console.log(client);
client.publish({
destination: `/app/chat.sendMessage/${id}`, // 백엔드의 웹소켓 엔드포인트
headers: {},
body: JSON.stringify(chatMEssage),
});
}
};
const disconnect = () => {
client?.deactivate();
};
useEffect(() => {
const client = new Client({
webSocketFactory: () => {
return new SockJS("http://localhost:8080/ws");
},
onConnect: () => {
setConnected(true);
if (client) {
client.subscribe(`/topic/${id}`, onMessageReceived); // url의 id = chatroomId
client.publish({
destination: `/app/chat.addUser/${id}`, // 백엔드의 웹소켓 엔드포인트
headers: {},
body: JSON.stringify({ sender: "name", type: "JOIN" }),
});
}
},
onStompError: () => {
console.log("에러 발생");
},
debug: function (str) {
console.log(str);
},
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
});
setClient(client);
try {
console.log("[웹소켓] 연결 시도");
client.activate();
} catch (e) {
console.log(e);
}
}, []);
return (
<div>
<div>connection: {connected ? "true" : "false"}</div>
{messages.map((message, i) => (
<div key={i}>{message}</div>
))}
<div style={{ display: "flex", flexDirection: "column" }}>
<button
style={{ border: "1px solid green" }}
onClick={sendMessage}
type="button"
>
메시지 보내기
</button>
<button style={{ border: "1px solid red" }} onClick={disconnect}>
채팅방 입장
</button>
</div>
</div>
);
}
'API 만들어 보기 > 실시간 채팅' 카테고리의 다른 글
실시간 채팅 3: STOMP로 실시간 채팅 구현 시 Nginx 추가 설정 (0) | 2024.05.11 |
---|---|
실시간 채팅 1: STOMP로 실시간 채팅 구현 - SpringBoot 코드 (0) | 2024.04.09 |