feat: 실시간 채팅 UI (STOMP 연동)

- ChatDialog: 히스토리 로드 + 실시간 송수신, 내 메시지 우측 정렬, 연결상태 표시
- lib/chatSocket: STOMP 클라이언트(브로커 ws, Bearer 인증, 자동 재연결)
- api/chat 히스토리, HomePage 스팟 채팅 버튼
- @stomp/stompjs 의존성 추가

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sb
2026-07-11 09:18:23 +09:00
parent d2e5495619
commit 9e4d1aa2af
6 changed files with 251 additions and 11 deletions
+45
View File
@@ -0,0 +1,45 @@
import { Client, type IMessage } from '@stomp/stompjs'
import { tokenStore } from '@/lib/token'
import type { ChatMessage } from '@/api/chat'
function wsUrl(): string {
const base = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:8080'
// http(s) -> ws(s)
return base.replace(/^http/, 'ws') + '/ws'
}
/**
* 스팟 채팅용 STOMP 클라이언트.
* connect 후 스팟 토픽을 구독하고, send 로 메시지를 전송한다.
*/
export function createSpotChat(
spotId: number,
onMessage: (m: ChatMessage) => void,
onStatus?: (connected: boolean) => void,
) {
const client = new Client({
brokerURL: wsUrl(),
connectHeaders: { Authorization: `Bearer ${tokenStore.get() ?? ''}` },
reconnectDelay: 3000,
onConnect: () => {
onStatus?.(true)
client.subscribe(`/topic/spots/${spotId}`, (msg: IMessage) => {
onMessage(JSON.parse(msg.body) as ChatMessage)
})
},
onDisconnect: () => onStatus?.(false),
onWebSocketClose: () => onStatus?.(false),
})
return {
activate: () => client.activate(),
deactivate: () => client.deactivate(),
send: (content: string) => {
client.publish({
destination: `/app/spots/${spotId}/chat`,
body: JSON.stringify({ content }),
})
},
isConnected: () => client.connected,
}
}