diff --git a/package-lock.json b/package-lock.json index 686b382..37fdfab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@capacitor/app": "^8.0.0", "@capacitor/core": "^8.3.4", "@quasar/extras": "^1.16.12", + "@stomp/stompjs": "^7.3.0", "pinia": "^2.3.0", "quasar": "^2.17.4", "vue": "^3.5.13", @@ -1509,6 +1510,12 @@ "win32" ] }, + "node_modules/@stomp/stompjs": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-7.3.0.tgz", + "integrity": "sha512-nKMLoFfJhrQAqkvvKd1vLq/cVBGCMwPRCD0LqW7UT1fecRx9C3GoKEIR2CYwVuErGeZu8w0kFkl2rlhPlqHVgQ==", + "license": "Apache-2.0" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", diff --git a/package.json b/package.json index 205e4b5..9c8bb57 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@capacitor/app": "^8.0.0", "@capacitor/core": "^8.3.4", "@quasar/extras": "^1.16.12", + "@stomp/stompjs": "^7.3.0", "pinia": "^2.3.0", "quasar": "^2.17.4", "vue": "^3.5.13", @@ -24,8 +25,8 @@ "@capacitor/android": "^8.3.4", "@capacitor/cli": "^8.3.4", "@capacitor/ios": "^8.3.4", - "@types/navermaps": "^3.7.5", "@quasar/vite-plugin": "^1.9.0", + "@types/navermaps": "^3.7.5", "@vitejs/plugin-vue": "^5.2.1", "sass-embedded": "^1.83.0", "typescript": "^5.7.2", diff --git a/src/api/chat.ts b/src/api/chat.ts new file mode 100644 index 0000000..feb3f32 --- /dev/null +++ b/src/api/chat.ts @@ -0,0 +1,14 @@ +import { http } from './http' + +export interface ChatMessage { + id: number + spotId: number + senderId: number + senderNickname: string + content: string + createdAt: string +} + +export const chatApi = { + history: (spotId: number) => http.get(`/api/chat/spots/${spotId}/messages`), +} diff --git a/src/components/ChatDialog.vue b/src/components/ChatDialog.vue new file mode 100644 index 0000000..7b94728 --- /dev/null +++ b/src/components/ChatDialog.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/src/lib/chatSocket.ts b/src/lib/chatSocket.ts new file mode 100644 index 0000000..8396c65 --- /dev/null +++ b/src/lib/chatSocket.ts @@ -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, + } +} diff --git a/src/pages/HomePage.vue b/src/pages/HomePage.vue index bbaf36b..5358b92 100644 --- a/src/pages/HomePage.vue +++ b/src/pages/HomePage.vue @@ -11,16 +11,39 @@ 백엔드에 연결하지 못했습니다. (서버 실행 여부를 확인하세요) - - +
+
+ +
+
+ +
+
+ + + @@ -77,11 +100,14 @@ import { ApiError } from '@/api/http' import { useAuthStore } from '@/stores/auth' import { useDogsStore } from '@/stores/dogs' import NaverMap from '@/components/NaverMap.vue' +import ChatDialog from '@/components/ChatDialog.vue' const $q = useQuasar() const auth = useAuthStore() const dogs = useDogsStore() +const chatOpen = ref(false) + // 체크인/매칭 주체: 로그인 회원 + 대표 강아지 const currentUserId = () => auth.member?.id ?? 0