/** * Determines the last non-activity message between store and API messages. * @param {Object} messageInStore - The last non-activity message from the store. * @param {Object} messageFromAPI - The last non-activity message from the API. * @returns {Object} The latest non-activity message. */ const getLastNonActivityMessage = (messageInStore, messageFromAPI) => { // If both API value and store value for last non activity message // are available, then return the latest one. if (messageInStore && messageFromAPI) { return messageInStore.created_at >= messageFromAPI.created_at ? messageInStore : messageFromAPI; } // Otherwise, return whichever is available return messageInStore || messageFromAPI; }; /** * Filters out duplicate source messages from an array of messages. * @param {Array} messages - The array of messages to filter. * @returns {Array} An array of messages without duplicates. */ export const filterDuplicateSourceMessages = (messages = []) => { const messagesWithoutDuplicates = []; // We cannot use Map or any short hand method as it returns the last message with the duplicate ID // We should return the message with smaller id when there is a duplicate messages.forEach(m1 => { if (m1.source_id) { const index = messagesWithoutDuplicates.findIndex( m2 => m1.source_id === m2.source_id ); if (index < 0) { messagesWithoutDuplicates.push(m1); } } else { messagesWithoutDuplicates.push(m1); } }); return messagesWithoutDuplicates; }; /** * Retrieves the last message from a conversation, prioritizing non-activity messages. * @param {Object} m - The conversation object containing messages. * @returns {Object} The last message of the conversation. */ // A reaction whose user-facing state is invisible (toggled off / blank). // Treated as non-existent for chat list preview purposes — otherwise the // preview would render " reagiu <>" or fall back to "no content" // even when there's a real previous message we could show. const isRemovedReaction = message => message?.content_attributes?.is_reaction && (message?.content_attributes?.deleted || !message?.content); export const getLastMessage = m => { // Drop removed reactions up-front so neither the activity fallback nor the // visible-messages list ever picks a blank "X reagiu " row. const nonRemovedMessages = m.messages.filter( message => !isRemovedReaction(message) ); const lastMessageIncludingActivity = nonRemovedMessages[nonRemovedMessages.length - 1]; const visibleMessages = nonRemovedMessages.filter( message => message.message_type !== 2 ); const lastNonActivityMessageInStore = visibleMessages[visibleMessages.length - 1]; // ADD_MESSAGE mutates `chat.messages` in place but never touches // `last_non_activity_message`, so the API field can hold a stale snapshot of // a message that has since been updated (eg. a reaction toggled off). When // the same id is present in the store, merge the fresher store fields onto // the API snapshot — replacing would strip jbuilder-only fields like // `in_reply_to_snippet` that aren't present on push_event_data. const apiCandidate = m.last_non_activity_message; const apiId = apiCandidate?.id; const storeVersion = apiId ? m.messages.find(msg => msg.id === apiId) : null; const refreshedApiCandidate = storeVersion ? { ...apiCandidate, ...storeVersion } : apiCandidate; const lastNonActivityMessageFromAPI = isRemovedReaction(refreshedApiCandidate) ? null : refreshedApiCandidate; // If API value and store value for last non activity message // is empty, then return the last activity message if (!lastNonActivityMessageInStore && !lastNonActivityMessageFromAPI) { return lastMessageIncludingActivity || null; } return getLastNonActivityMessage( lastNonActivityMessageInStore, lastNonActivityMessageFromAPI ); }; /** * Filters messages that have been read by the agent. * @param {Array} messages - The array of messages to filter. * @param {number} agentLastSeenAt - The timestamp of when the agent last saw the messages. * @returns {Array} An array of read messages. */ export const getReadMessages = (messages, agentLastSeenAt) => { return messages.filter( message => message.created_at * 1000 <= agentLastSeenAt * 1000 ); }; /** * Filters messages that have not been read by the agent. * @param {Array} messages - The array of messages to filter. * @param {number} agentLastSeenAt - The timestamp of when the agent last saw the messages. * @returns {Array} An array of unread messages. */ export const getUnreadMessages = (messages, agentLastSeenAt) => { return messages.filter( message => message.created_at * 1000 > agentLastSeenAt * 1000 ); };