feature/IO-3000-messaging-sockets-migrations2 -

- sync send
- fix status events

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-11-20 19:23:35 -08:00
parent 06afd6da5b
commit 15151cb4ac
4 changed files with 92 additions and 30 deletions

View File

@@ -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");
};

View File

@@ -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 };

View File

@@ -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, {

View File

@@ -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);