From 5392659db6d8b846bbf7ee2c83bba589bfea9a79 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 21 Nov 2024 11:32:43 -0800 Subject: [PATCH] feature/IO-3000-messaging-sockets-migrations2 - - Checkpoint Signed-off-by: Dave Richer --- .../registerMessagingSocketHandlers.js | 92 +++++++++++++++---- .../chat-archive-button.component.jsx | 20 +++- .../chat-conversation-title.component.jsx | 2 +- .../chat-conversation.container.jsx | 1 - .../chat-send-message.component.jsx | 3 +- server/sms/status.js | 6 ++ server/web-sockets/redisSocketEvents.js | 2 +- 7 files changed, 100 insertions(+), 26 deletions(-) diff --git a/client/src/components/chat-affix/registerMessagingSocketHandlers.js b/client/src/components/chat-affix/registerMessagingSocketHandlers.js index 44b79c3b3..ab4473e97 100644 --- a/client/src/components/chat-affix/registerMessagingSocketHandlers.js +++ b/client/src/components/chat-affix/registerMessagingSocketHandlers.js @@ -1,35 +1,45 @@ import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries"; import { gql } from "@apollo/client"; +const logLocal = (message, ...args) => { + if (import.meta.env.PROD) { + return; + } + console.log(`==================== ${message} ====================`); + console.dir({ ...args }); +}; + export const registerMessagingHandlers = ({ socket, client }) => { if (!(socket && client)) return; const handleNewMessageSummary = (message) => { const { conversationId, newConversation, existingConversation, isoutbound } = message; + logLocal("handleNewMessageSummary", message); + if (!existingConversation && newConversation?.phone_num) { const queryResults = client.cache.readQuery({ query: CONVERSATION_LIST_QUERY, variables: { offset: 0 } }); - - const fullConversation = { - ...newConversation, - updated_at: newConversation.updated_at || new Date().toISOString(), - unreadcnt: newConversation.unreadcnt || 0, - archived: newConversation.archived || false, - label: newConversation.label || null, - job_conversations: newConversation.job_conversations || [], - messages_aggregate: newConversation.messages_aggregate || { - aggregate: { count: isoutbound ? 0 : 1 } - } - }; - client.cache.writeQuery({ query: CONVERSATION_LIST_QUERY, variables: { offset: 0 }, data: { - conversations: [fullConversation, ...(queryResults?.conversations || [])] + conversations: [ + { + ...newConversation, + updated_at: newConversation.updated_at || new Date().toISOString(), + unreadcnt: newConversation.unreadcnt || 0, + archived: newConversation.archived || false, + label: newConversation.label || null, + job_conversations: newConversation.job_conversations || [], + messages_aggregate: newConversation.messages_aggregate || { + aggregate: { count: isoutbound ? 0 : 1 } + } + }, + ...(queryResults?.conversations || []) + ] } }); } else { @@ -51,9 +61,12 @@ export const registerMessagingHandlers = ({ socket, client }) => { }); } }; + const handleNewMessageDetailed = (message) => { const { conversationId, newMessage } = message; + logLocal("handleNewMessageDetailed", message); + // Append the new message to the conversation's message list const queryResults = client.cache.readQuery({ query: GET_CONVERSATION_DETAILS, @@ -76,6 +89,10 @@ export const registerMessagingHandlers = ({ socket, client }) => { }; const handleMessageChanged = (message) => { + if (!message) return; + + logLocal("handleMessageChanged", message); + client.cache.modify({ id: client.cache.identify({ __typename: "conversations", id: message.conversationid }), fields: { @@ -118,8 +135,12 @@ export const registerMessagingHandlers = ({ socket, client }) => { }; const handleConversationChanged = (data) => { + if (!data) return; + const { conversationId, type, job_conversations, ...fields } = data; + logLocal("handleConversationChanged", data); + // Identify the conversation in the Apollo cache const cacheId = client.cache.identify({ __typename: "conversations", @@ -161,23 +182,60 @@ export const registerMessagingHandlers = ({ socket, client }) => { }; const handleNewMessage = ({ conversationId, message }) => { + if (!conversationId || !message.id || !message.text) { + return; + } + + logLocal("handleNewMessage", { conversationId, message }); + client.cache.modify({ id: client.cache.identify({ __typename: "conversations", id: conversationId }), fields: { messages(existing = []) { + // Ensure that the `message` object matches the schema const newMessageRef = client.cache.writeFragment({ - data: message, + data: { + __typename: "messages", + id: message.id, + body: message.text, + selectedMedia: message.image_path || [], + imexshopid: message.userid, + status: message.status, + created_at: message.created_at, + read: message.read + }, fragment: gql` fragment NewMessage on messages { id body - createdAt selectedMedia imexshopid + status + created_at + read } ` }); - return [...existing, newMessageRef]; + + // Prevent duplicates by checking if the message already exists + const isDuplicate = existing.some( + (msgRef) => + client.cache.readFragment({ + id: msgRef.__ref, + fragment: gql` + fragment CheckMessage on messages { + id + } + ` + })?.id === message.id + ); + + // We already have it, so return the existing list + if (isDuplicate) { + return existing; + } + + return [...existing, newMessageRef]; // Add the new message reference } } }); diff --git a/client/src/components/chat-archive-button/chat-archive-button.component.jsx b/client/src/components/chat-archive-button/chat-archive-button.component.jsx index 755e8f514..f7ed348c1 100644 --- a/client/src/components/chat-archive-button/chat-archive-button.component.jsx +++ b/client/src/components/chat-archive-button/chat-archive-button.component.jsx @@ -1,21 +1,31 @@ import { useMutation } from "@apollo/client"; import { Button } from "antd"; -import React, { useState } from "react"; +import React, { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries"; +import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; -export default function ChatArchiveButton({ conversation }) { +export default function ChatArchiveButton({ conversation, bodyshop }) { const [loading, setLoading] = useState(false); const { t } = useTranslation(); const [updateConversation] = useMutation(TOGGLE_CONVERSATION_ARCHIVE); + const { socket } = useContext(SocketContext); + const handleToggleArchive = async () => { setLoading(true); - await updateConversation({ - variables: { id: conversation.id, archived: !conversation.archived }, - refetchQueries: ["CONVERSATION_LIST_QUERY"] + const updatedConversation = await updateConversation({ + variables: { id: conversation.id, archived: !conversation.archived } }); + if (socket) { + socket.emit("conversation-modified", { + conversationId: conversation.id, + bodyshopId: bodyshop.id, + archived: updatedConversation.data.update_conversations_by_pk.archived + }); + } + setLoading(false); }; diff --git a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx index 243f699a0..7754ea347 100644 --- a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx +++ b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx @@ -18,7 +18,7 @@ export default function ChatConversationTitle({ conversation, bodyshop }) { bodyshop={bodyshop} /> - + ); } diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index 0fb60aea4..613d96288 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -26,7 +26,6 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { fetchPolicy: "network-only", nextFetchPolicy: "network-only" }); - const { socket } = useContext(SocketContext); useEffect(() => { 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 2ea90322e..e86531ee4 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 @@ -50,10 +50,11 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi }; sendMessage(newMessage); if (socket) { + const lastMessage = conversation.messages?.[conversation.messages.length - 1]; // Get the last message socket.emit("message-added", { conversationId: conversation.id, bodyshopId: bodyshop.id, - message: newMessage + message: lastMessage }); } setSelectedMedia( diff --git a/server/sms/status.js b/server/sms/status.js index bfdeb5ed2..51d2f7ecb 100644 --- a/server/sms/status.js +++ b/server/sms/status.js @@ -15,6 +15,11 @@ exports.status = async (req, res) => { } = req; try { + // Ignore status 'queued' + if (SmsStatus === "queued") { + return res.status(200).json({ message: "Status 'queued' disregarded." }); + } + // Update message status in the database const response = await client.request(queries.UPDATE_MESSAGE_STATUS, { msid: SmsSid, @@ -47,6 +52,7 @@ exports.status = async (req, res) => { warning: "No message returned from the database update." }); } + res.sendStatus(200); } catch (error) { logger.log("sms-status-update-error", "ERROR", "api", null, { diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js index 33faab361..a8e753f2f 100644 --- a/server/web-sockets/redisSocketEvents.js +++ b/server/web-sockets/redisSocketEvents.js @@ -181,7 +181,7 @@ const redisSocketEvents = ({ const messageAdded = ({ bodyshopId, conversationId, message }) => { try { const room = getBodyshopConversationRoom({ bodyshopId, conversationId }); - io.to(room).emit("new-message", message); + io.to(room).emit("new-message", { message, conversationId }); } catch (error) { logger.log("Failed to handle new message", "error", "io-redis", null, { error: error.message,