435 lines
15 KiB
JavaScript
435 lines
15 KiB
JavaScript
import { CONVERSATION_LIST_QUERY, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
|
|
import { gql } from "@apollo/client";
|
|
|
|
const logLocal = (message, ...args) => {
|
|
if (import.meta.env.VITE_APP_IS_TEST || !import.meta.env.PROD) {
|
|
console.log(`==================== ${message} ====================`);
|
|
console.dir({ ...args });
|
|
}
|
|
};
|
|
|
|
// Utility function to enrich conversation data
|
|
const enrichConversation = (conversation, isOutbound) => ({
|
|
...conversation,
|
|
updated_at: conversation.updated_at || new Date().toISOString(),
|
|
unreadcnt: conversation.unreadcnt || 0,
|
|
archived: conversation.archived || false,
|
|
label: conversation.label || null,
|
|
job_conversations: conversation.job_conversations || [],
|
|
messages_aggregate: conversation.messages_aggregate || {
|
|
__typename: "messages_aggregate",
|
|
aggregate: {
|
|
__typename: "messages_aggregate_fields",
|
|
count: isOutbound ? 0 : 1
|
|
}
|
|
},
|
|
__typename: "conversations"
|
|
});
|
|
|
|
export const registerMessagingHandlers = ({ socket, client }) => {
|
|
if (!(socket && client)) return;
|
|
|
|
const handleNewMessageSummary = async (message) => {
|
|
const { conversationId, newConversation, existingConversation, isoutbound } = message;
|
|
|
|
logLocal("handleNewMessageSummary - Start", { message, isNew: !existingConversation });
|
|
|
|
const queryVariables = { offset: 0 };
|
|
|
|
if (!existingConversation && conversationId) {
|
|
// Attempt to read from the cache to determine if this is actually a new conversation
|
|
try {
|
|
const cachedConversation = client.cache.readFragment({
|
|
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
|
fragment: gql`
|
|
fragment ExistingConversationCheck on conversations {
|
|
id
|
|
}
|
|
`
|
|
});
|
|
|
|
if (cachedConversation) {
|
|
logLocal("handleNewMessageSummary - Existing Conversation inferred from cache", {
|
|
conversationId
|
|
});
|
|
return handleNewMessageSummary({
|
|
...message,
|
|
existingConversation: true
|
|
});
|
|
}
|
|
} catch (error) {
|
|
logLocal("handleNewMessageSummary - Cache miss", { conversationId });
|
|
}
|
|
}
|
|
|
|
// Handle new conversation
|
|
if (!existingConversation && newConversation?.phone_num) {
|
|
logLocal("handleNewMessageSummary - New Conversation", newConversation);
|
|
|
|
try {
|
|
const queryResults = client.cache.readQuery({
|
|
query: CONVERSATION_LIST_QUERY,
|
|
variables: queryVariables
|
|
});
|
|
|
|
const existingConversations = queryResults?.conversations || [];
|
|
const enrichedConversation = enrichConversation(newConversation, isoutbound);
|
|
|
|
if (!existingConversations.some((conv) => conv.id === enrichedConversation.id)) {
|
|
client.cache.modify({
|
|
id: "ROOT_QUERY",
|
|
fields: {
|
|
conversations(existingConversations = []) {
|
|
return [enrichedConversation, ...existingConversations];
|
|
}
|
|
}
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("Error updating cache for new conversation:", error);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Handle existing conversation
|
|
if (existingConversation) {
|
|
try {
|
|
client.cache.modify({
|
|
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
|
fields: {
|
|
updated_at: () => new Date().toISOString(),
|
|
archived: () => false,
|
|
messages_aggregate(cached = { aggregate: { count: 0 } }) {
|
|
const currentCount = cached.aggregate?.count || 0;
|
|
if (!isoutbound) {
|
|
return {
|
|
__typename: "messages_aggregate",
|
|
aggregate: {
|
|
__typename: "messages_aggregate_fields",
|
|
count: currentCount + 1
|
|
}
|
|
};
|
|
}
|
|
return cached;
|
|
}
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error("Error updating cache for existing conversation:", error);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
logLocal("New Conversation Summary finished without work", { message });
|
|
};
|
|
|
|
const handleNewMessageDetailed = (message) => {
|
|
const { conversationId, newMessage } = message;
|
|
|
|
logLocal("handleNewMessageDetailed - Start", message);
|
|
|
|
try {
|
|
// Check if the conversation exists in the cache
|
|
const queryResults = client.cache.readQuery({
|
|
query: GET_CONVERSATION_DETAILS,
|
|
variables: { conversationId }
|
|
});
|
|
|
|
if (!queryResults?.conversations_by_pk) {
|
|
console.warn("Conversation not found in cache:", { conversationId });
|
|
return;
|
|
}
|
|
|
|
// Append the new message to the conversation's message list using cache.modify
|
|
client.cache.modify({
|
|
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
|
fields: {
|
|
messages(existingMessages = []) {
|
|
return [...existingMessages, newMessage];
|
|
}
|
|
}
|
|
});
|
|
|
|
logLocal("handleNewMessageDetailed - Message appended successfully", {
|
|
conversationId,
|
|
newMessage
|
|
});
|
|
} catch (error) {
|
|
console.error("Error updating conversation messages in cache:", error);
|
|
}
|
|
};
|
|
|
|
const handleMessageChanged = (message) => {
|
|
if (!message) {
|
|
logLocal("handleMessageChanged - No message provided", message);
|
|
return;
|
|
}
|
|
|
|
logLocal("handleMessageChanged - Start", message);
|
|
|
|
try {
|
|
client.cache.modify({
|
|
id: client.cache.identify({ __typename: "conversations", id: message.conversationid }),
|
|
fields: {
|
|
messages(existingMessages = [], { readField }) {
|
|
return existingMessages.map((messageRef) => {
|
|
// Check if this is the message to update
|
|
if (readField("id", messageRef) === message.id) {
|
|
const currentStatus = readField("status", messageRef);
|
|
|
|
// Handle known types of message changes
|
|
switch (message.type) {
|
|
case "status-changed":
|
|
// Prevent overwriting if the current status is already "delivered"
|
|
if (currentStatus === "delivered") {
|
|
logLocal("handleMessageChanged - Status already delivered, skipping update", {
|
|
messageId: message.id
|
|
});
|
|
return messageRef;
|
|
}
|
|
|
|
// Update the status field
|
|
return {
|
|
...messageRef,
|
|
status: message.status
|
|
};
|
|
|
|
case "text-updated":
|
|
// Handle changes to the message text
|
|
return {
|
|
...messageRef,
|
|
text: message.text
|
|
};
|
|
|
|
// Add cases for other known message types as needed
|
|
|
|
default:
|
|
// Log a warning for unhandled message types
|
|
logLocal("handleMessageChanged - Unhandled message type", { type: message.type });
|
|
return messageRef;
|
|
}
|
|
}
|
|
|
|
return messageRef; // Keep other messages unchanged
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
logLocal("handleMessageChanged - Message updated successfully", {
|
|
messageId: message.id,
|
|
type: message.type
|
|
});
|
|
} catch (error) {
|
|
console.error("handleMessageChanged - Error modifying cache:", error);
|
|
}
|
|
};
|
|
|
|
const handleConversationChanged = async (data) => {
|
|
if (!data) {
|
|
logLocal("handleConversationChanged - No data provided", data);
|
|
return;
|
|
}
|
|
|
|
const { conversationId, type, job_conversations, messageIds, ...fields } = data;
|
|
logLocal("handleConversationChanged - Start", data);
|
|
|
|
const updatedAt = new Date().toISOString();
|
|
|
|
const updateConversationList = (newConversation) => {
|
|
try {
|
|
const existingList = client.cache.readQuery({
|
|
query: CONVERSATION_LIST_QUERY,
|
|
variables: { offset: 0 }
|
|
});
|
|
|
|
const updatedList = existingList?.conversations
|
|
? [
|
|
newConversation,
|
|
...existingList.conversations.filter((conv) => conv.id !== newConversation.id) // Prevent duplicates
|
|
]
|
|
: [newConversation];
|
|
|
|
client.cache.writeQuery({
|
|
query: CONVERSATION_LIST_QUERY,
|
|
variables: { offset: 0 },
|
|
data: {
|
|
conversations: updatedList
|
|
}
|
|
});
|
|
|
|
logLocal("handleConversationChanged - Conversation list updated successfully", newConversation);
|
|
} catch (error) {
|
|
console.error("Error updating conversation list in the cache:", error);
|
|
}
|
|
};
|
|
|
|
// Handle specific types
|
|
try {
|
|
switch (type) {
|
|
case "conversation-marked-read":
|
|
if (conversationId && messageIds?.length > 0) {
|
|
client.cache.modify({
|
|
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
|
fields: {
|
|
messages(existingMessages = [], { readField }) {
|
|
return existingMessages.map((message) => {
|
|
if (messageIds.includes(readField("id", message))) {
|
|
return { ...message, read: true };
|
|
}
|
|
return message;
|
|
});
|
|
},
|
|
messages_aggregate: () => ({
|
|
__typename: "messages_aggregate",
|
|
aggregate: { __typename: "messages_aggregate_fields", count: 0 }
|
|
})
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
|
|
case "conversation-created":
|
|
updateConversationList({ ...fields, job_conversations, updated_at: updatedAt });
|
|
break;
|
|
|
|
case "conversation-unarchived":
|
|
case "conversation-archived":
|
|
// Would like to someday figure out how to get this working without refetch queries,
|
|
// But I have but a solid 4 hours into it, and there are just too many weird occurrences
|
|
try {
|
|
const listQueryVariables = { offset: 0 };
|
|
const detailsQueryVariables = { conversationId };
|
|
|
|
// Check if conversation details exist in the cache
|
|
const detailsExist = !!client.cache.readQuery({
|
|
query: GET_CONVERSATION_DETAILS,
|
|
variables: detailsQueryVariables
|
|
});
|
|
|
|
// Refetch conversation list
|
|
await client.refetchQueries({
|
|
include: [CONVERSATION_LIST_QUERY, ...(detailsExist ? [GET_CONVERSATION_DETAILS] : [])],
|
|
variables: [
|
|
{ query: CONVERSATION_LIST_QUERY, variables: listQueryVariables },
|
|
...(detailsExist
|
|
? [
|
|
{
|
|
query: GET_CONVERSATION_DETAILS,
|
|
variables: detailsQueryVariables
|
|
}
|
|
]
|
|
: [])
|
|
]
|
|
});
|
|
|
|
logLocal("handleConversationChanged - Refetched queries after state change", {
|
|
conversationId,
|
|
type
|
|
});
|
|
} catch (error) {
|
|
console.error("Error refetching queries after conversation state change:", error);
|
|
}
|
|
break;
|
|
|
|
case "tag-added":
|
|
// Ensure `job_conversations` is properly formatted
|
|
const formattedJobConversations = job_conversations.map((jc) => ({
|
|
__typename: "job_conversations",
|
|
jobid: jc.jobid || jc.job?.id,
|
|
conversationid: conversationId,
|
|
job: jc.job || {
|
|
__typename: "jobs",
|
|
id: data.selectedJob.id,
|
|
ro_number: data.selectedJob.ro_number,
|
|
ownr_co_nm: data.selectedJob.ownr_co_nm,
|
|
ownr_fn: data.selectedJob.ownr_fn,
|
|
ownr_ln: data.selectedJob.ownr_ln
|
|
}
|
|
}));
|
|
|
|
client.cache.modify({
|
|
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
|
fields: {
|
|
job_conversations: (existing = []) => {
|
|
// Ensure no duplicates based on both `conversationid` and `jobid`
|
|
const existingLinks = new Set(
|
|
existing.map((jc) => {
|
|
const jobId = client.cache.readFragment({
|
|
id: client.cache.identify(jc),
|
|
fragment: gql`
|
|
fragment JobConversationLinkAdded on job_conversations {
|
|
jobid
|
|
conversationid
|
|
}
|
|
`
|
|
})?.jobid;
|
|
return `${jobId}:${conversationId}`; // Unique identifier for a job-conversation link
|
|
})
|
|
);
|
|
|
|
const newItems = formattedJobConversations.filter((jc) => {
|
|
const uniqueLink = `${jc.jobid}:${jc.conversationid}`;
|
|
return !existingLinks.has(uniqueLink);
|
|
});
|
|
|
|
return [...existing, ...newItems];
|
|
}
|
|
}
|
|
});
|
|
|
|
break;
|
|
|
|
case "tag-removed":
|
|
try {
|
|
const conversationCacheId = client.cache.identify({ __typename: "conversations", id: conversationId });
|
|
|
|
// Evict the specific cache entry for job_conversations
|
|
client.cache.evict({
|
|
id: conversationCacheId,
|
|
fieldName: "job_conversations"
|
|
});
|
|
|
|
// Garbage collect evicted entries
|
|
client.cache.gc();
|
|
|
|
logLocal("handleConversationChanged - tag removed - Refetched conversation list after state change", {
|
|
conversationId,
|
|
type
|
|
});
|
|
} catch (error) {
|
|
console.error("Error refetching queries after conversation state change: (Tag Removed)", error);
|
|
}
|
|
|
|
break;
|
|
default:
|
|
logLocal("handleConversationChanged - Unhandled type", { type });
|
|
client.cache.modify({
|
|
id: client.cache.identify({ __typename: "conversations", id: conversationId }),
|
|
fields: {
|
|
...Object.fromEntries(
|
|
Object.entries(fields).map(([key, value]) => [key, (cached) => (value !== undefined ? value : cached)])
|
|
)
|
|
}
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("Error handling conversation changes:", { type, error });
|
|
}
|
|
};
|
|
|
|
socket.on("new-message-summary", handleNewMessageSummary);
|
|
socket.on("new-message-detailed", handleNewMessageDetailed);
|
|
socket.on("message-changed", handleMessageChanged);
|
|
socket.on("conversation-changed", handleConversationChanged);
|
|
};
|
|
|
|
export const unregisterMessagingHandlers = ({ socket }) => {
|
|
if (!socket) return;
|
|
socket.off("new-message-summary");
|
|
socket.off("new-message-detailed");
|
|
socket.off("message-changed");
|
|
socket.off("conversation-changed");
|
|
};
|