[STOMP] ์Šคํ”„๋ง + JWT + STOMP ๋กœ ์ฑ„ํŒ… ๊ธฐ๋Šฅ ๊ตฌํ˜„

์›น ์†Œ์ผ“์€ ์˜ˆ์ „๋ถ€ํ„ฐ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ์‹ถ์—ˆ๋‹ค. ์ง์ „ ํ”„๋กœ์ ํŠธ์—์„œ ์‹ค์‹œ๊ฐ„ ์•Œ๋žŒ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ–ˆ๋Š”๋ฐ ๊ทธ๋•Œ ์‚ฌ์šฉํ•˜๋ ค๋‹ค ์•Œ๋žŒ์—๋Š” SSE๊ฐ€ ๋” ์ ์ ˆํ•ด ๋ณด์˜€๊ณ  ์†Œ์ผ“์€ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๊ณ  ์ด๋ฒˆ์— ๊ฐœ์ธ์ ์œผ๋กœ ์ฑ„ํŒ… ์„œ๋น„์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. 

 

์›น ์†Œ์ผ“์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ธฐ ์ „ http ํ†ต์‹ ์˜ ํŠน์ง•๊ณผ ํ•œ๊ณ„์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž

 

 

 

http ํ†ต์‹ ์˜ ํŠน์ง•๊ณผ ํ•œ๊ณ„

httpํ†ต์‹ ์€ HyperText Transfer Plotocol์˜ ์•ฝ์ž๋กœ์„œ ์˜ค๋Š˜๋‚  ๊ด‘๋ฒ”์œ„ํ•˜๊ณ  ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ํ†ต์‹  ๊ธฐ๋ฒ•์ด๋‹ค.
http์˜ ํ†ต์‹  ๊ณผ์ •์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

  1. client๊ฐ€ server์—๊ฒŒ ์ž์‹ ์ด ๋ฐ›๊ณ ์‹ถ์€ ์ •๋ณด๋ฅผ request์— ๋‹ด์•„ ์ „์†กํ•œ๋‹ค.
  2. server๋Š” client์˜ request์— ๋”ฐ๋ผ์„œ ์•Œ๋งž์€ response๋กœ ์‘๋‹ตํ•œ๋‹ค.
  3. client๋Š” server์—๊ฒŒ ๋ฐ›์€ response์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

์ฆ‰ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๋Š” ์‹œ์ ์€ client๊ฐ€ server์— ์š”์ฒญ์„ ํ–ˆ์„ ๋•Œ ์ด๋‹ค.

 

httpํ†ต์‹ ์˜ ๊ฐ€์žฅ ํฐ ํŠน์ง• ์ค‘ Stateless, Connectionless๋ผ๋Š” ํŠน์ง•์ด ์žˆ๋‹ค.
client๊ฐ€ server์— request๋ฅผ ๋ณด๋‚ด๋ฉด, ์„œ๋ฒ„๋Š” ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ response๋ฅผ ํ•˜๊ณ , ๊ทธ ์ดํ›„ ์—ฐ๊ฒฐ์ด ๋Š์–ด์ ธ ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๋Š” ๋…๋ฆฝ๋œ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

๋”ฐ๋ผ์„œ http ํ†ต์‹ ์œผ๋กœ ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…์„ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ์ ˆ์ฐจ๋ฅผ ๋ฐŸ์•„์•ผ ํ•œ๋‹ค.

  1. client1์ด ์„œ๋ฒ„์— ๋ฉ”์„ธ์ง€๋ฅผ ์ „์†กํ•˜๊ณ ,
  2. server๋Š” ๊ทธ ๋ฉ”์„ธ์ง€๋ฅผ client2์—๊ฒŒ ์ „์†กํ•˜๊ณ ,
  3. client2๋Š” ์ž์‹ ์—๊ฒŒ ์˜จ ๋ฉ”์„ธ์ง€๋ฅผ ํ™•์ธํ•œ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด client2๋Š” ์–ธ์ œ ์–ด๋Š ์ฃผ๊ธฐ๋กœ ์ž์‹ ์—๊ฒŒ ๋ฉ”์„ธ์ง€๊ฐ€ ์™”๋Š”์ง€ ํ™•์ธํ•ด์•ผํ• ๊นŒ? ์ฃผ๊ธฐ์ ์œผ๋กœ ์š”์ฒญํ•˜๋Š” ๋ฐฉ๋ฒ•๋ฐ–์— ์—†๋Š”๋ฐ ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์„œ๋ฒ„์— ๋ฌด๋ฆฌ๊ฐ€ ๋‚˜๋ฉฐ ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ๊ธฐ๋Šฅ์ด๋ผ๊ณ ๋„ ํ•  ์ˆ˜ ์—†๋‹ค. 

 

๋”ฐ๋ผ์„œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์™€ ํ•œ๋ฒˆ ์—ฐ๊ฒฐ์„ ๋งบ๊ณ  ๋‚˜๋ฉด ์ปค๋„ฅ์…˜์„ ๋Š์ง€ ์•Š๊ณ  ์œ ์ง€ํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค. 

 

 

 

SSE(server push)

sse๋Š” server sent event์˜ ์ค„์ž„๋ง๋กœ ์ด๋ฒคํŠธ๊ฐ€ [์„œ๋ฒ„ -> ํด๋ผ์ด์–ธํŠธ] ๋ฐฉํ–ฅ์œผ๋กœ๋งŒ ํ๋ฅด๋Š” ๋‹จ๋ฐฉํ–ฅ ํ†ต์‹  ์ฑ„๋„์ด๋‹ค. SSE๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ polling๊ณผ ๊ฐ™์ด ์ฃผ๊ธฐ์ ์œผ๋กœ http ์š”์ฒญ์„ ๋ณด๋‚ผ ํ•„์š”์—†์ด http ์—ฐ๊ฒฐ์„ ํ†ตํ•ด ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค. 

์•Œ๋ฆผ ๊ฐ™์€ ๊ฒฝ์šฐ ์„œ๋ฒ„์™€ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋Š” ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์„œ๋ฒ„๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด๋‹ค. ๋”ฐ๋ผ์„œ ์„œ๋ฒ„->ํด๋ผ์ด์–ธํŠธ ์ปค๋„ฅ์…˜๋งŒ ์œ ์ง€ํ•˜๊ณ  ์žˆ์–ด๋„ ์ถฉ๋ถ„ํ•˜๋‹ค. 

 

ํ•˜์ง€๋งŒ ์ฑ„ํŒ…์€ ์œ ์ € A๊ฐ€ ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๊ณ  ์„œ๋ฒ„๋Š” ์ฑ„ํŒ…๋ฐฉ์— ์žˆ๋Š” ๋ชจ๋“  ์œ ์ € B, C, D.. ์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•ด์•ผ ํ•œ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ B, C, D..๋„ ๋ฉ”์„ธ์ง€๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ web socket ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

 

 

 

Web Socket

Web socket์€ ์‹ค์‹œ๊ฐ„ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์„ ์œ„ํ•œ ์ŠคํŽ™์œผ๋กœ ์„œ๋ฒ„์™€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ง€์†์ ์œผ๋กœ ์—ฐ๊ฒฐ๋œ TCP๋ผ์ธ์„ ํ†ตํ•ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. 

์ง€์†์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜๋Š” ์ •๋ณด๋ฅผ ์ˆ˜์‹ ํ•ด์•ผ ํ•˜๋Š” ์ฑ„ํŒ…์ด๋‚˜ ์ฃผ์‹ ๋ณด๊ณ ์„œ์—์„œ WebSocket ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•˜๋Š” ์ด์ ์ด ์žˆ๋‹ค. ์ด ํ”„๋กœํ† ์ฝœ์€ ์ •๋ณด๋ฅผ ๋™์‹œ์— ์†ก์ˆ˜์‹ ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ „์ด์ค‘ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์ด ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ ์ •๋ณด ๊ตํ™˜์ด ๋” ๋นจ๋ผ์ง„๋‹ค.

์›น ์†Œ์ผ“ ๋™์ž‘ ์›๋ฆฌ๋Š” ๋ฌด์—‡์ผ๊นŒ?

ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๊ฐ„์˜ ์—ฐ๊ฒฐ์€ ๋‹น์‚ฌ์ž ์ค‘ ํ•˜๋‚˜์— ์˜ํ•ด ์ข…๋ฃŒ๋˜๊ฑฐ๋‚˜ ์‹œ๊ฐ„ ์ดˆ๊ณผ์— ์˜ํ•ด ๋‹ซํž ๋•Œ๊นŒ์ง€ ์—ด๋ฆฐ ์ƒํƒœ๋กœ ์œ ์ง€๋œ๋‹ค. ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๊ฐ„์˜ ์—ฐ๊ฒฐ์„ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด ์œ„ ์‚ฌ์ง„ ์ฒ˜๋Ÿผ ํ•ธ๋“œ์…ฐ์ดํฌ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์„ค์ •๋œ ์—ฐ๊ฒฐ์€ ์—ด๋ฆฐ ์ƒํƒœ๋กœ ์œ ์ง€๋˜๋ฉฐ ํด๋ผ์ด์–ธํŠธ ๋˜๋Š” ์„œ๋ฒ„ ์ธก์—์„œ ์—ฐ๊ฒฐ์ด ์ข…๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋™์ผํ•œ ์ฑ„๋„์„ ์‚ฌ์šฉํ•˜์—ฌ ํ†ต์‹ ์ด ์ˆ˜ํ–‰๋œ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฉ”์‹œ์ง€๋Š” ์–‘๋ฐฉํ–ฅ์œผ๋กœ ์ „์†ก๋  ์ˆ˜ ์žˆ๋‹ค.

 

 

์›น ์†Œ์ผ“์˜ ํŠน์ง•

  1. ์–‘๋ฐฉํ–ฅ ํ†ต์‹ 
    • ๋ฐ์ดํ„ฐ ์†ก์ˆ˜์‹ ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ํ†ต์‹  ๋ฐฉ๋ฒ•
    • ํ†ต์ƒ์ ์ธ http ํ†ต์‹ ์€ client๊ฐ€ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒฝ์šฐ์—๋งŒ server์—์„œ ์‘๋‹ต
  2. ์‹ค์‹œ๊ฐ„ ๋„คํŠธ์›Œํ‚น
    • ์›น ํ™˜๊ฒฝ์—์„œ ์—ฐ์†๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋น ๋ฅด๊ฒŒ ๋…ธ์ถœ
    • ex) ์ฑ„ํŒ…, ์ฃผ์‹, ๋น„๋””์˜ค ๋ฐ์ดํ„ฐ
  3. ์š”์ฒญ ํ•ด๋”
    • Upgrade: websocket
    • Connection: Upgrade
    • ์œ„ ๋‘ ํ—ค๋”๊ฐ€ ์—†์œผ๋ฉด ์›น ์†Œ์ผ“ ์—ฐ๊ฒฐ์ด ๋˜์ง€ ์•Š๋Š”๋‹ค.
    • GET๋ฐฉ์‹์œผ๋กœ ํ•ธ๋“œ์‰์ดํ‚น ํ•˜๋ฉฐ HTTP ๋ฒ„์ „์€ ๋ฐ˜๋“œ์‹œ 1.1 ์ด์ƒ
    • ์œ„ ๋‚ด์šฉ๋“ค์€ Nginx๋ฅผ ์‚ฌ์šฉ ํ•  ๋•Œ ๋”ฐ๋กœ ์„ค์ •ํ•ด์ค˜์•ผ ํ–ˆ๋˜ ๋ถ€๋ถ„๋“ค์ž„

 

 

 

์ฝ”๋“œ๋กœ ์‚ดํŽด๋ณด์ž

WebSocketConfig.class

@Configuration
@RequiredArgsConstructor
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    private final ChatPreHandler chatPreHandler;
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/send");
        registry.enableSimpleBroker("/room");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws-stomp").setAllowedOriginPatterns("*").withSockJS();
    }

   
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(chatPreHandler);
    }
}
  • configureMessageBroker
    • ๋ณด๋‚ผ ๋•Œ์™€ ๋ฐ›์„ ๋•Œ์˜ prefix ์ง€์ •
    • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ๋•Œ ๊ฒฝ๋กœ ๋งจ์•ž์— "/send"์ด ๋ถ™์–ด์žˆ์œผ๋ฉด Broker๋กœ ๋ณด๋‚ด์ง.
    • "/room" ๊ฒฝ๋กœ๊ฐ€ ๋ถ™์€ ๊ฒฝ์šฐ messageBroker๊ฐ€ ์žก์•„์„œ ํ•ด๋‹น ์ฑ„ํŒ…๋ฐฉ์„ ๊ตฌ๋…ํ•˜๊ณ  ์žˆ๋Š” ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌํ•ด์คŒ
  • registerStompEndpoints
    • Client์—์„œ websocket์—ฐ๊ฒฐํ•  ๋•Œ ์‚ฌ์šฉํ•  API ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•ด์ฃผ๋Š” ๋ฉ”์„œ๋“œ.
  • configureClientInboundChannel
    • ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋งŒ ๋ฐ›๊ธฐ ์œ„ํ•ด chatPreHandler ๋“ฑ๋ก.

 

ChatPreHandler

@RequiredArgsConstructor
@Component
@Slf4j
public class ChatPreHandler implements ChannelInterceptor {

    private final JwtTokenProvider jwtTokenProvider;
    private static final String BEARER_PREFIX = "Bearer ";

    // websocket์„ ํ†ตํ•ด ๋“ค์–ด์˜จ ์š”์ฒญ์ด ์ฒ˜๋ฆฌ ๋˜๊ธฐ์ „ ์‹คํ–‰๋œ๋‹ค.
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
        if (StompCommand.CONNECT == accessor.getCommand()) { // websocket ์—ฐ๊ฒฐ์š”์ฒญ
            String jwtToken = accessor.getFirstNativeHeader("Authorization");
            log.info("CONNECT {}", jwtToken);
            // Header์˜ jwt token ๊ฒ€์ฆ
            String token = jwtToken.substring(7);
            jwtTokenProvider.validateTokenInChat(token);
        }

        return message;
    }
}

 

 

ChatController.class

@Controller
@RequiredArgsConstructor
@Slf4j
public class ChatController {
    private final ChatService chatService;
    private final JwtTokenProvider jwtTokenProvider;

    @MessageMapping("/{roomId}")
    @SendTo("/room/{roomId}") // ์—ฌ๊ธธ ๊ตฌ๋…ํ•˜๊ณ  ์žˆ๋Š” ๊ณณ์œผ๋กœ ๋ฉ”์‹œ์ง€ ์ „์†ก
    public ChatDto messageHandler(@DestinationVariable Long roomId, ChatDto message, @Header("token") String token) {
        String loginId = jwtTokenProvider.getUserNameFromJwt(token);//loginId ๊ฐ€์ ธ์˜ด
        return chatService.createChat(roomId, message.getMessage(), loginId);
    }
}

 

 

Client

Socket ์—ฐ๊ฒฐ, ๋ฉ”์‹œ์ง€ send ๋“ฑ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•˜์˜€๋‹ค.

์ฑ„ํŒ… ๊ตฌํ˜„ ๊ด€๋ จ ๋ธ”๋กœ๊ทธ ๊ธ€์„ ๋ณด๋ฉด ํด๋ผ์ด์–ธํŠธ์ชฝ ์ฝ”๋“œ๊ฐ€ ์ž˜ ์—†์–ด ํž˜๋“ค์—ˆ๊ธฐ์— ์ „์ฒด ์ฝ”๋“œ๋ฅผ ๊ณต์œ ํ•œ๋‹ค.

<script th:inline="javascript">
    // WebSocket ์—ฐ๊ฒฐ์„ ๊ด€๋ฆฌํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ
    let stompClient = null;

    // ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜จ ๋ฐฉ ID์™€ ์ฑ„ํŒ… ๋ชฉ๋ก
    let roomId = [[${roomId}]];
    let chatList = [[${chatList}]];
    const token = localStorage.getItem('token');
    const name = localStorage.getItem('name');

    // ์›น ์†Œ์ผ“ ์—ฐ๊ฒฐ ์ƒ์„ฑ
    function connect() {
        var socket = new SockJS('/ws-stomp');
        stompClient = Stomp.over(socket);

        let headers = {Authorization: token};
        stompClient.connect(headers, function (frame) {
            console.log('Connected: ' + frame);
            loadChat(chatList); // ์ €์žฅ๋œ ์ฑ„ํŒ… ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

            // ๊ตฌ๋… ์„ค์ •
            stompClient.subscribe('/room/' + roomId, function (chatMessage) {
                console.log(JSON.parse(chatMessage.body));
                showChat(JSON.parse(chatMessage.body));
            });
        });
    }

    // ์Šคํฌ๋กค ํ•ญ์ƒ ์•„๋ž˜๋กœ ์œ ์ง€
    function scrollChatToBottom() {
        let chatContainer = document.getElementById('chatting');
        chatContainer.scrollTop = chatContainer.scrollHeight;
    }

    function disconnect() {
        if (stompClient !== null) {
            stompClient.disconnect();
        }
        console.log("Disconnected");
    }


    // ์ž…๋ ฅ๋œ ์ฑ„ํŒ… ์ „์†ก
    function sendChat() {
        const message = $("#message").val();
        // WebSocket ํ†ต์‹ ์„ ์œ„ํ•œ ํ—ค๋” ์„ค์ •
        const headers = {
            "token": token // ํ† ํฐ ๊ฐ’์„ ์—ฌ๊ธฐ์— ๋„ฃ์–ด์ฃผ์„ธ์š”
        };
        stompClient.send("/send/" + roomId, headers, JSON.stringify({message: message}))
        $("#message").val("");
    }

    // ์ €์žฅ๋œ ์ฑ„ํŒ… ๋ถˆ๋Ÿฌ์™€ ํ™”๋ฉด์— ํ‘œ์‹œ
    function loadChat(chatList) {
        if (chatList != null) {
            for (let chat of chatList) {
                let chatHtml = '';
                if (chat.sender !== name) {
                    chatHtml = `<div class="chat ch1">
                    <div class="icon"><i class="fa-solid fa-user"></i></div>
                    <div class="sender">${chat.sender}</div>
                    <div class="textbox">${chat.message}</div>
                </div>`;
                } else if (chat.sender === name) {
                    chatHtml = `<div class="chat ch2">
                    <div class="icon"><i class="fa-solid fa-user"></i></div>
                    <div class="sender">${chat.sender}</div>
                    <div class="textbox">${chat.message}</div>
                </div>`;
                }
                $("#chatting").append(chatHtml);
            }
        }
    }


    // ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐ›์€ ์ฑ„ํŒ…์„ ํ™”๋ฉด์— ํ‘œ์‹œ
    function showChat(chatMessage) {
        let chatHtml = '';
        if (chatMessage.sender !== name) {
            chatHtml = `<div class="chat ch1">
            <div class="icon"><i class="fa-solid fa-user"></i></div>
            <div class="sender">${chatMessage.sender}</div>
            <div class="textbox">${chatMessage.message}</div>
        </div>`;
        } else if (chatMessage.sender === name) {
            chatHtml = `<div class="chat ch2">
            <div class="icon"><i class="fa-solid fa-user"></i></div>
            <div class="sender">${chatMessage.sender}</div>
            <div class="textbox">${chatMessage.message}</div>
        </div>`;
        }
        $("#chatting").append(chatHtml);
        scrollChatToBottom(); // ์Šคํฌ๋กค ํ•ญ์ƒ ์•„๋ž˜๋กœ ์œ ์ง€
    }

    // ํผ ์ œ์ถœ ์‹œ ๊ธฐ๋ณธ ๋™์ž‘ ๋ฐฉ์ง€
    $(function () {
        $("#chat-form").on('submit', function (e) {
            e.preventDefault();
        });
        // ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ์— ํ•จ์ˆ˜ ํ• ๋‹น
        $("#send").click(function () {
            sendChat();
        });
    });

</script>