feature/IO-3000-messaging-sockets-migrations2 -
- sync send - fix status events Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
|
import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
|
||||||
|
import { gql } from "@apollo/client";
|
||||||
|
|
||||||
export const registerMessagingHandlers = ({ socket, client }) => {
|
export const registerMessagingHandlers = ({ socket, client }) => {
|
||||||
if (!(socket && client)) return;
|
if (!(socket && client)) return;
|
||||||
@@ -14,8 +15,6 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
|||||||
|
|
||||||
const fullConversation = {
|
const fullConversation = {
|
||||||
...newConversation,
|
...newConversation,
|
||||||
phone_num: newConversation.phone_num,
|
|
||||||
id: newConversation.id,
|
|
||||||
updated_at: newConversation.updated_at || new Date().toISOString(),
|
updated_at: newConversation.updated_at || new Date().toISOString(),
|
||||||
unreadcnt: newConversation.unreadcnt || 0,
|
unreadcnt: newConversation.unreadcnt || 0,
|
||||||
archived: newConversation.archived || false,
|
archived: newConversation.archived || false,
|
||||||
@@ -77,21 +76,43 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMessageChanged = (message) => {
|
const handleMessageChanged = (message) => {
|
||||||
// Find the message in the cache and update all fields dynamically
|
|
||||||
client.cache.modify({
|
client.cache.modify({
|
||||||
id: client.cache.identify({
|
id: client.cache.identify({ __typename: "conversations", id: message.conversationid }),
|
||||||
__typename: "messages",
|
|
||||||
id: message.id
|
|
||||||
}),
|
|
||||||
fields: {
|
fields: {
|
||||||
// Dynamically update all fields based on the incoming message object
|
...(message.type === "status-changed" && {
|
||||||
__typename: (existingType) => existingType || "messages", // Ensure __typename is preserved
|
messages(existing = [], { readField }) {
|
||||||
...Object.fromEntries(
|
return existing.map((messageRef) => {
|
||||||
Object.entries(message).map(([key, value]) => [
|
// Match the message by ID
|
||||||
key,
|
if (readField("id", messageRef) === message.id) {
|
||||||
(cached) => (value !== undefined ? value : cached) // Update with new value or keep existing
|
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({
|
client.cache.modify({
|
||||||
id: cacheId,
|
id: cacheId,
|
||||||
fields: {
|
fields: {
|
||||||
|
// This is a catch-all for just sending it fields off conversation
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
Object.entries(fields).map(([key, value]) => [
|
Object.entries(fields).map(([key, value]) => [
|
||||||
key,
|
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-summary", handleNewMessageSummary);
|
||||||
socket.on("new-message-detailed", handleNewMessageDetailed);
|
socket.on("new-message-detailed", handleNewMessageDetailed);
|
||||||
socket.on("message-changed", handleMessageChanged);
|
socket.on("message-changed", handleMessageChanged);
|
||||||
@@ -146,9 +192,9 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
|||||||
|
|
||||||
export const unregisterMessagingHandlers = ({ socket }) => {
|
export const unregisterMessagingHandlers = ({ socket }) => {
|
||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
|
socket.off("new-message");
|
||||||
socket.off("new-message-summary");
|
socket.off("new-message-summary");
|
||||||
socket.off("new-message-detailed");
|
socket.off("new-message-detailed");
|
||||||
socket.off("message-changed");
|
socket.off("message-changed");
|
||||||
socket.off("message-changed");
|
|
||||||
socket.off("conversation-changed");
|
socket.off("conversation-changed");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { LoadingOutlined, SendOutlined } from "@ant-design/icons";
|
import { LoadingOutlined, SendOutlined } from "@ant-design/icons";
|
||||||
import { Input, Spin } from "antd";
|
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 { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -10,6 +10,7 @@ import { selectIsSending, selectMessage } from "../../redux/messaging/messaging.
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component";
|
import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component";
|
||||||
import ChatPresetsComponent from "../chat-presets/chat-presets.component";
|
import ChatPresetsComponent from "../chat-presets/chat-presets.component";
|
||||||
|
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -25,6 +26,8 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSending, message, setMessage }) {
|
function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSending, message, setMessage }) {
|
||||||
const inputArea = useRef(null);
|
const inputArea = useRef(null);
|
||||||
const [selectedMedia, setSelectedMedia] = useState([]);
|
const [selectedMedia, setSelectedMedia] = useState([]);
|
||||||
|
const { socket } = useContext(SocketContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
inputArea.current.focus();
|
inputArea.current.focus();
|
||||||
}, [isSending, setMessage]);
|
}, [isSending, setMessage]);
|
||||||
@@ -37,14 +40,22 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi
|
|||||||
logImEXEvent("messaging_send_message");
|
logImEXEvent("messaging_send_message");
|
||||||
|
|
||||||
if (selectedImages.length < 11) {
|
if (selectedImages.length < 11) {
|
||||||
sendMessage({
|
const newMessage = {
|
||||||
to: conversation.phone_num,
|
to: conversation.phone_num,
|
||||||
body: message || "",
|
body: message || "",
|
||||||
messagingServiceSid: bodyshop.messagingservicesid,
|
messagingServiceSid: bodyshop.messagingservicesid,
|
||||||
conversationid: conversation.id,
|
conversationid: conversation.id,
|
||||||
selectedMedia: selectedImages,
|
selectedMedia: selectedImages,
|
||||||
imexshopid: bodyshop.imexshopid
|
imexshopid: bodyshop.imexshopid
|
||||||
});
|
};
|
||||||
|
sendMessage(newMessage);
|
||||||
|
if (socket) {
|
||||||
|
socket.emit("message-added", {
|
||||||
|
conversationId: conversation.id,
|
||||||
|
bodyshopId: bodyshop.id,
|
||||||
|
message: newMessage
|
||||||
|
});
|
||||||
|
}
|
||||||
setSelectedMedia(
|
setSelectedMedia(
|
||||||
selectedMedia.map((i) => {
|
selectedMedia.map((i) => {
|
||||||
return { ...i, isSelected: false };
|
return { ...i, isSelected: false };
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ exports.status = async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ioRedis.to(conversationRoom).emit("message-changed", {
|
ioRedis.to(conversationRoom).emit("message-changed", {
|
||||||
message
|
...message,
|
||||||
|
status: SmsStatus,
|
||||||
|
type: "status-changed"
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
logger.log("sms-status-update-warning", "WARN", "api", null, {
|
logger.log("sms-status-update-warning", "WARN", "api", null, {
|
||||||
@@ -80,21 +82,11 @@ exports.markConversationRead = async (req, res) => {
|
|||||||
|
|
||||||
const broadcastRoom = getBodyshopRoom(bodyshopid);
|
const broadcastRoom = getBodyshopRoom(bodyshopid);
|
||||||
|
|
||||||
const conversationRoom = getBodyshopConversationRoom({
|
|
||||||
bodyshopId: bodyshopid,
|
|
||||||
conversationId: conversationid
|
|
||||||
});
|
|
||||||
|
|
||||||
ioRedis.to(broadcastRoom).emit("conversation-changed", {
|
ioRedis.to(broadcastRoom).emit("conversation-changed", {
|
||||||
type: "conversation-marked-read",
|
type: "conversation-marked-read",
|
||||||
conversationId: conversationid
|
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." });
|
res.status(200).json({ success: true, message: "Conversation marked as read." });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("conversation-mark-read-error", "ERROR", "api", null, {
|
logger.log("conversation-mark-read-error", "ERROR", "api", null, {
|
||||||
|
|||||||
@@ -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("conversation-modified", conversationModified);
|
||||||
socket.on("join-bodyshop-conversation", joinConversationRoom);
|
socket.on("join-bodyshop-conversation", joinConversationRoom);
|
||||||
socket.on("leave-bodyshop-conversation", leaveConversationRoom);
|
socket.on("leave-bodyshop-conversation", leaveConversationRoom);
|
||||||
|
|||||||
Reference in New Issue
Block a user