Build and publish Docker images / Build and publish images (push) Successful in 2m48s
170 lines
5.4 KiB
JavaScript
170 lines
5.4 KiB
JavaScript
/**
|
|
* 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.
|
|
*/
|
|
export const getLastMessage = m => {
|
|
const lastMessageIncludingActivity = m.messages[m.messages.length - 1];
|
|
|
|
const nonActivityMessages = m.messages.filter(
|
|
message => message.message_type !== 2
|
|
);
|
|
const lastNonActivityMessageInStore =
|
|
nonActivityMessages[nonActivityMessages.length - 1];
|
|
|
|
const lastNonActivityMessageFromAPI = m.last_non_activity_message;
|
|
|
|
// If API value and store value for last non activity message
|
|
// is empty, then return the last activity message
|
|
if (!lastNonActivityMessageInStore && !lastNonActivityMessageFromAPI) {
|
|
return lastMessageIncludingActivity;
|
|
}
|
|
|
|
return getLastNonActivityMessage(
|
|
lastNonActivityMessageInStore,
|
|
lastNonActivityMessageFromAPI
|
|
);
|
|
};
|
|
|
|
const anonymousContactNames = new Set([
|
|
'',
|
|
'匿名',
|
|
'匿名客户',
|
|
'Anonymous Visitor',
|
|
'商务通访客',
|
|
]);
|
|
|
|
const firstAttribute = (attributes, ...keys) => {
|
|
const value = keys
|
|
.map(key => attributes?.[key])
|
|
.find(item => typeof item === 'string' && item.trim());
|
|
return value?.trim() || '';
|
|
};
|
|
|
|
const contactAttributes = contact => ({
|
|
...(contact?.additional_attributes || {}),
|
|
...(contact?.additionalAttributes || {}),
|
|
...(contact?.custom_attributes || {}),
|
|
...(contact?.customAttributes || {}),
|
|
});
|
|
|
|
const visitorLocation = attributes => {
|
|
const province = firstAttribute(attributes, 'visitor_province');
|
|
const city = firstAttribute(attributes, 'visitor_city');
|
|
if (province || city) {
|
|
if (!city || !province || city === province || city.startsWith(province)) {
|
|
return city || province;
|
|
}
|
|
return `${province}${city}`;
|
|
}
|
|
return firstAttribute(attributes, 'swt_ip_location');
|
|
};
|
|
|
|
const shortConversationCode = conversationId => {
|
|
const value = String(conversationId ?? '').trim();
|
|
if (!/^[1-9]\d*$/.test(value)) {
|
|
return '';
|
|
}
|
|
const numericValue = Number(value);
|
|
return Number.isSafeInteger(numericValue)
|
|
? numericValue.toString(36).toUpperCase()
|
|
: '';
|
|
};
|
|
|
|
/**
|
|
* Adds a stable, compact session code only to generated anonymous names.
|
|
* The stored contact name remains unchanged.
|
|
*/
|
|
export const getConversationContactDisplayName = (contact, conversationId) => {
|
|
const name = String(contact?.name || '').trim();
|
|
const attributes = contactAttributes(contact);
|
|
const source = firstAttribute(attributes, 'visitor_name_source');
|
|
const isGeneratedName =
|
|
anonymousContactNames.has(name) ||
|
|
source === 'ip_geolocation' ||
|
|
source === 'anonymous_fallback';
|
|
|
|
if (!isGeneratedName) {
|
|
return name;
|
|
}
|
|
|
|
const code = shortConversationCode(conversationId);
|
|
if (!code) {
|
|
return name || '匿名';
|
|
}
|
|
|
|
const location =
|
|
visitorLocation(attributes) ||
|
|
(source === 'ip_geolocation' && name.endsWith('客户')
|
|
? name.slice(0, -2)
|
|
: '');
|
|
return location ? `${location}·${code}` : `匿名${code}`;
|
|
};
|
|
|
|
/**
|
|
* 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
|
|
);
|
|
};
|