2024.10.26 - [Programming/Spring] - [Spring Boot] WebSocket, Kafka 채팅 서버 구현 (1)
2024.11.04 - [Programming/Spring] - [Spring Boot] WebSocket, Kafka 채팅 서버 구현 (2)
채팅 서버를 만들고 이를 활용하기 위해서 낸 아이디어가 크롬 확장자이다.
단순하게 각 URL 별로 확장자를 통해 익명 채팅할 수 있다면 어떤 웹사이트에서도 채팅 기능이 구현되어있는 것처럼 `확장자` 스럽게 사용할 수 있을 것 같았다.
그래서 이번 포스팅에서는 크롬 확장자를 구성해 보겠다.
https://support.google.com/chrome/a/answer/2714278?hl=ko
구글의 가이드를 통해 샘플을 가져올 수 있었고, 여기에는 websocket 샘플도 존재했다.
내가 구상한 아이디어는 확장자를 누르면 popup 화면이 뜨고 채팅을 할 수 있는 구조다.
{
...
"action": {
"default_popup": "popup.html",
"default_icon": "icons/socket-inactive.png"
},
...
}
manifest.json 파일에 popup 을 추가하자.
let stompClient = null;
const base62Encoder = new Base62Encoder();
// 랜덤 닉네임 생성 함수
function generateNickname() {
const adjectives = [
'행복한', '즐거운', '신나는', '귀여운', '멋진',
'똑똑한', '현명한', '친절한', '활기찬', '열정적인',
'다정한', '유쾌한', '상냥한', '재미있는', '사랑스러운'
];
const nouns = [
'곰돌이', '토끼', '강아지', '고양이', '판다',
'코알라', '펭귄', '다람쥐', '여우', '사자',
'호랑이', '기린', '코끼리', '햄스터', '돌고래'
];
const randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)];
const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
return `${randomAdjective}${randomNoun}`;
}
// 닉네임을 저장할 변수
const nickname = generateNickname();
console.log('Generated nickname:', nickname);
function connect() {
console.log('Connecting to WebSocket...');
const socket = new WebSocket('ws://localhost:8080/ws/chat');
stompClient = Stomp.over(socket);
stompClient.connect({}, async function (frame) {
console.log('Connected: ' + frame);
try {
const currentTab = await getCurrentTab();
const topic = base62Encoder.sanitizeUrl(currentTab.url);
console.log('Subscribing to topic:', topic);
stompClient.subscribe(`/sub/${topic}`, function (message) {
displayMessage(JSON.parse(message.body));
});
} catch (error) {
console.error('Error subscribing to topic:', error);
}
});
}
function displayMessage(message) {
const messagesDiv = document.getElementById('messages');
const messageElement = document.createElement('div');
if (message.url) {
messageElement.innerHTML = `${message.sender}: ${message.message} <br>`;
} else {
messageElement.textContent = `${message.sender}: ${message.message}`;
}
messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
async function getCurrentTab() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
return tab;
}
async function sendMessage(content) {
if (stompClient && content) {
try {
const currentTab = await getCurrentTab();
const chatMessage = {
message: content,
sender: nickname, // 생성된 닉네임 사용
url: currentTab.url
};
stompClient.send(`/pub/chat.send`, {}, JSON.stringify(chatMessage));
console.log('Message sent:', chatMessage);
} catch (error) {
console.error('Error sending message:', error);
}
}
}
document.getElementById('message-form').addEventListener('submit', function (e) {
e.preventDefault();
const messageInput = document.getElementById('message-input');
const content = messageInput.value.trim();
if (content) {
sendMessage(content);
messageInput.value = '';
}
});
// 페이지 로드시 연결
connect();
popup.js 에서는 연결, 구독, 메세지 발행 등 모든 로직을 수행한다.
일단 랜덤한 닉네임을 생성하고, Base62 Encoder 를 통해서 현재 url 을 인코딩한다. 이 값이 Topic 값이며, 서버에서도 메세지에 포함된 URL 을 통해 동일한 Topic 값을 인코딩해서 사용한다.
실제 구동 환경은 위와 같다. 두 개의 서로 다른 탭에서 크롬 확장자를 통해서 채팅을 보낼 수 있다.
이러한 데이터는 Kafka 내부에서 확인할 수 있고, 해당 토픽은 네이버의 주소를 Base62 인코딩한 값을 가지고 있다.
실제 배포를 위해서는 서버의 상시 배포가 필요하다. 이외에도 현재는 popup 을 사용하다보니 웹소켓 연결이 끊어지는 등의 문제가 있다. 실제 배포보다는 서버쪽에서 더 고민해보고자 한다.
'Programming > Spring' 카테고리의 다른 글
[Spring Boot][WebSocket] 실시간 시세 데이터 처리 및 관리하기 (3) | 2024.11.22 |
---|---|
[Spring Boot] 동시성 제어 with 비관적 락, Redis 그리고 @Transactional 사용 시 동시성 문제점 (5) | 2024.11.19 |
[Spring Boot] WebSocket, Kafka 채팅 서버 구현 (2) (8) | 2024.11.04 |
[Spring] @ModelAttribute 사용 방법과 원리 by 생성자 개수, Setter (0) | 2024.10.31 |
[Spring Boot] WebSocket, Kafka 채팅 서버 구현 (1) (1) | 2024.10.26 |