w-aura/src/hooks/chatsSocket/useChatsSocket.ts
Daniil Chemerkin 0f949466c4 develop
2025-01-28 00:45:05 +00:00

339 lines
11 KiB
TypeScript

// 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<Socket<ServerToClientEvents, ClientToServerEvents> | null>(null);
const [messages, setMessages] = useState<IMessage[]>([]);
const [balance, setBalance] = useState<ICurrentBalance>();
const [session, setSession] = useState<ISessionStarted | null>();
// const [dateEnded, setDateEnded] = useState<string>("");
const [isLoading, setIsLoading] = useState(true);
// For first modal shown
const [initialBalance, setInitialBalance] = useState<number | null>(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<null>) => {
console.log("chatJoined");
}, [])
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const chatLeft = useCallback((_response: IResponse<boolean>) => {
console.log("chatLeft");
}, [])
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const balanceUpdated = useCallback((_response: IResponse<IBalanceUpdated>) => {
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<ICurrentBalance>) => {
setBalance(balance?.data);
}, [])
const sessionStarted = useCallback((response: IResponse<ISessionStarted>) => {
setSession(response?.data);
}, [])
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const sessionEnded = useCallback((_response: IResponse<boolean>) => {
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<null>) => {
chatJoined(response);
});
newSocket.on('receive_message', (message: IMessage[]) => {
receiveMessage(message);
});
newSocket.on('current_balance', (balance: IResponse<ICurrentBalance>) => {
currentBalance(balance);
});
newSocket.on('session_started', (response: IResponse<ISessionStarted>) => {
sessionStarted(response);
});
newSocket.on('session_ended', (response: IResponse<boolean>) => {
sessionEnded(response);
});
newSocket.on('chat_left', (response: IResponse<boolean>) => {
chatLeft(response);
});
newSocket.on('balance_updated', (response: IResponse<IBalanceUpdated>) => {
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<void> = 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;