@@ -189,185 +189,197 @@ <h2>채팅방 목록</h2>
189189 const socket = new SockJS ( '/ws?token=' + encodeURIComponent ( token ) ) ;
190190 stompClient = Stomp . over ( socket ) ;
191191
192- stompClient . connect ( { } , ( ) => {
193- const messagesEl = document . getElementById ( 'messages' ) ;
194- const roomListEl = document . getElementById ( 'roomList' ) ;
195- const receiptId = 'sub-1' ;
192+ // ─── Heartbeat 5분(300,000ms) 설정 ───
193+ stompClient . heartbeat . outgoing = 300000 ; // 클라이언트→서버 heartbeat 전송 간격
194+ stompClient . heartbeat . incoming = 300000 ; // 서버→클라이언트 heartbeat 수신 허용 최대 간격
195+ const hbHeader = { 'heart-beat' : '300000,300000' } ;
196+ // ────────────────────────────────────
196197
197- stompClient . subscribe ( '/user/queue/notification' , msg => {
198- console . log ( msg ) ;
199- } ) ;
198+ stompClient . connect (
199+ hbHeader ,
200+ ( ) => {
201+ const messagesEl = document . getElementById ( 'messages' ) ;
202+ const roomListEl = document . getElementById ( 'roomList' ) ;
203+ const receiptId = 'sub-1' ;
200204
201- stompClient . subscribe ( '/user/queue/notifications' , msg => {
202- console . log ( msg ) ;
203- } ) ;
204-
205- // 채팅방 목록 초기 구독 및 오름차순 정렬
206- stompClient . subscribe ( '/user/queue/chatrooms' , msg => {
207- let rooms ;
208- try { rooms = JSON . parse ( msg . body ) ; } catch { return ; }
209- // roomId 기준 오름차순 정렬
210- rooms . sort ( ( a , b ) => Number ( a . roomId ) - Number ( b . roomId ) ) ;
211- roomListEl . innerHTML = '' ;
212- rooms . forEach ( r => {
213- const li = document . createElement ( 'li' ) ;
214- li . textContent = `${ r . title } (${ r . roomId } ) — ${ r . headCount } 명` ;
215- li . dataset . roomId = r . roomId ;
216- roomListEl . appendChild ( li ) ;
205+ stompClient . subscribe ( '/user/queue/notification' , msg => {
206+ console . log ( msg ) ;
217207 } ) ;
218- } , { receipt : receiptId } ) ;
219-
220- // onreceipt 처리: 입장 메시지 전송
221- stompClient . onreceipt = frame => {
222- if ( frame . headers [ 'receipt-id' ] === receiptId ) {
223- stompClient . send ( '/chat/enter' , { } , '경오' ) ;
224- }
225- } ;
226208
227- // 채팅 메시지 구독
228- stompClient . subscribe ( '/user/queue/chat' , msg => {
229- messagesEl . innerHTML = '' ;
230- let chats ;
231- try { chats = JSON . parse ( msg . body ) ; } catch { return ; }
232- chats . forEach ( c => {
233- const time = new Date ( c . time )
234- . toLocaleTimeString ( [ ] , { hour : '2-digit' , minute : '2-digit' } ) ;
235- const li = document . createElement ( 'li' ) ;
236- li . textContent = `[${ time } ] ${ c . name } (${ c . tier } ): ${ c . message } ` ;
237- messagesEl . appendChild ( li ) ;
209+ stompClient . subscribe ( '/user/queue/notifications' , msg => {
210+ console . log ( msg ) ;
238211 } ) ;
239- messagesEl . scrollTop = messagesEl . scrollHeight ;
240- } ) ;
241-
242- // 채팅방 목록 변경 브로드캐스트 구독 (업데이트 시 오름차순 정렬 유지)
243- stompClient . subscribe ( '/topic/chatrooms' , msg => {
244- let room ;
245- try { room = JSON . parse ( msg . body ) ; } catch { return ; }
246212
247- if ( room . eventType === 'CREATE' ) {
248- // 신규 방 append (중복 방지)
249- if ( ! roomListEl . querySelector ( `li[data-room-id="${ room . roomId } "]` ) ) {
250- const li = document . createElement ( 'li' ) ;
251- li . textContent = `${ room . title } (${ room . roomId } ) — ${ room . headCount } 명` ;
252- li . dataset . roomId = room . roomId ;
253- roomListEl . appendChild ( li ) ;
254- }
255- // 오름차순 정렬
256- const reordered = Array . from ( roomListEl . children ) . sort ( ( a , b ) =>
257- Number ( a . dataset . roomId ) - Number ( b . dataset . roomId )
258- ) ;
213+ // 채팅방 목록 초기 구독 및 오름차순 정렬
214+ stompClient . subscribe ( '/user/queue/chatrooms' , msg => {
215+ let rooms ;
216+ try { rooms = JSON . parse ( msg . body ) ; } catch { return ; }
217+ // roomId 기준 오름차순 정렬
218+ rooms . sort ( ( a , b ) => Number ( a . roomId ) - Number ( b . roomId ) ) ;
259219 roomListEl . innerHTML = '' ;
260- reordered . forEach ( li => roomListEl . appendChild ( li ) ) ;
261- } else if ( room . eventType === 'UPDATE' ) {
262- // 방 정보 변경, 없으면 추가
263- let li = roomListEl . querySelector ( `li[data-room-id="${ room . roomId } "]` ) ;
264- if ( li ) {
265- li . textContent = `${ room . title } (${ room . roomId } ) — ${ room . headCount } 명` ;
266- } else {
267- li = document . createElement ( 'li' ) ;
268- li . textContent = `${ room . title } (${ room . roomId } ) — ${ room . headCount } 명` ;
269- li . dataset . roomId = room . roomId ;
220+ rooms . forEach ( r => {
221+ const li = document . createElement ( 'li' ) ;
222+ li . textContent = `${ r . title } (${ r . roomId } ) — ${ r . headCount } 명` ;
223+ li . dataset . roomId = r . roomId ;
270224 roomListEl . appendChild ( li ) ;
225+ } ) ;
226+ } , { receipt : receiptId } ) ;
227+
228+ // onreceipt 처리: 입장 메시지 전송
229+ stompClient . onreceipt = frame => {
230+ if ( frame . headers [ 'receipt-id' ] === receiptId ) {
231+ stompClient . send ( '/chat/enter' , { } , '경오' ) ;
271232 }
272- // 오름차순 정렬
273- const reordered = Array . from ( roomListEl . children ) . sort ( ( a , b ) =>
274- Number ( a . dataset . roomId ) - Number ( b . dataset . roomId )
275- ) ;
276- roomListEl . innerHTML = '' ;
277- reordered . forEach ( li => roomListEl . appendChild ( li ) ) ;
278- } else if ( room . eventType === 'DELETE' ) {
279- // 삭제
280- const li = roomListEl . querySelector ( `li[data-room-id="${ room . roomId } "]` ) ;
281- if ( li ) li . remove ( ) ;
282- } else if ( room . eventType === 'GET' ) {
283- // 전체 교체 (room.rooms는 방 목록 배열)
284- if ( ! Array . isArray ( room . rooms ) ) return ;
285- roomListEl . innerHTML = '' ;
286- room . rooms
287- . sort ( ( a , b ) => Number ( a . roomId ) - Number ( b . roomId ) )
288- . forEach ( r => {
233+ } ;
234+
235+ // 채팅 메시지 구독
236+ stompClient . subscribe ( '/user/queue/chat' , msg => {
237+ messagesEl . innerHTML = '' ;
238+ let chats ;
239+ try { chats = JSON . parse ( msg . body ) ; } catch { return ; }
240+ chats . forEach ( c => {
241+ const time = new Date ( c . time )
242+ . toLocaleTimeString ( [ ] , { hour : '2-digit' , minute : '2-digit' } ) ;
243+ const li = document . createElement ( 'li' ) ;
244+ li . textContent = `[${ time } ] ${ c . name } (${ c . tier } ): ${ c . message } ` ;
245+ messagesEl . appendChild ( li ) ;
246+ } ) ;
247+ messagesEl . scrollTop = messagesEl . scrollHeight ;
248+ } ) ;
249+
250+ // 채팅방 목록 변경 브로드캐스트 구독 (업데이트 시 오름차순 정렬 유지)
251+ stompClient . subscribe ( '/topic/chatrooms' , msg => {
252+ let room ;
253+ try { room = JSON . parse ( msg . body ) ; } catch { return ; }
254+
255+ if ( room . eventType === 'CREATE' ) {
256+ // 신규 방 append (중복 방지)
257+ if ( ! roomListEl . querySelector ( `li[data-room-id="${ room . roomId } "]` ) ) {
289258 const li = document . createElement ( 'li' ) ;
290- li . textContent = `${ r . title } (${ r . roomId } ) — ${ r . headCount } 명` ;
291- li . dataset . roomId = r . roomId ;
259+ li . textContent = `${ room . title } (${ room . roomId } ) — ${ room . headCount } 명` ;
260+ li . dataset . roomId = room . roomId ;
292261 roomListEl . appendChild ( li ) ;
293- } ) ;
294- }
295- } ) ;
262+ }
263+ // 오름차순 정렬
264+ const reordered = Array . from ( roomListEl . children ) . sort ( ( a , b ) =>
265+ Number ( a . dataset . roomId ) - Number ( b . dataset . roomId )
266+ ) ;
267+ roomListEl . innerHTML = '' ;
268+ reordered . forEach ( li => roomListEl . appendChild ( li ) ) ;
269+ } else if ( room . eventType === 'UPDATE' ) {
270+ // 방 정보 변경, 없으면 추가
271+ let li = roomListEl . querySelector ( `li[data-room-id="${ room . roomId } "]` ) ;
272+ if ( li ) {
273+ li . textContent = `${ room . title } (${ room . roomId } ) — ${ room . headCount } 명` ;
274+ } else {
275+ li = document . createElement ( 'li' ) ;
276+ li . textContent = `${ room . title } (${ room . roomId } ) — ${ room . headCount } 명` ;
277+ li . dataset . roomId = room . roomId ;
278+ roomListEl . appendChild ( li ) ;
279+ }
280+ // 오름차순 정렬
281+ const reordered = Array . from ( roomListEl . children ) . sort ( ( a , b ) =>
282+ Number ( a . dataset . roomId ) - Number ( b . dataset . roomId )
283+ ) ;
284+ roomListEl . innerHTML = '' ;
285+ reordered . forEach ( li => roomListEl . appendChild ( li ) ) ;
286+ } else if ( room . eventType === 'DELETE' ) {
287+ // 삭제
288+ const li = roomListEl . querySelector ( `li[data-room-id="${ room . roomId } "]` ) ;
289+ if ( li ) li . remove ( ) ;
290+ } else if ( room . eventType === 'GET' ) {
291+ // 전체 교체 (room.rooms는 방 목록 배열)
292+ if ( ! Array . isArray ( room . rooms ) ) return ;
293+ roomListEl . innerHTML = '' ;
294+ room . rooms
295+ . sort ( ( a , b ) => Number ( a . roomId ) - Number ( b . roomId ) )
296+ . forEach ( r => {
297+ const li = document . createElement ( 'li' ) ;
298+ li . textContent = `${ r . title } (${ r . roomId } ) — ${ r . headCount } 명` ;
299+ li . dataset . roomId = r . roomId ;
300+ roomListEl . appendChild ( li ) ;
301+ } ) ;
302+ }
303+ } ) ;
296304
297- // 채팅방 클릭 핸들러
298- roomListEl . addEventListener ( 'click' , e => {
299- const li = e . target ;
300- const nr = li . dataset . roomId ;
301- if ( ! nr || currentRoomId === nr ) return ;
302- if ( chatSubscription && currentRoomId ) {
303- stompClient . send ( `/chat/room/${ currentRoomId } /left` , { } , currentRoomId ) ;
304- chatSubscription . unsubscribe ( ) ;
305- }
306- currentRoomId = nr ;
307- roomListEl . querySelectorAll ( 'li' ) . forEach ( el => el . classList . remove ( 'active' ) ) ;
308- li . classList . add ( 'active' ) ;
309- messagesEl . innerHTML = '' ;
305+ // 채팅방 클릭 핸들러
306+ roomListEl . addEventListener ( 'click' , e => {
307+ const li = e . target ;
308+ const nr = li . dataset . roomId ;
309+ if ( ! nr || currentRoomId === nr ) return ;
310+ if ( chatSubscription && currentRoomId ) {
311+ stompClient . send ( `/chat/room/${ currentRoomId } /left` , { } , currentRoomId ) ;
312+ chatSubscription . unsubscribe ( ) ;
313+ }
314+ currentRoomId = nr ;
315+ roomListEl . querySelectorAll ( 'li' ) . forEach ( el => el . classList . remove ( 'active' ) ) ;
316+ li . classList . add ( 'active' ) ;
317+ messagesEl . innerHTML = '' ;
310318
311- setTimeout ( ( ) => {
312- chatSubscription = stompClient . subscribe ( `/topic/chat/${ nr } ` , m => {
313- setTimeout ( ( ) => {
314- let p ;
315- try { p = JSON . parse ( m . body ) ; } catch {
316- const lf = document . createElement ( 'li' ) ;
317- lf . textContent = m . body ;
318- messagesEl . appendChild ( lf ) ;
319+ setTimeout ( ( ) => {
320+ chatSubscription = stompClient . subscribe ( `/topic/chat/${ nr } ` , m => {
321+ setTimeout ( ( ) => {
322+ let p ;
323+ try { p = JSON . parse ( m . body ) ; } catch {
324+ const lf = document . createElement ( 'li' ) ;
325+ lf . textContent = m . body ;
326+ messagesEl . appendChild ( lf ) ;
327+ messagesEl . scrollTop = messagesEl . scrollHeight ;
328+ return ;
329+ }
330+ const { time, name, tier, message } = p ;
331+ const ft = new Date ( time )
332+ . toLocaleTimeString ( [ ] , { hour : '2-digit' , minute : '2-digit' } ) ;
333+ const liB = document . createElement ( 'li' ) ;
334+ liB . textContent = `[${ ft } ] ${ name } (${ tier } ): ${ message } ` ;
335+ messagesEl . appendChild ( liB ) ;
319336 messagesEl . scrollTop = messagesEl . scrollHeight ;
320- return ;
321- }
322- const { time, name, tier, message } = p ;
323- const ft = new Date ( time )
324- . toLocaleTimeString ( [ ] , { hour : '2-digit' , minute : '2-digit' } ) ;
325- const liB = document . createElement ( 'li' ) ;
326- liB . textContent = `[${ ft } ] ${ name } (${ tier } ): ${ message } ` ;
327- messagesEl . appendChild ( liB ) ;
328- messagesEl . scrollTop = messagesEl . scrollHeight ;
337+ } , 100 ) ;
338+ } ) ;
339+ setTimeout ( ( ) => {
340+ stompClient . send ( `/chat/room/${ nr } /enter` , { } , nr ) ;
329341 } , 100 ) ;
330- } ) ;
331- setTimeout ( ( ) => {
332- stompClient . send ( `/chat/room/${ nr } /enter` , { } , nr ) ;
333342 } , 100 ) ;
334- } , 100 ) ;
335- } ) ;
343+ } ) ;
336344
337- // 전송 버튼 클릭 시 fetch 호출
338- const sendBtn = document . getElementById ( 'sendBtn' ) ;
339- const msgInput = document . getElementById ( 'msg' ) ;
340- msgInput . addEventListener ( 'keydown' , e => {
341- if ( e . key === 'Enter' ) { e . preventDefault ( ) ; sendBtn . click ( ) ; }
342- } ) ;
343- sendBtn . addEventListener ( 'click' , ( ) => {
344- if ( ! currentRoomId ) return ;
345- const m = msgInput . value . trim ( ) ;
346- if ( ! m ) return ;
347- fetch ( `/api/room/${ currentRoomId } /chat` , {
348- method : 'POST' ,
349- headers : {
350- 'Content-Type' : 'application/json' ,
351- 'Authorization' : 'Bearer ' + token
352- } ,
353- body : JSON . stringify ( { message : m } )
354- } )
355- . then ( res => {
356- if ( ! res . ok ) throw new Error ( '메시지 전송 실패' ) ;
357- return res . json ( ) ;
358- } )
359- . then ( d => {
360- const t = new Date ( d . time )
361- . toLocaleTimeString ( [ ] , { hour : '2-digit' , minute : '2-digit' } ) ;
362- const li = document . createElement ( 'li' ) ;
363- li . textContent = `[${ t } ] ${ d . name } (${ d . tier } ): ${ d . message } ` ;
364- messagesEl . appendChild ( li ) ;
365- messagesEl . scrollTop = messagesEl . scrollHeight ;
345+ // 전송 버튼 클릭 시 fetch 호출
346+ const sendBtn = document . getElementById ( 'sendBtn' ) ;
347+ const msgInput = document . getElementById ( 'msg' ) ;
348+ msgInput . addEventListener ( 'keydown' , e => {
349+ if ( e . key === 'Enter' ) { e . preventDefault ( ) ; sendBtn . click ( ) ; }
350+ } ) ;
351+ sendBtn . addEventListener ( 'click' , ( ) => {
352+ if ( ! currentRoomId ) return ;
353+ const m = msgInput . value . trim ( ) ;
354+ if ( ! m ) return ;
355+ fetch ( `/api/room/${ currentRoomId } /chat` , {
356+ method : 'POST' ,
357+ headers : {
358+ 'Content-Type' : 'application/json' ,
359+ 'Authorization' : 'Bearer ' + token
360+ } ,
361+ body : JSON . stringify ( { message : m } )
366362 } )
367- . catch ( err => console . error ( err ) ) ;
368- msgInput . value = '' ;
369- } ) ;
370- } ) ;
363+ . then ( res => {
364+ if ( ! res . ok ) throw new Error ( '메시지 전송 실패' ) ;
365+ return res . json ( ) ;
366+ } )
367+ . then ( d => {
368+ const t = new Date ( d . time )
369+ . toLocaleTimeString ( [ ] , { hour : '2-digit' , minute : '2-digit' } ) ;
370+ const li = document . createElement ( 'li' ) ;
371+ li . textContent = `[${ t } ] ${ d . name } (${ d . tier } ): ${ d . message } ` ;
372+ messagesEl . appendChild ( li ) ;
373+ messagesEl . scrollTop = messagesEl . scrollHeight ;
374+ } )
375+ . catch ( err => console . error ( err ) ) ;
376+ msgInput . value = '' ;
377+ } ) ;
378+ } ,
379+ error => {
380+ console . error ( 'STOMP 연결 에러' , error ) ;
381+ }
382+ ) ;
371383 }
372384</ script >
373385</ body >
0 commit comments