// src/hooks/useChatSocket.js import { useApi } from '@/api'; import { IMessage } from '@/api/resources/ChatMessages'; // import { useAuth } from '@/auth'; import routes from '@/routes'; // import { getZodiacSignByDate } from '@/services/zodiac-sign'; import { actions, selectors } from '@/store'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { io, Socket } from 'socket.io-client'; import { ClientToServerEvents, IBalanceUpdated, ICurrentBalance, IResponse, ISessionStarted, ServerToClientEvents } from './data'; import { sleep } from '@/services/date'; const compareDates = (date1: string, date2: string) => { return new Date(date1).getTime() - new Date(date2).getTime(); } const isAvailableDateUsing = (dateEnd?: string) => { if (!dateEnd) { return false } return compareDates(dateEnd, new Date().toString()) > 0 // return (new Date(dateEnd).getTime() - new Date().getTime()) > 0 } const useChatSocket = (userId: string, chatId: string) => { const dispatch = useDispatch(); const token = useSelector(selectors.selectToken) const api = useApi(); const [socket, setSocket] = useState | null>(null); const [messages, setMessages] = useState([]); const [balance, setBalance] = useState(); const [session, setSession] = useState(); // const [dateEnded, setDateEnded] = useState(""); const [isLoading, setIsLoading] = useState(true); // For first modal shown const [initialBalance, setInitialBalance] = useState(null); const [isLoadingSelfMessage, setIsLoadingSelfMessage] = useState(false); const [isLoadingAdvisorMessage, setIsLoadingAdvisorMessage] = useState(false); const [isAvailableChatting, setIsAvailableChatting] = useState(true); const dateEndChattingFromStore = useSelector(selectors.selectDateEndChatting); const dateEndChatting = useMemo(() => { // return "2024-11-17T00:26:19.329Z" if (dateEndChattingFromStore?.length) return dateEndChattingFromStore; if (balance?.maxFinishedAt) return balance.maxFinishedAt; if (session?.maxFinishedAt) return session.maxFinishedAt; return "2000-01-01T00:00:00.290Z" }, [balance, dateEndChattingFromStore, session]); const messagesAfterEnd = useMemo(() => { return messages.filter((message) => compareDates(message.createdDate, dateEndChatting) >= 0) }, [dateEndChatting, messages]) const isAvailableChattingByMessages = useMemo(() => { return !messagesAfterEnd.find((message) => message.role === "assistant") || isAvailableChatting }, [isAvailableChatting, messagesAfterEnd]) // send const joinChat = useCallback((chatId: string) => { if (chatId && socket) { socket.emit('join_chat', { chatId }); } }, [socket]); const startSession = useCallback((chatId: string) => { if (chatId && socket && !session) { socket.emit('start_session', { chatId }); } }, [session, socket]); const sendMessage = useCallback((message: string) => { if (chatId && socket) { setIsLoadingAdvisorMessage(true); setIsLoadingSelfMessage(true); socket.emit('send_message', { chatId, message }); } }, [chatId, socket]); const readMessage = useCallback((messagesId: string[]) => { if (chatId && socket) { socket.emit('read_message', { messages: messagesId }); } }, [chatId, socket]); const endSession = useCallback((chatId: string) => { if (chatId && socket) { socket.emit('end_session', { chatId }); } }, [socket]); const leaveChat = useCallback((chatId: string) => { if (chatId && socket) { socket.emit('leave_chat', { chatId }); } }, [socket]); const fetchBalance = useCallback((chatId: string) => { if (chatId && socket) { socket.emit('fetch_balance', { chatId }); } }, [socket]); // receive // eslint-disable-next-line @typescript-eslint/no-unused-vars const chatJoined = useCallback((_response: IResponse) => { console.log("chatJoined"); }, []) // eslint-disable-next-line @typescript-eslint/no-unused-vars const chatLeft = useCallback((_response: IResponse) => { console.log("chatLeft"); }, []) // eslint-disable-next-line @typescript-eslint/no-unused-vars const balanceUpdated = useCallback((_response: IResponse) => { fetchBalance(chatId); }, [chatId, fetchBalance]) const receiveMessage = useCallback((message: IMessage[]) => { if (!message?.[0]) return; if (message[0].role === "user") { setIsLoadingSelfMessage(false); } if (message[0].role === "assistant") { setIsLoadingAdvisorMessage(false); } setMessages((prev) => { if (prev.some(msg => msg.id === message[0].id)) { return prev; } return [...prev, message[0]]; }); }, []); const currentBalance = useCallback((balance: IResponse) => { setBalance(balance?.data); }, []) const sessionStarted = useCallback((response: IResponse) => { setSession(response?.data); }, []) // eslint-disable-next-line @typescript-eslint/no-unused-vars const sessionEnded = useCallback((_response: IResponse) => { setSession(null); }, []) // logic const init = useCallback(async () => { if (!chatId || !userId) { return; } const messages = await api.getChatMessages({ token, chatId }) // routes.server.chatSocket() const newSocket = io(routes.server.chatSocket(), { query: { userId } }); setSocket(newSocket); setMessages(messages?.messages || []); newSocket.on('chat_joined', (response: IResponse) => { chatJoined(response); }); newSocket.on('receive_message', (message: IMessage[]) => { receiveMessage(message); }); newSocket.on('current_balance', (balance: IResponse) => { currentBalance(balance); }); newSocket.on('session_started', (response: IResponse) => { sessionStarted(response); }); newSocket.on('session_ended', (response: IResponse) => { sessionEnded(response); }); newSocket.on('chat_left', (response: IResponse) => { chatLeft(response); }); newSocket.on('balance_updated', (response: IResponse) => { balanceUpdated(response); }); return () => { newSocket.disconnect(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { setIsLoading(true); init(); }, [init]); // useEffect(() => { // if (!socket) return setIsAvailableChatting(false); // joinChat(chatId); // fetchBalance(chatId); // }, [chatId, fetchBalance, joinChat, socket]); useEffect(() => { const interval = setInterval(() => { fetchBalance(chatId); }, 5000); return () => clearInterval(interval); }, [chatId, fetchBalance, session]); useEffect(() => { if (!balance) return setIsAvailableChatting(false); if (initialBalance === null) setInitialBalance(balance.balance); if (balance.balance > 0 && !session) { startSession(chatId); } if (balance.balance <= 0 && session) { endSession(chatId); } setIsLoading(false); // eslint-disable-next-line react-hooks/exhaustive-deps }, [balance, chatId, endSession, session, startSession]); useEffect(() => { if (!session) return setIsAvailableChatting(false); setIsLoading(true); const checkIsAvailableChatting: () => Promise = async () => { const isAvailableDate = isAvailableDateUsing(session?.maxFinishedAt); if (isAvailableDate) { setIsAvailableChatting(true); setIsLoading(false); await sleep(1000); return checkIsAvailableChatting(); } setIsAvailableChatting(false); setIsLoading(false); } checkIsAvailableChatting(); }, [chatId, endSession, session]) useEffect(() => { if (session?.maxFinishedAt?.length) { dispatch(actions.chat.updateDateEndChatting(session.maxFinishedAt)); } }, [dispatch, session]); useEffect(() => { if (!socket) return; const handleReconnect = () => { setIsLoadingSelfMessage(false); setIsLoadingAdvisorMessage(false); joinChat(chatId); fetchBalance(chatId); }; socket.on('connect', handleReconnect); socket.on('disconnect', () => { setIsLoadingSelfMessage(false); setIsLoadingAdvisorMessage(false); }); return () => { socket.off('connect', handleReconnect); socket.off('disconnect'); }; }, [socket, chatId, joinChat, fetchBalance]); // clean up useEffect(() => { const handleBeforeUnload = () => { if (socket) { endSession(chatId); leaveChat(chatId); socket.disconnect(); } }; window.addEventListener('beforeunload', handleBeforeUnload); return () => { window.removeEventListener('beforeunload', handleBeforeUnload); handleBeforeUnload(); }; }, [socket, chatId, endSession, leaveChat]); return useMemo(() => ({ isLoading, isLoadingSelfMessage, isLoadingAdvisorMessage, socket, messages, balance, session, isAvailableChatting, messagesAfterEnd, isAvailableChattingByMessages, initialBalance, sendMessage, readMessage }), [ isLoading, isLoadingSelfMessage, isLoadingAdvisorMessage, socket, messages, balance, session, isAvailableChatting, messagesAfterEnd, isAvailableChattingByMessages, initialBalance, sendMessage, readMessage ]); }; export default useChatSocket;