purge shout/chat (#49)

Reviewed-on: https://akkoma.dev/AkkomaGang/pleroma-fe/pulls/49
This commit is contained in:
floatingghost 2022-07-20 15:36:45 +00:00
parent 9a82e8b097
commit a8c4aec721
55 changed files with 15 additions and 2872 deletions

View file

@ -1,5 +1,5 @@
import { each, map, concat, last, get } from 'lodash'
import { parseStatus, parseUser, parseNotification, parseAttachment, parseChat, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
import { parseStatus, parseUser, parseNotification, parseAttachment, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
import { RegistrationError, StatusCodeError } from '../errors/errors'
/* eslint-env browser */
@ -92,11 +92,6 @@ const MASTODON_ANNOUNCEMENTS_DISMISS_URL = id => `/api/v1/announcements/${id}/di
const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/reactions`
const PLEROMA_EMOJI_REACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
const PLEROMA_CHATS_URL = `/api/v1/pleroma/chats`
const PLEROMA_CHAT_URL = id => `/api/v1/pleroma/chats/by-account-id/${id}`
const PLEROMA_CHAT_MESSAGES_URL = id => `/api/v1/pleroma/chats/${id}/messages`
const PLEROMA_CHAT_READ_URL = id => `/api/v1/pleroma/chats/${id}/read`
const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
const PLEROMA_ANNOUNCEMENTS_URL = '/api/v1/pleroma/admin/announcements'
const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements'
@ -1378,7 +1373,6 @@ const MASTODON_STREAMING_EVENTS = new Set([
])
const PLEROMA_STREAMING_EVENTS = new Set([
'pleroma:chat_update'
])
// A thin wrapper around WebSocket API that allows adding a pre-processor to it
@ -1448,8 +1442,6 @@ export const handleMastoWS = (wsEvent) => {
return { event, status: parseStatus(data) }
} else if (event === 'notification') {
return { event, notification: parseNotification(data) }
} else if (event === 'pleroma:chat_update') {
return { event, chatUpdate: parseChat(data) }
}
} else {
console.warn('Unknown event', wsEvent)
@ -1466,82 +1458,6 @@ export const WSConnectionStatus = Object.freeze({
'STARTING_INITIAL': 6
})
const chats = ({ credentials }) => {
return fetch(PLEROMA_CHATS_URL, { headers: authHeaders(credentials) })
.then((data) => data.json())
.then((data) => {
return { chats: data.map(parseChat).filter(c => c) }
})
}
const getOrCreateChat = ({ accountId, credentials }) => {
return promisedRequest({
url: PLEROMA_CHAT_URL(accountId),
method: 'POST',
credentials
})
}
const chatMessages = ({ id, credentials, maxId, sinceId, limit = 20 }) => {
let url = PLEROMA_CHAT_MESSAGES_URL(id)
const args = [
maxId && `max_id=${maxId}`,
sinceId && `since_id=${sinceId}`,
limit && `limit=${limit}`
].filter(_ => _).join('&')
url = url + (args ? '?' + args : '')
return promisedRequest({
url,
method: 'GET',
credentials
})
}
const sendChatMessage = ({ id, content, mediaId = null, idempotencyKey, credentials }) => {
const payload = {
'content': content
}
if (mediaId) {
payload['media_id'] = mediaId
}
const headers = {}
if (idempotencyKey) {
headers['idempotency-key'] = idempotencyKey
}
return promisedRequest({
url: PLEROMA_CHAT_MESSAGES_URL(id),
method: 'POST',
payload: payload,
credentials,
headers
})
}
const readChat = ({ id, lastReadId, credentials }) => {
return promisedRequest({
url: PLEROMA_CHAT_READ_URL(id),
method: 'POST',
payload: {
'last_read_id': lastReadId
},
credentials
})
}
const deleteChatMessage = ({ chatId, messageId, credentials }) => {
return promisedRequest({
url: PLEROMA_DELETE_CHAT_MESSAGE_URL(chatId, messageId),
method: 'DELETE',
credentials
})
}
const apiService = {
verifyCredentials,
fetchTimeline,
@ -1639,12 +1555,6 @@ const apiService = {
fetchDomainMutes,
muteDomain,
unmuteDomain,
chats,
getOrCreateChat,
chatMessages,
sendChatMessage,
readChat,
deleteChatMessage,
fetchAnnouncements,
dismissAnnouncement,
postAnnouncement,

View file

@ -1,226 +0,0 @@
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

View file

@ -1,41 +0,0 @@
import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js'
export const maybeShowChatNotification = (store, chat) => {
if (!chat.lastMessage) return
if (store.rootState.chats.currentChatId === chat.id && !document.hidden) return
if (store.rootState.users.currentUser.id === chat.lastMessage.account_id) return
const opts = {
tag: chat.lastMessage.id,
title: chat.account.name,
icon: chat.account.profile_image_url,
body: chat.lastMessage.content
}
if (chat.lastMessage.attachment && chat.lastMessage.attachment.type === 'image') {
opts.image = chat.lastMessage.attachment.preview_url
}
showDesktopNotification(store.rootState, opts)
}
export const buildFakeMessage = ({ content, chatId, attachments, userId, idempotencyKey }) => {
const fakeMessage = {
content,
chat_id: chatId,
created_at: new Date(),
id: `${new Date().getTime()}`,
attachments: attachments,
account_id: userId,
idempotency_key: idempotencyKey,
emojis: [],
pending: true,
isNormalized: true
}
if (attachments[0]) {
fakeMessage.attachment = attachments[0]
}
return fakeMessage
}

View file

@ -94,7 +94,6 @@ export const parseUser = (data) => {
output.background_image = data.pleroma.background_image
output.favicon = data.pleroma.favicon
output.token = data.pleroma.chat_token
if (relationship) {
output.relationship = relationship
@ -200,7 +199,6 @@ export const parseUser = (data) => {
: data.pleroma.deactivated // old backend
output.notification_settings = data.pleroma.notification_settings
output.unread_chat_count = data.pleroma.unread_chat_count
}
output.tags = output.tags || []
@ -403,7 +401,7 @@ export const parseNotification = (data) => {
? parseStatus(data.notice.favorited_status)
: parsedNotice
output.action = parsedNotice
output.from_profile = output.type === 'pleroma:chat_mention' ? parseUser(data.account) : parseUser(data.from_profile)
output.from_profile = parseUser(data.from_profile)
}
output.created_at = new Date(data.created_at)
@ -429,34 +427,3 @@ export const parseLinkHeaderPagination = (linkHeader, opts = {}) => {
minId: flakeId ? minId : parseInt(minId, 10)
}
}
export const parseChat = (chat) => {
const output = {}
output.id = chat.id
output.account = parseUser(chat.account)
output.unread = chat.unread
output.lastMessage = parseChatMessage(chat.last_message)
output.updated_at = new Date(chat.updated_at)
return output
}
export const parseChatMessage = (message) => {
if (!message) { return }
if (message.isNormalized) { return message }
const output = message
output.id = message.id
output.created_at = new Date(message.created_at)
output.chat_id = message.chat_id
output.emojis = message.emojis
output.content = message.content
if (message.attachment) {
output.attachments = [parseAttachment(message.attachment)]
} else {
output.attachments = []
}
output.pending = !!message.pending
output.error = false
output.idempotency_key = message.idempotency_key
output.isNormalized = true
return output
}

View file

@ -106,8 +106,7 @@ export const generateRadii = (input) => {
avatar: 5,
avatarAlt: 50,
tooltip: 2,
attachment: 5,
chatMessage: inputRadii.panel
attachment: 5
})
return {

View file

@ -23,9 +23,7 @@ export const LAYERS = {
inputTopBar: 'topBar',
alert: 'bg',
alertPanel: 'panel',
poll: 'bg',
chatBg: 'underlay',
chatMessage: 'chatBg'
poll: 'bg'
}
/* By default opacity slots have 1 as default opacity
@ -707,58 +705,5 @@ export const SLOT_INHERITANCE = {
layer: 'badge',
variant: 'badgeNotification',
textColor: 'bw'
},
chatBg: {
depends: ['bg']
},
chatMessageIncomingBg: {
depends: ['chatBg']
},
chatMessageIncomingText: {
depends: ['text'],
layer: 'chatMessage',
variant: 'chatMessageIncomingBg',
textColor: true
},
chatMessageIncomingLink: {
depends: ['link'],
layer: 'chatMessage',
variant: 'chatMessageIncomingBg',
textColor: 'preserve'
},
chatMessageIncomingBorder: {
depends: ['border'],
opacity: 'border',
color: (mod, border) => brightness(2 * mod, border).rgb
},
chatMessageOutgoingBg: {
depends: ['chatMessageIncomingBg'],
color: (mod, chatMessage) => brightness(5 * mod, chatMessage).rgb
},
chatMessageOutgoingText: {
depends: ['text'],
layer: 'chatMessage',
variant: 'chatMessageOutgoingBg',
textColor: true
},
chatMessageOutgoingLink: {
depends: ['link'],
layer: 'chatMessage',
variant: 'chatMessageOutgoingBg',
textColor: 'preserve'
},
chatMessageOutgoingBorder: {
depends: ['chatMessageOutgoingBg'],
opacity: 'border',
color: (mod, border) => brightness(2 * mod, border).rgb
}
}