From 15151cb4ac0bb943cda1125cbf51e44383632259 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 20 Nov 2024 19:23:35 -0800 Subject: [PATCH] feature/IO-3000-messaging-sockets-migrations2 - - sync send - fix status events Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 78 +++++++++++++++---- .../chat-send-message.component.jsx | 17 +++- server/sms/status.js | 14 +--- server/web-sockets/redisSocketEvents.js | 13 ++++ 4 files changed, 92 insertions(+), 30 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index 120c184dd..44b79c3b3 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -1,4 +1,5 @@ import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries"; +import { gql } from "@apollo/client"; export const registerMessagingHandlers = ({ socket, client }) => { if (!(socket && client)) return; @@ -14,8 +15,6 @@ export const registerMessagingHandlers = ({ socket, client }) => { const fullConversation = { ...newConversation, - phone_num: newConversation.phone_num, - id: newConversation.id, updated_at: newConversation.updated_at || new Date().toISOString(), unreadcnt: newConversation.unreadcnt || 0, archived: newConversation.archived || false, @@ -77,21 +76,43 @@ export const registerMessagingHandlers = ({ socket, client }) => { }; const handleMessageChanged = (message) => { - // Find the message in the cache and update all fields dynamically client.cache.modify({ - id: client.cache.identify({ - __typename: "messages", - id: message.id - }), + id: client.cache.identify({ __typename: "conversations", id: message.conversationid }), fields: { - // Dynamically update all fields based on the incoming message object - __typename: (existingType) => existingType || "messages", // Ensure __typename is preserved - ...Object.fromEntries( - Object.entries(message).map(([key, value]) => [ - key, - (cached) => (value !== undefined ? value : cached) // Update with new value or keep existing - ]) - ) + ...(message.type === "status-changed" && { + messages(existing = [], { readField }) { + return existing.map((messageRef) => { + // Match the message by ID + if (readField("id", messageRef) === message.id) { + const currentStatus = readField("status", messageRef); + + // Prevent overwriting if the current status is already "delivered" + if (currentStatus === "delivered") { + return messageRef; + } + + // Update the existing message fields + return client.cache.writeFragment({ + id: messageRef.__ref, + fragment: gql` + fragment UpdatedMessage on messages { + id + status + conversationid + __typename + } + `, + data: { + __typename: "messages", + ...message // Only update the fields provided in the message object + } + }); + } + + return messageRef; // Keep other messages unchanged + }); + } + }) } }); }; @@ -113,6 +134,7 @@ export const registerMessagingHandlers = ({ socket, client }) => { client.cache.modify({ id: cacheId, fields: { + // This is a catch-all for just sending it fields off conversation ...Object.fromEntries( Object.entries(fields).map(([key, value]) => [ key, @@ -138,6 +160,30 @@ export const registerMessagingHandlers = ({ socket, client }) => { }); }; + const handleNewMessage = ({ conversationId, message }) => { + client.cache.modify({ + id: client.cache.identify({ __typename: "conversations", id: conversationId }), + fields: { + messages(existing = []) { + const newMessageRef = client.cache.writeFragment({ + data: message, + fragment: gql` + fragment NewMessage on messages { + id + body + createdAt + selectedMedia + imexshopid + } + ` + }); + return [...existing, newMessageRef]; + } + } + }); + }; + + socket.on("new-message", handleNewMessage); socket.on("new-message-summary", handleNewMessageSummary); socket.on("new-message-detailed", handleNewMessageDetailed); socket.on("message-changed", handleMessageChanged); @@ -146,9 +192,9 @@ export const registerMessagingHandlers = ({ socket, client }) => { export const unregisterMessagingHandlers = ({ socket }) => { if (!socket) return; + socket.off("new-message"); socket.off("new-message-summary"); socket.off("new-message-detailed"); socket.off("message-changed"); - socket.off("message-changed"); socket.off("conversation-changed"); }; diff --git a/client/src/components/chat-send-message/chat-send-message.component.jsx b/client/src/components/chat-send-message/chat-send-message.component.jsx index 918be1b5f..2ea90322e 100644 --- a/client/src/components/chat-send-message/chat-send-message.component.jsx +++ b/client/src/components/chat-send-message/chat-send-message.component.jsx @@ -1,6 +1,6 @@ import { LoadingOutlined, SendOutlined } from "@ant-design/icons"; import { Input, Spin } from "antd"; -import React, { useEffect, useRef, useState } from "react"; +import React, { useContext, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -10,6 +10,7 @@ import { selectIsSending, selectMessage } from "../../redux/messaging/messaging. import { selectBodyshop } from "../../redux/user/user.selectors"; import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component"; import ChatPresetsComponent from "../chat-presets/chat-presets.component"; +import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -25,6 +26,8 @@ const mapDispatchToProps = (dispatch) => ({ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSending, message, setMessage }) { const inputArea = useRef(null); const [selectedMedia, setSelectedMedia] = useState([]); + const { socket } = useContext(SocketContext); + useEffect(() => { inputArea.current.focus(); }, [isSending, setMessage]); @@ -37,14 +40,22 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi logImEXEvent("messaging_send_message"); if (selectedImages.length < 11) { - sendMessage({ + const newMessage = { to: conversation.phone_num, body: message || "", messagingServiceSid: bodyshop.messagingservicesid, conversationid: conversation.id, selectedMedia: selectedImages, imexshopid: bodyshop.imexshopid - }); + }; + sendMessage(newMessage); + if (socket) { + socket.emit("message-added", { + conversationId: conversation.id, + bodyshopId: bodyshop.id, + message: newMessage + }); + } setSelectedMedia( selectedMedia.map((i) => { return { ...i, isSelected: false }; diff --git a/server/sms/status.js b/server/sms/status.js index 9527ed5f8..bfdeb5ed2 100644 --- a/server/sms/status.js +++ b/server/sms/status.js @@ -36,7 +36,9 @@ exports.status = async (req, res) => { }); ioRedis.to(conversationRoom).emit("message-changed", { - message + ...message, + status: SmsStatus, + type: "status-changed" }); } else { logger.log("sms-status-update-warning", "WARN", "api", null, { @@ -80,21 +82,11 @@ exports.markConversationRead = async (req, res) => { const broadcastRoom = getBodyshopRoom(bodyshopid); - const conversationRoom = getBodyshopConversationRoom({ - bodyshopId: bodyshopid, - conversationId: conversationid - }); - ioRedis.to(broadcastRoom).emit("conversation-changed", { type: "conversation-marked-read", conversationId: conversationid }); - ioRedis.to(conversationRoom).emit("message-changed", { - type: "all-messages-marked-read", - conversationId: conversationid - }); - res.status(200).json({ success: true, message: "Conversation marked as read." }); } catch (error) { logger.log("conversation-mark-read-error", "ERROR", "api", null, { diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js index d2dd9eb7b..33faab361 100644 --- a/server/web-sockets/redisSocketEvents.js +++ b/server/web-sockets/redisSocketEvents.js @@ -178,6 +178,19 @@ const redisSocketEvents = ({ } }; + const messageAdded = ({ bodyshopId, conversationId, message }) => { + try { + const room = getBodyshopConversationRoom({ bodyshopId, conversationId }); + io.to(room).emit("new-message", message); + } catch (error) { + logger.log("Failed to handle new message", "error", "io-redis", null, { + error: error.message, + stack: error.stack + }); + } + }; + + socket.on("message-added", messageAdded); socket.on("conversation-modified", conversationModified); socket.on("join-bodyshop-conversation", joinConversationRoom); socket.on("leave-bodyshop-conversation", leaveConversationRoom);