pleroma-fe/src/services/chat_service/chat_service.js

255 lines
6.2 KiB
JavaScript
Raw Normal View History

2026-06-03 01:56:18 +03:00
import { maxBy, minBy } from 'lodash'
2020-05-07 16:10:53 +03:00
const empty = (chatId) => {
return {
idIndex: {},
2020-10-29 13:33:06 +03:00
idempotencyKeyIndex: {},
2020-05-07 16:10:53 +03:00
messages: [],
newMessageCount: 0,
lastSeenMessageId: '0',
2022-07-31 12:35:48 +03:00
chatId,
2020-05-07 16:10:53 +03:00
minId: undefined,
2026-01-06 16:22:52 +02:00
maxId: undefined,
2020-05-07 16:10:53 +03:00
}
}
const clear = (storage) => {
2020-10-29 13:33:06 +03:00
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]
2020-10-29 13:33:06 +03:00
}
}
2026-01-06 16:22:52 +02:00
storage.messages = storage.messages.filter((m) =>
failedMessageIds.includes(m.id),
)
2020-05-07 16:10:53 +03:00
storage.newMessageCount = 0
storage.lastSeenMessageId = '0'
2020-05-07 16:10:53 +03:00
storage.minId = undefined
storage.maxId = undefined
2020-05-07 16:10:53 +03:00
}
const deleteMessage = (storage, messageId) => {
2026-01-06 16:22:52 +02:00
if (!storage) {
return
}
storage.messages = storage.messages.filter((m) => m.id !== messageId)
2020-05-07 16:10:53 +03:00
delete storage.idIndex[messageId]
if (storage.maxId === messageId) {
2026-06-03 01:56:18 +03:00
const lastMessage = maxBy(storage.messages, 'id')
storage.maxId = lastMessage.id
2020-05-07 16:10:53 +03:00
}
if (storage.minId === messageId) {
2026-06-03 01:56:18 +03:00
const firstMessage = minBy(storage.messages, 'id')
storage.minId = firstMessage.id
2020-05-07 16:10:53 +03:00
}
}
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)
}
2020-10-29 13:33:06 +03:00
const handleMessageError = (storage, fakeId, isRetry) => {
2026-01-06 16:22:52 +02:00
if (!storage) {
return
}
2020-10-29 13:33:06 +03:00
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.
2026-01-06 16:22:52 +02:00
const lastPersistedMessage = _.orderBy(
storage.messages,
['pending', 'id'],
['asc', 'desc'],
)[0]
2020-10-29 13:33:06 +03:00
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 }) => {
2026-01-06 16:22:52 +02:00
if (!storage) {
return
}
2020-05-07 16:10:53 +03:00
for (let i = 0; i < newMessages.length; i++) {
const message = newMessages[i]
// sanity check
2026-01-06 16:22:52 +02:00
if (message.chat_id !== storage.chatId) {
return
}
2020-05-07 16:10:53 +03:00
2020-10-29 13:33:06 +03:00
if (message.fakeId) {
const fakeMessage = storage.idIndex[message.fakeId]
if (fakeMessage) {
2020-11-01 14:25:02 +00:00
// 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]
2026-01-06 16:22:52 +02:00
storage.messages = storage.messages.filter(
(msg) => msg.id !== message.id,
)
2020-11-01 14:25:02 +00:00
}
2020-10-29 13:33:06 +03:00
Object.assign(fakeMessage, message, { error: false })
2022-07-31 12:35:48 +03:00
delete fakeMessage.fakeId
2020-10-29 13:33:06 +03:00
storage.idIndex[fakeMessage.id] = fakeMessage
delete storage.idIndex[message.fakeId]
return
}
}
if (!storage.minId || (!message.pending && message.id < storage.minId)) {
2020-05-07 16:10:53 +03:00
storage.minId = message.id
}
if (!storage.maxId || message.id > storage.maxId) {
if (updateMaxId) {
storage.maxId = message.id
}
2020-05-07 16:10:53 +03:00
}
2020-10-29 13:33:06 +03:00
if (!storage.idIndex[message.id] && !isConfirmation(storage, message)) {
if (storage.lastSeenMessageId < message.id) {
2020-05-07 16:10:53 +03:00
storage.newMessageCount++
}
storage.idIndex[message.id] = message
2020-10-29 13:33:06 +03:00
storage.messages.push(storage.idIndex[message.id])
storage.idempotencyKeyIndex[message.idempotency_key] = true
2020-05-07 16:10:53 +03:00
}
}
}
2020-10-29 13:33:06 +03:00
const isConfirmation = (storage, message) => {
if (!message.idempotency_key) return
return storage.idempotencyKeyIndex[message.idempotency_key]
}
2020-05-07 16:10:53 +03:00
const resetNewMessageCount = (storage) => {
2026-01-06 16:22:52 +02:00
if (!storage) {
return
}
2020-05-07 16:10:53 +03:00
storage.newMessageCount = 0
storage.lastSeenMessageId = storage.maxId
2020-05-07 16:10:53 +03:00
}
// Inserts date separators and marks the head and tail if it's the chain of messages made by the same user
const getView = (storage) => {
2026-01-06 16:22:52 +02:00
if (!storage) {
return []
}
2020-05-07 16:10:53 +03:00
const result = []
2026-01-06 16:22:52 +02:00
const messages = _.orderBy(
storage.messages,
['pending', 'id'],
['asc', 'asc'],
)
const firstMessage = messages[0]
let previousMessage = messages[messages.length - 1]
2020-05-07 16:10:53 +03:00
let currentMessageChainId
if (firstMessage) {
const date = new Date(firstMessage.created_at)
2020-05-07 16:10:53 +03:00
date.setHours(0, 0, 0, 0)
result.push({
type: 'date',
date,
2026-01-06 16:22:52 +02:00
id: date.getTime().toString(),
2020-05-07 16:10:53 +03:00
})
}
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) {
2020-05-07 16:10:53 +03:00
result.push({
type: 'date',
date,
2026-01-06 16:22:52 +02:00
id: date.getTime().toString(),
2020-05-07 16:10:53 +03:00
})
2022-07-31 12:35:48 +03:00
previousMessage.isTail = true
2020-05-07 16:10:53 +03:00
currentMessageChainId = undefined
afterDate = true
}
const object = {
type: 'message',
data: message,
date,
id: message.id,
2026-01-06 16:22:52 +02:00
messageChainId: currentMessageChainId,
2020-05-07 16:10:53 +03:00
}
// end a message chian
if ((nextMessage && nextMessage.account_id) !== message.account_id) {
2022-07-31 12:35:48 +03:00
object.isTail = true
2020-05-07 16:10:53 +03:00
currentMessageChainId = undefined
}
// start a new message chain
2026-01-06 16:22:52 +02:00
if (
(previousMessage &&
previousMessage.data &&
previousMessage.data.account_id) !== message.account_id ||
afterDate
) {
2020-05-07 16:10:53 +03:00
currentMessageChainId = _.uniqueId()
2022-07-31 12:35:48 +03:00
object.isHead = true
object.messageChainId = currentMessageChainId
2020-05-07 16:10:53 +03:00
}
result.push(object)
previousMessage = object
2020-05-07 16:10:53 +03:00
afterDate = false
}
return result
}
const ChatService = {
add,
empty,
getView,
deleteMessage,
cullOlderMessages,
2020-05-07 16:10:53 +03:00
resetNewMessageCount,
2020-10-29 13:33:06 +03:00
clear,
2026-01-06 16:22:52 +02:00
handleMessageError,
2020-05-07 16:10:53 +03:00
}
export default ChatService