forked from mirrors/amputoma-fe
226 lines
6.1 KiB
JavaScript
226 lines
6.1 KiB
JavaScript
import _ from 'lodash'
|
|
|
|
const empty = (chatId) => {
|
|
return {
|
|
idIndex: {},
|
|
idempotencyKeyIndex: {},
|
|
messages: [],
|
|
newMessageCount: 0,
|
|
lastSeenMessageId: '0',
|
|
chatId: chatId,
|
|
minId: undefined,
|
|
maxId: undefined
|
|
}
|
|
}
|
|
|
|
const clear = (storage) => {
|
|
const failedMessageIds = []
|
|
|
|
for (const message of storage.messages) {
|
|
if (message.error) {
|
|
failedMessageIds.push(message.id)
|
|
} else {
|
|
delete storage.idIndex[message.id]
|
|
delete storage.idempotencyKeyIndex[message.idempotency_key]
|
|
}
|
|
}
|
|
|
|
storage.messages = storage.messages.filter(m => failedMessageIds.includes(m.id))
|
|
storage.newMessageCount = 0
|
|
storage.lastSeenMessageId = '0'
|
|
storage.minId = undefined
|
|
storage.maxId = undefined
|
|
}
|
|
|
|
const deleteMessage = (storage, messageId) => {
|
|
if (!storage) { return }
|
|
storage.messages = storage.messages.filter(m => m.id !== messageId)
|
|
delete storage.idIndex[messageId]
|
|
|
|
if (storage.maxId === messageId) {
|
|
const lastMessage = _.maxBy(storage.messages, 'id')
|
|
storage.maxId = lastMessage.id
|
|
}
|
|
|
|
if (storage.minId === messageId) {
|
|
const firstMessage = _.minBy(storage.messages, 'id')
|
|
storage.minId = firstMessage.id
|
|
}
|
|
}
|
|
|
|
const cullOlderMessages = (storage) => {
|
|
const maxIndex = storage.messages.length
|
|
const minIndex = maxIndex - 50
|
|
if (maxIndex <= 50) return
|
|
|
|
storage.messages = _.sortBy(storage.messages, ['id'])
|
|
storage.minId = storage.messages[minIndex].id
|
|
for (const message of storage.messages) {
|
|
if (message.id < storage.minId) {
|
|
delete storage.idIndex[message.id]
|
|
delete storage.idempotencyKeyIndex[message.idempotency_key]
|
|
}
|
|
}
|
|
storage.messages = storage.messages.slice(minIndex, maxIndex)
|
|
}
|
|
|
|
const handleMessageError = (storage, fakeId, isRetry) => {
|
|
if (!storage) { return }
|
|
const fakeMessage = storage.idIndex[fakeId]
|
|
if (fakeMessage) {
|
|
fakeMessage.error = true
|
|
fakeMessage.pending = false
|
|
if (!isRetry) {
|
|
// Ensure the failed message doesn't stay at the bottom of the list.
|
|
const lastPersistedMessage = _.orderBy(storage.messages, ['pending', 'id'], ['asc', 'desc'])[0]
|
|
if (lastPersistedMessage) {
|
|
const oldId = fakeMessage.id
|
|
fakeMessage.id = `${lastPersistedMessage.id}-${new Date().getTime()}`
|
|
storage.idIndex[fakeMessage.id] = fakeMessage
|
|
delete storage.idIndex[oldId]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const add = (storage, { messages: newMessages, updateMaxId = true }) => {
|
|
if (!storage) { return }
|
|
for (let i = 0; i < newMessages.length; i++) {
|
|
const message = newMessages[i]
|
|
|
|
// sanity check
|
|
if (message.chat_id !== storage.chatId) { return }
|
|
|
|
if (message.fakeId) {
|
|
const fakeMessage = storage.idIndex[message.fakeId]
|
|
if (fakeMessage) {
|
|
// In case the same id exists (chat update before POST response)
|
|
// make sure to remove the older duplicate message.
|
|
if (storage.idIndex[message.id]) {
|
|
delete storage.idIndex[message.id]
|
|
storage.messages = storage.messages.filter(msg => msg.id !== message.id)
|
|
}
|
|
Object.assign(fakeMessage, message, { error: false })
|
|
delete fakeMessage['fakeId']
|
|
storage.idIndex[fakeMessage.id] = fakeMessage
|
|
delete storage.idIndex[message.fakeId]
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
if (!storage.minId || (!message.pending && message.id < storage.minId)) {
|
|
storage.minId = message.id
|
|
}
|
|
|
|
if (!storage.maxId || message.id > storage.maxId) {
|
|
if (updateMaxId) {
|
|
storage.maxId = message.id
|
|
}
|
|
}
|
|
|
|
if (!storage.idIndex[message.id] && !isConfirmation(storage, message)) {
|
|
if (storage.lastSeenMessageId < message.id) {
|
|
storage.newMessageCount++
|
|
}
|
|
storage.idIndex[message.id] = message
|
|
storage.messages.push(storage.idIndex[message.id])
|
|
storage.idempotencyKeyIndex[message.idempotency_key] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
const isConfirmation = (storage, message) => {
|
|
if (!message.idempotency_key) return
|
|
return storage.idempotencyKeyIndex[message.idempotency_key]
|
|
}
|
|
|
|
const resetNewMessageCount = (storage) => {
|
|
if (!storage) { return }
|
|
storage.newMessageCount = 0
|
|
storage.lastSeenMessageId = storage.maxId
|
|
}
|
|
|
|
// Inserts date separators and marks the head and tail if it's the chain of messages made by the same user
|
|
const getView = (storage) => {
|
|
if (!storage) { return [] }
|
|
|
|
const result = []
|
|
const messages = _.orderBy(storage.messages, ['pending', 'id'], ['asc', 'asc'])
|
|
const firstMessage = messages[0]
|
|
let previousMessage = messages[messages.length - 1]
|
|
let currentMessageChainId
|
|
|
|
if (firstMessage) {
|
|
const date = new Date(firstMessage.created_at)
|
|
date.setHours(0, 0, 0, 0)
|
|
result.push({
|
|
type: 'date',
|
|
date,
|
|
id: date.getTime().toString()
|
|
})
|
|
}
|
|
|
|
let afterDate = false
|
|
|
|
for (let i = 0; i < messages.length; i++) {
|
|
const message = messages[i]
|
|
const nextMessage = messages[i + 1]
|
|
|
|
const date = new Date(message.created_at)
|
|
date.setHours(0, 0, 0, 0)
|
|
|
|
// insert date separator and start a new message chain
|
|
if (previousMessage && previousMessage.date < date) {
|
|
result.push({
|
|
type: 'date',
|
|
date,
|
|
id: date.getTime().toString()
|
|
})
|
|
|
|
previousMessage['isTail'] = true
|
|
currentMessageChainId = undefined
|
|
afterDate = true
|
|
}
|
|
|
|
const object = {
|
|
type: 'message',
|
|
data: message,
|
|
date,
|
|
id: message.id,
|
|
messageChainId: currentMessageChainId
|
|
}
|
|
|
|
// end a message chian
|
|
if ((nextMessage && nextMessage.account_id) !== message.account_id) {
|
|
object['isTail'] = true
|
|
currentMessageChainId = undefined
|
|
}
|
|
|
|
// start a new message chain
|
|
if ((previousMessage && previousMessage.data && previousMessage.data.account_id) !== message.account_id || afterDate) {
|
|
currentMessageChainId = _.uniqueId()
|
|
object['isHead'] = true
|
|
object['messageChainId'] = currentMessageChainId
|
|
}
|
|
|
|
result.push(object)
|
|
previousMessage = object
|
|
afterDate = false
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
const ChatService = {
|
|
add,
|
|
empty,
|
|
getView,
|
|
deleteMessage,
|
|
cullOlderMessages,
|
|
resetNewMessageCount,
|
|
clear,
|
|
handleMessageError
|
|
}
|
|
|
|
export default ChatService
|