useChat.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. 'use client';
  2. import { useState, useEffect, useCallback, useRef } from 'react';
  3. import { useSignalRContext } from '@/contexts/signalrProvider';
  4. import type { ChatMessage } from '@/types/chat';
  5. type SystemMessage = {
  6. id: number;
  7. content: string;
  8. receivedAt: string;
  9. };
  10. type ChatParticipant = {
  11. memberName: string;
  12. isGuest: boolean;
  13. };
  14. let systemMsgId = 0;
  15. export default function useChat(channelSID?: string) {
  16. const { chatConnection, chatConnected } = useSignalRContext();
  17. const [messages, setMessages] = useState<ChatMessage[]>([]);
  18. const [systemMessages, setSystemMessages] = useState<SystemMessage[]>([]);
  19. const [participantCount, setParticipantCount] = useState(0);
  20. const [participants, setParticipants] = useState<ChatParticipant[]>([]);
  21. const prevConnectionRef = useRef<typeof chatConnection>(null);
  22. const prevChannelRef = useRef<string|null>(null);
  23. useEffect(() => {
  24. if (!chatConnection || !chatConnected) {
  25. return;
  26. }
  27. // 동일 connection + 동일 channel 이면 중복 등록 방지
  28. if (prevConnectionRef.current === chatConnection && prevChannelRef.current === channelSID) {
  29. return;
  30. }
  31. prevConnectionRef.current = chatConnection;
  32. prevChannelRef.current = channelSID ?? null;
  33. chatConnection.on('ReceiveHistory', (history: ChatMessage[]) => {
  34. setMessages(history);
  35. });
  36. chatConnection.on('ReceiveMessage', (message: ChatMessage) => {
  37. setMessages((prev) => [...prev, message]);
  38. });
  39. chatConnection.on('ReceiveSystemMessage', (content: string) => {
  40. setSystemMessages((prev) => [
  41. ...prev,
  42. { id: ++systemMsgId, content, receivedAt: new Date().toISOString() }
  43. ]);
  44. });
  45. chatConnection.on('ReceiveParticipantCount', (count: number) => {
  46. setParticipantCount(count);
  47. });
  48. chatConnection.on('ReceiveParticipants', (list: ChatParticipant[]) => {
  49. setParticipants(list);
  50. });
  51. // 채널 참가 (channelSID 필수)
  52. if (channelSID) {
  53. chatConnection.invoke('JoinChannel', channelSID).catch((err) => {
  54. console.error('채팅 채널 참가 실패:', err);
  55. });
  56. }
  57. return () => {
  58. chatConnection.off('ReceiveHistory');
  59. chatConnection.off('ReceiveMessage');
  60. chatConnection.off('ReceiveSystemMessage');
  61. chatConnection.off('ReceiveParticipantCount');
  62. chatConnection.off('ReceiveParticipants');
  63. // 채널 퇴장
  64. if (channelSID && chatConnection.state === 'Connected') {
  65. chatConnection.invoke('LeaveChannel').catch(() => {});
  66. }
  67. prevConnectionRef.current = null;
  68. prevChannelRef.current = null;
  69. };
  70. }, [chatConnection, chatConnected, channelSID]);
  71. const sendMessage = useCallback(async (content: string) => {
  72. if (!chatConnection || !chatConnected) {
  73. return;
  74. }
  75. const trimmed = content.trim();
  76. if (!trimmed || trimmed.length > 500) {
  77. return;
  78. }
  79. try {
  80. await chatConnection.invoke('SendMessage', trimmed);
  81. } catch (error) {
  82. console.error('메시지 전송 실패:', error);
  83. }
  84. }, [chatConnection, chatConnected]);
  85. const clearMessages = useCallback(() => {
  86. setMessages([]);
  87. setSystemMessages([]);
  88. }, []);
  89. const refreshChat = useCallback(async () => {
  90. if (!chatConnection || !chatConnected) {
  91. return;
  92. }
  93. setMessages([]);
  94. setSystemMessages([]);
  95. try {
  96. await chatConnection.invoke('RequestHistory');
  97. await chatConnection.invoke('RequestParticipantCount');
  98. } catch {
  99. // ignore
  100. }
  101. }, [chatConnection, chatConnected]);
  102. const requestParticipants = useCallback(async () => {
  103. if (!chatConnection || !chatConnected) {
  104. return;
  105. }
  106. try {
  107. await chatConnection.invoke('RequestParticipants');
  108. } catch {
  109. // ignore
  110. }
  111. }, [chatConnection, chatConnected]);
  112. return {
  113. messages,
  114. systemMessages,
  115. participantCount,
  116. participants,
  117. sendMessage,
  118. clearMessages,
  119. refreshChat,
  120. requestParticipants,
  121. chatConnected
  122. };
  123. }