Merged in feature/IO-3000-messaging-sockets-migrationv2 (pull request #1974)

Feature/IO-3000 messaging sockets migrationv2
This commit is contained in:
Dave Richer
2024-11-28 20:16:43 +00:00
2 changed files with 76 additions and 48 deletions

View File

@@ -1,6 +1,6 @@
// Scripts for firebase and firebase messaging
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging.js");
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging-compat.js");
// Initialize the Firebase app in the service worker by passing the generated config
let firebaseConfig;

View File

@@ -1,10 +1,10 @@
import { useApolloClient, useQuery } from "@apollo/client";
import { gql, useApolloClient, useQuery, useSubscription } from "@apollo/client";
import axios from "axios";
import React, { useCallback, useContext, useEffect, useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import SocketContext from "../../contexts/SocketIO/socketContext";
import { GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
import { GET_CONVERSATION_DETAILS, CONVERSATION_SUBSCRIPTION_BY_PK } from "../../graphql/conversations.queries";
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ChatConversationComponent from "./chat-conversation.component";
@@ -14,11 +14,12 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
export function ChatConversationContainer({ bodyshop, selectedConversation }) {
function ChatConversationContainer({ bodyshop, selectedConversation }) {
const client = useApolloClient();
const { socket } = useContext(SocketContext);
const [markingAsReadInProgress, setMarkingAsReadInProgress] = useState(false);
// Fetch conversation details
const {
loading: convoLoading,
error: convoError,
@@ -29,49 +30,85 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
nextFetchPolicy: "network-only"
});
const updateCacheWithReadMessages = useCallback(
(conversationId, messageIds) => {
if (!conversationId || !messageIds || messageIds.length === 0) return;
// Subscription for conversation updates
useSubscription(CONVERSATION_SUBSCRIPTION_BY_PK, {
skip: socket?.connected,
variables: { conversationId: selectedConversation },
onData: ({ data: subscriptionResult, client }) => {
// Extract the messages array from the result
const messages = subscriptionResult?.data?.messages;
if (!messages || messages.length === 0) {
console.warn("No messages found in subscription result.");
return;
}
// Mark individual messages as read
messageIds.forEach((messageId) => {
messages.forEach((message) => {
const messageRef = client.cache.identify(message);
// Write the new message to the cache
client.cache.writeFragment({
id: messageRef,
fragment: gql`
fragment NewMessage on messages {
id
status
text
isoutbound
image
image_path
userid
created_at
read
}
`,
data: message
});
// Update the conversation cache to include the new message
client.cache.modify({
id: client.cache.identify({ __typename: "messages", id: messageId }),
id: client.cache.identify({ __typename: "conversations", id: selectedConversation }),
fields: {
read() {
return true; // Mark message as read
messages(existingMessages = []) {
const alreadyExists = existingMessages.some((msg) => msg.__ref === messageRef);
if (alreadyExists) return existingMessages;
return [...existingMessages, { __ref: messageRef }];
}
}
});
});
}
});
const updateCacheWithReadMessages = useCallback(
(conversationId, messageIds) => {
if (!conversationId || !messageIds?.length) return;
messageIds.forEach((messageId) => {
client.cache.modify({
id: client.cache.identify({ __typename: "messages", id: messageId }),
fields: {
read: () => true
}
});
});
// Update aggregate unread count for the conversation
client.cache.modify({
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
fields: {
messages_aggregate(existingAggregate) {
return {
...existingAggregate,
aggregate: {
...existingAggregate.aggregate,
count: 0 // No unread messages remaining
}
};
}
unreadcnt: () => 0
}
});
},
[client.cache]
);
// Handle WebSocket events
// WebSocket event handlers
useEffect(() => {
if (!socket || !socket.connected) return;
if (!socket?.connected) return;
const handleConversationChange = (data) => {
if (data.type === "conversation-marked-read") {
const { conversationId, messageIds } = data;
console.log("Conversation change received:", data);
updateCacheWithReadMessages(conversationId, messageIds);
}
};
@@ -81,11 +118,11 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
return () => {
socket.off("conversation-changed", handleConversationChange);
};
}, [socket, client, updateCacheWithReadMessages]);
}, [socket, updateCacheWithReadMessages]);
// Handle joining/leaving conversation
// Join and leave conversation via WebSocket
useEffect(() => {
if (!socket || !socket.connected) return;
if (!socket?.connected || !selectedConversation || !bodyshop?.id) return;
socket.emit("join-bodyshop-conversation", {
bodyshopId: bodyshop.id,
@@ -98,17 +135,14 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
conversationId: selectedConversation
});
};
}, [selectedConversation, bodyshop, socket]);
}, [socket, bodyshop, selectedConversation]);
// Handle marking conversation as read
// Mark conversation as read
const handleMarkConversationAsRead = async () => {
if (!convoData || !selectedConversation || markingAsReadInProgress) return;
if (!convoData || markingAsReadInProgress) return;
const conversation = convoData.conversations_by_pk;
if (!conversation) {
console.warn(`No data found for conversation ID: ${selectedConversation}`);
return;
}
if (!conversation) return;
const unreadMessageIds = conversation.messages
?.filter((message) => !message.read && !message.isoutbound)
@@ -116,22 +150,16 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
if (unreadMessageIds?.length > 0) {
setMarkingAsReadInProgress(true);
try {
const payload = {
await axios.post("/sms/markConversationRead", {
conversation,
imexshopid: bodyshop?.imexshopid,
bodyshopid: bodyshop?.id
};
});
console.log("Marking conversation as read:", payload);
await axios.post("/sms/markConversationRead", payload);
// Update local cache
updateCacheWithReadMessages(selectedConversation, unreadMessageIds);
} catch (error) {
console.error("Error marking conversation as read:", error.response?.data || error.message);
console.error("Error marking conversation as read:", error.message);
} finally {
setMarkingAsReadInProgress(false);
}
@@ -141,11 +169,11 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
return (
<ChatConversationComponent
subState={[convoLoading, convoError]}
conversation={convoData ? convoData.conversations_by_pk : {}}
messages={convoData ? convoData.conversations_by_pk.messages : []}
conversation={convoData?.conversations_by_pk || {}}
messages={convoData?.conversations_by_pk?.messages || []}
handleMarkConversationAsRead={handleMarkConversationAsRead}
/>
);
}
export default connect(mapStateToProps, null)(ChatConversationContainer);
export default connect(mapStateToProps)(ChatConversationContainer);