stompjs 2 → 5버전에 맞춰 리액트 코드 작성하기
문제 상황
백엔드 개발자 분이 Java프로젝트 폴더 안에 직접 뷰 부분 코드를 넣어서 했을 때는 채팅테스트가 잘 되었다. 그런데 내가 그 코드를 보고 옮겨적었더니 채팅 테스트가 잘 안되었다. 참고했다는 블로그 글을 앞뒤로 열심히 읽어 봤지만 백엔드 기반 설명이라서 어떤 상황인지 잘 이해가 안됐다.
원인
블로그 글을 자세히 보니 예시 코드가 stompjs 2버전으로 되어 있었다. 그리고 내가 npm으로 설치한 버전은 6번대였다.
2버전으로 설치하면 될까 했지만 에러가 나서 다시 돌렸다.
(참고했던 블로그 코드)
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
const chats = document.querySelector('.chats');
const messageContent = document.querySelector('#message');
const btnSend = document.querySelector('.btn-send');
const chatRoomId = [[${chatRoomId}]];
const nickname = [[${nickname}]];
const sockJS = new SockJS("/stomp/chat");
const stomp = Stomp.over(sockJS);
stomp.heartbeat.outgoing = 0; //Rabbit에선 heartbeat 안먹힌다고 함
stomp.heartbeat.incoming = 0; //Rabbit에선 heartbeat 안먹힌다고 함
function onError(e) {
console.log("STOMP ERROR", e);
}
function onDebug(m) {
console.log("STOMP DEBUG", m);
}
stomp.debug = onDebug;
stomp.connect('guest', 'guest', function (frame) {
console.log('STOMP Connected');
/* subscribe 설정에 따라 rabbit의 Exchange, Queue가 상당히 많이 바뀜 */
stomp.subscribe(``, function (content) {
const payload = JSON.parse(content.body);
let className = payload.nickname == nickname? 'mine' : 'yours';
const html = `<div class="${className}">
<div class="nickname">${payload.nickname}</div>
<div class="message">${payload.message}</div>
</div>`
chats.insertAdjacentHTML('beforeend', html);
//밑의 인자는 Queue 생성 시 주는 옵션
//auto-delete : Consumer가 없으면 스스로 삭제되는 Queue
//durable : 서버와 연결이 끊겨도 메세지를 저장하고 있음
//exclusive : 동일한 이름의 Queue 생길 수 있음
},{'auto-delete':true, 'durable':false, 'exclusive':false});
//입장 메세지 전송
stomp.send(`/pub/chat.enter.${chatRoomId}`, {}, JSON.stringify({
memberId: 1,
nickname: nickname
}));
}, onError, '/');
//메세지 전송 버튼 click
btnSend.addEventListener('click', (e) => {
e.preventDefault();
const message = messageContent.value;
messageContent.value = '';
stomp.send(`/pub/chat.message.${chatRoomId}`, {}, JSON.stringify({
message: message,
memberId: 1,
nickname: nickname
}));
});
해결 방법
여러 블로그 글을 봐도 잘 모르겠어서 공식 문서를 천천히 읽어보기로 했다. 결론은 공식 문서에서 굉장히 필요한 부분만 잘 설명해 주고 있었고 실제로 적용하는 데도 어렵지 않았다.
큰 차이점
1. sockJS 연결 부분
// ver 2
const sockJS = new SockJS("/stomp/chat");
// ver 5+
stompClient.webSocketFactory = function () {
return new SockJS("http://채팅서버:8080/stomp/chat")
}
2. 메세지 전송 send ⇒ publish
// ver 2
stomp.send(`/pub/chat.message.${chatRoomId}`, {}, JSON.stringify({
message: message,
memberId: 1,
nickname: nickname
}))
// ver 5+
stompClient.publish({
destination: `/pub/chat.message.${roomId}`,
body: JSON.stringify({
message: input.value,
userId,
profileImage: "{유저 프로필 이미지 주소}",
senderName: "{유저 닉네임}",
})
})
3. stompclient 생성
// ver 2
const stomp = Stomp.over(sockJS)
// ver 5+
const stompClient = new StompJs.Client({
brokerURL: "ws://채팅서버:61613/stomp/chat",
...
})
수정한 전체코드(react)
import { useEffect, useRef, useState } from "react"
import { useParams } from "react-router-dom"
import SockJS from "sockjs-client"
import * as StompJs from "@stomp/stompjs"
import { createChatBubble } from "../utils/createChatBubble" // 채팅 말풍선 생성
import { getUserToken } from "../utils/getUserToken" // 유저 토큰 가져오기
let stompClient
let subscription
function ChatRoom() {
// stomp & user
const { roomId } = useParams()
const textInputRef = useRef()
const messageListRef = useRef()
const userId = "유저아이디"
const [token, setToken] = useState("유저토큰")
// 채팅방 구독
function subscribe() {
if (stompClient != null) {
subscription = stompClient.subscribe(
`/exchange/chat.exchange/room.${roomId}`,
(content) => {
const payload = JSON.parse(content.body)
console.log(payload)
const bubble = createChatBubble({ payload, userId })
messageListRef.current.appendChild(bubble)
messageListRef.current.scrollTop = messageListRef.current.scrollHeight
},
)
}
}
// stompClient 생성
useEffect(() => {
if (token !== "") {
stompClient = new StompJs.Client({
brokerURL: "ws://채팅서버:61613/stomp/chat",
connectHeaders: {
login: "user",
passcode: "password",
Authorization: token,
},
debug: function (str) {
console.log(str)
},
onConnect: () => {
// 연결 됐을 때 구독 시작
subscribe()
},
onStompError: function (frame) {
console.log("Broker reported error: " + frame.headers["message"])
console.log("Additional details: " + frame.body)
},
onDisconnect: () => {
disConnect()
},
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
})
stompClient.webSocketFactory = function () {
return new SockJS("http://채팅서버:8080/stomp/chat")
}
stompClient.activate()
textInputRef.current.focus()
}
// 컴포넌트 언마운트 될 때 웹소켓 연결을 끊기
return () => {
disConnect()
}
}, [token])
// 채팅 연결 끊어질 때
async function disConnect() {
if (stompClient && subscription) {
stompClient.deactivate()
subscription.unsubscribe()
console.log("채팅 연결 끊어짐")
}
}
// 메세지 전송
async function handleSubmit(event) {
event.preventDefault()
const input = event.target.querySelector("input")
if (input.value === "") return
const message = {
message: input.value,
userId,
profileImage: "{유저 프로필 이미지 주소}",
senderName: "{유저 닉네임}",
}
const token = await getUserToken()
stompClient.publish({
destination: `/pub/chat.message.${roomId}`,
body: JSON.stringify(message),
headers: {
Authorization: token,
},
})
}
return (
<div>
{/* 채팅 내용 나타나는 부분 */}
<ul ref={messageListRef}></ul>
{/* 채팅 전송 */}
<form onSubmit={handleSubmit}>
<div>
<input ref={textInputRef} type="text" />
<button type="submit">보내기</button>
</div>
</form>
</div>
)
}
export default ChatRoom
참고 레퍼런스
https://dev-gorany.tistory.com/m/325?category=901854#js
https://stomp-js.github.io/guide/stompjs/using-stompjs-v5.html
Leave a comment