chat_service.js (6220B)
- import _ from 'lodash'
- const empty = (chatId) => {
- return {
- idIndex: {},
- idempotencyKeyIndex: {},
- messages: [],
- newMessageCount: 0,
- lastSeenMessageId: '0',
- 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