From 03ae7bb16021058f40543dfc3e9f423a2f35582a Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 18 Nov 2024 21:06:56 -0800 Subject: [PATCH] feature/IO-3000-Migrate-MSG-to-Sockets - Progress Checkpoint Signed-off-by: Dave Richer --- .../chat-conversation.container.jsx | 34 +++++++++++++------ client/src/contexts/SocketIO/useSocket.js | 8 ++--- .../pages/manage/manage.page.component.jsx | 2 +- .../src/redux/messaging/messaging.actions.js | 6 ++-- .../src/redux/messaging/messaging.reducer.js | 17 +++++++--- server/web-sockets/redisSocketEvents.js | 27 +++++++++++---- 6 files changed, 64 insertions(+), 30 deletions(-) diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index c4552950a..dec5dcb68 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -4,15 +4,19 @@ import { createStructuredSelector } from "reselect"; import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; import ChatConversationComponent from "./chat-conversation.component"; -import axios from "axios"; import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; +import { updateUnreadCount } from "../../redux/messaging/messaging.actions.js"; const mapStateToProps = createStructuredSelector({ selectedConversation: selectSelectedConversation, bodyshop: selectBodyshop }); -export function ChatConversationContainer({ bodyshop, selectedConversation }) { +const mapDispatchToProps = (dispatch) => ({ + updateUnreadCounts: (data) => dispatch(updateUnreadCount(data.conversationId, data.unreadcnt)) +}); + +export function ChatConversationContainer({ bodyshop, selectedConversation, updateUnreadCounts }) { const { socket } = useContext(SocketContext); const [conversationDetails, setConversationDetails] = useState({}); const [messages, setMessages] = useState([]); @@ -36,26 +40,34 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { setMessages((prevMessages) => [...prevMessages, message]); }); + socket.on("unread-count-updated", (data) => { + updateUnreadCounts(data); + }); + + socket.on("conversation-list-updated", (data) => { + setConversationDetails(data.conversation); + setMessages(data.messages); + }); + return () => { socket.emit("leave-conversation", selectedConversation); socket.off("conversation-details"); socket.off("new-message"); + socket.off("unread-count-updated"); + socket.off("conversation-list-updated"); }; } - }, [socket, selectedConversation]); + }, [socket, selectedConversation, updateUnreadCounts]); // Mark messages as read const handleMarkConversationAsRead = async () => { if (messages.some((msg) => !msg.read) && !markingAsReadInProgress) { setMarkingAsReadInProgress(true); - // Emit a WebSocket event to mark messages as read - socket.emit("mark-as-read", { conversationId: selectedConversation }); - - // Fallback to an API call to update the read status in the database - await axios.post("/sms/markConversationRead", { - conversationid: selectedConversation, - imexshopid: bodyshop.imexshopid + socket.emit("mark-as-read", { + conversationId: selectedConversation, + imexshopid: bodyshop.imexshopid, + bodyshopId: bodyshop.id }); setMarkingAsReadInProgress(false); @@ -76,4 +88,4 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { ); } -export default connect(mapStateToProps, null)(ChatConversationContainer); +export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationContainer); diff --git a/client/src/contexts/SocketIO/useSocket.js b/client/src/contexts/SocketIO/useSocket.js index 4b562ab61..1833a1558 100644 --- a/client/src/contexts/SocketIO/useSocket.js +++ b/client/src/contexts/SocketIO/useSocket.js @@ -81,13 +81,13 @@ const useSocket = (bodyshop) => { dispatch({ type: "ADD_MESSAGE", payload: data.message }); }; - const handleReadUpdated = ({ conversationId }) => { - dispatch({ type: "UPDATE_UNREAD_COUNT", payload: conversationId }); - }; + // const handleReadUpdated = ({ conversationId }) => { + // dispatch({ type: "UPDATE_UNREAD_COUNT", payload: conversationId }); + // }; socketInstance.on("messaging-list", handleMessagingList); socketInstance.on("new-message", handleNewMessage); - socketInstance.on("read-updated", handleReadUpdated); + // socketInstance.on("mark-as-read", handleReadUpdated); socketInstance.on("connect", handleConnect); socketInstance.on("reconnect", handleReconnect); socketInstance.on("connect_error", handleConnectionError); diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index a5b953335..c1a8e561c 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -145,7 +145,7 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) { }; fetchAlerts(); - }, []); + }, [setAlerts]); // Use useEffect to watch for new alerts useEffect(() => { diff --git a/client/src/redux/messaging/messaging.actions.js b/client/src/redux/messaging/messaging.actions.js index ce2f3feed..a33c236ad 100644 --- a/client/src/redux/messaging/messaging.actions.js +++ b/client/src/redux/messaging/messaging.actions.js @@ -52,12 +52,12 @@ export const addMessage = (message) => ({ // Add a Conversation to the list of conversations export const addConversation = (conversation) => ({ - type: "ADD_CONVERSATION", + type: MessagingActionTypes.ADD_CONVERSATION, payload: conversation }); // Update unread count for a conversation (e.g., after marking messages as read) -export const updateUnreadCount = (conversationId) => ({ +export const updateUnreadCount = (conversationId, unreadCount) => ({ type: MessagingActionTypes.UPDATE_UNREAD_COUNT, - payload: conversationId + payload: { conversationId, unreadCount } }); diff --git a/client/src/redux/messaging/messaging.reducer.js b/client/src/redux/messaging/messaging.reducer.js index 27477b695..37402f419 100644 --- a/client/src/redux/messaging/messaging.reducer.js +++ b/client/src/redux/messaging/messaging.reducer.js @@ -8,7 +8,7 @@ const INITIAL_STATE = { message: null, conversations: [], // Holds the list of conversations messages: [], // Holds the list of messages for the selected conversation - unreadCount: 0, + unreadcnt: 0, searchingForConversation: false }; @@ -83,12 +83,21 @@ const messagingReducer = (state = INITIAL_STATE, action) => { conversations: { ...state.conversations, conversations: state.conversations.conversations.map((conversation) => - conversation.id === action.payload - ? { ...conversation, unreadcnt: 0 } // Reset unread count for the selected conversation + conversation.id === action.payload.conversationId + ? { ...conversation, unreadcnt: action.payload.unreadcnt } // Update unread count to the value in the payload : conversation ) }, - unreadCount: Math.max(state.unreadCount - 1, 0) // Ensure unreadCount does not go below zero + unreadcnt: Math.max( + state.conversations.conversations.reduce( + (total, conversation) => + conversation.id === action.payload.conversationId + ? total + action.payload.unreadcnt + : total + conversation.unreadcnt, + 0 + ), + 0 + ) // Recalculate the global unreadcnt based on all conversations }; default: diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js index de2f8e25a..f4b2329d3 100644 --- a/server/web-sockets/redisSocketEvents.js +++ b/server/web-sockets/redisSocketEvents.js @@ -1,6 +1,5 @@ const { admin } = require("../firebase/firebase-handler"); const { MARK_MESSAGES_AS_READ, GET_CONVERSATIONS, GET_CONVERSATION_DETAILS } = require("../graphql-client/queries"); -const logger = require("../utils/logger"); const { phone } = require("phone"); const { client: gqlClient } = require("../graphql-client/graphql-client"); const queries = require("../graphql-client/queries"); @@ -151,7 +150,6 @@ const redisSocketEvents = ({ const conversations = await client.request(GET_CONVERSATIONS, { bodyshopId: bodyshopUUID }); socket.emit("messaging-list", { conversations }); } catch (error) { - console.dir(error); logger.log("error", "Failed to fetch conversations", error); socket.emit("error", { message: "Failed to fetch conversations" }); } @@ -171,19 +169,35 @@ const redisSocketEvents = ({ } }; - const markAsRead = async ({ conversationId, userId }) => { + const markAsRead = async ({ conversationId, userId, imexshopid, bodyshopId }) => { try { await client.request(MARK_MESSAGES_AS_READ, { conversationId, userId }); - io.to(`conversation-${conversationId}`).emit("read-updated", { conversationId }); + + // Fetch the updated unread count for this conversation + const conversations = await client.request(GET_CONVERSATIONS, { bodyshopId }); + + // Emit the updated unread count to all clients + const room = `conversation-${conversationId}`; + io.to(room).emit("messaging-list", { conversations }); + + admin.messaging().send({ + topic: `${imexshopid}-messaging`, + data: { + type: "messaging-mark-conversation-read", + conversationid: conversationId || "" + } + }); } catch (error) { - logger.log("error", "Failed to mark messages as read", error); + logger.log("Failed to mark messages as read", "error", null, null, { + message: error.message, + stack: error.stack + }); socket.emit("error", { message: "Failed to mark messages as read" }); } }; const sendMessage = (data) => { const { to, messagingServiceSid, body, conversationid, selectedMedia, imexshopid, user } = data; - console.dir({ data }); logger.log("sms-outbound", "DEBUG", user.email, null, { messagingServiceSid: messagingServiceSid, to: phone(to).phoneNumber, @@ -217,7 +231,6 @@ const redisSocketEvents = ({ gqlClient .request(queries.INSERT_MESSAGE, { msg: newMessage, conversationid }) .then((r2) => { - //console.log("Responding GQL Message ID", JSON.stringify(r2)); logger.log("sms-outbound-success", "DEBUG", user.email, null, { msid: message.sid, conversationid