fix: speed up reaction UI update for opengroups
This commit is contained in:
parent
f309bf40f8
commit
b33ea096b4
|
@ -737,7 +737,12 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
|
||||
const chatMessagePrivate = new VisibleMessage(chatMessageParams);
|
||||
await getMessageQueue().sendToPubKey(destinationPubkey, chatMessagePrivate);
|
||||
await handleMessageReaction(reaction, UserUtils.getOurPubKeyStrFromCache(), true);
|
||||
await handleMessageReaction({
|
||||
reaction,
|
||||
sender: UserUtils.getOurPubKeyStrFromCache(),
|
||||
you: true,
|
||||
isOpenGroup: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -749,7 +754,12 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
});
|
||||
// we need the return await so that errors are caught in the catch {}
|
||||
await getMessageQueue().sendToGroup(closedGroupVisibleMessage);
|
||||
await handleMessageReaction(reaction, UserUtils.getOurPubKeyStrFromCache(), true);
|
||||
await handleMessageReaction({
|
||||
reaction,
|
||||
sender: UserUtils.getOurPubKeyStrFromCache(),
|
||||
you: true,
|
||||
isOpenGroup: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -322,11 +322,12 @@ async function handleSwarmMessage(
|
|||
// this call has to be made inside the queueJob!
|
||||
// We handle reaction DataMessages separately
|
||||
if (!msgModel.get('isPublic') && rawDataMessage.reaction) {
|
||||
await handleMessageReaction(
|
||||
rawDataMessage.reaction,
|
||||
msgModel.get('source'),
|
||||
isUsFromCache(msgModel.get('source'))
|
||||
);
|
||||
await handleMessageReaction({
|
||||
reaction: rawDataMessage.reaction,
|
||||
sender: msgModel.get('source'),
|
||||
you: isUsFromCache(msgModel.get('source')),
|
||||
isOpenGroup: false,
|
||||
});
|
||||
if (
|
||||
convoToAddMessageTo.isPrivate() &&
|
||||
msgModel.get('unread') &&
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import AbortController from 'abort-controller';
|
||||
import { OpenGroupReactionResponse } from '../../../../types/Reaction';
|
||||
import { handleClearReaction } from '../../../../util/reactions';
|
||||
import { OpenGroupRequestCommonType } from '../opengroupV2/ApiUtil';
|
||||
import {
|
||||
batchFirstSubIsSuccess,
|
||||
|
@ -25,8 +26,13 @@ export const clearSogsReactionByServerId = async (
|
|||
serverId: number,
|
||||
roomInfos: OpenGroupRequestCommonType
|
||||
): Promise<boolean> => {
|
||||
const canReact = await hasReactionSupport(serverId);
|
||||
if (!canReact) {
|
||||
const { supported, conversation } = await hasReactionSupport(serverId);
|
||||
if (!supported) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!conversation) {
|
||||
window.log.warn(`Conversation for ${reaction} not found in db`);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -44,10 +50,17 @@ export const clearSogsReactionByServerId = async (
|
|||
|
||||
addToMutationCache(cacheEntry);
|
||||
|
||||
// Since responses can take a long time we immediately update the moderators's UI and if there is a problem it is overwritten by handleOpenGroupMessageReactions later.
|
||||
await handleClearReaction(serverId, reaction);
|
||||
|
||||
const options: Array<OpenGroupBatchRow> = [
|
||||
{
|
||||
type: 'deleteReaction',
|
||||
deleteReaction: { reaction, messageId: serverId, roomId: roomInfos.roomId },
|
||||
deleteReaction: {
|
||||
reaction,
|
||||
messageId: serverId,
|
||||
roomId: roomInfos.roomId,
|
||||
},
|
||||
},
|
||||
];
|
||||
const result = await sogsBatchSend(
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import { AbortSignal } from 'abort-controller';
|
||||
import { Data } from '../../../../data/data';
|
||||
import { ConversationModel } from '../../../../models/conversation';
|
||||
import { Action, OpenGroupReactionResponse, Reaction } from '../../../../types/Reaction';
|
||||
import { getEmojiDataFromNative } from '../../../../util/emoji';
|
||||
import { hitRateLimit } from '../../../../util/reactions';
|
||||
import { handleMessageReaction, hitRateLimit } from '../../../../util/reactions';
|
||||
import { OnionSending } from '../../../onions/onionSend';
|
||||
import { UserUtils } from '../../../utils';
|
||||
import { OpenGroupPollingUtils } from '../opengroupV2/OpenGroupPollingUtils';
|
||||
import { getUsBlindedInThatServer } from './knownBlindedkeys';
|
||||
import { batchGlobalIsSuccess, parseBatchGlobalStatusCode } from './sogsV3BatchPoll';
|
||||
import {
|
||||
addToMutationCache,
|
||||
|
@ -13,25 +16,27 @@ import {
|
|||
updateMutationCache,
|
||||
} from './sogsV3MutationCache';
|
||||
|
||||
export const hasReactionSupport = async (serverId: number): Promise<boolean> => {
|
||||
export const hasReactionSupport = async (
|
||||
serverId: number
|
||||
): Promise<{ supported: boolean; conversation: ConversationModel | null }> => {
|
||||
const found = await Data.getMessageByServerId(serverId);
|
||||
if (!found) {
|
||||
window.log.warn(`Open Group Message ${serverId} not found in db`);
|
||||
return false;
|
||||
return { supported: false, conversation: null };
|
||||
}
|
||||
|
||||
const conversationModel = found?.getConversation();
|
||||
if (!conversationModel) {
|
||||
window.log.warn(`Conversation for ${serverId} not found in db`);
|
||||
return false;
|
||||
return { supported: false, conversation: null };
|
||||
}
|
||||
|
||||
if (!conversationModel.hasReactions()) {
|
||||
window.log.warn("This open group doesn't have reaction support. Server Message ID", serverId);
|
||||
return false;
|
||||
return { supported: false, conversation: null };
|
||||
}
|
||||
|
||||
return true;
|
||||
return { supported: true, conversation: conversationModel };
|
||||
};
|
||||
|
||||
export const sendSogsReactionOnionV4 = async (
|
||||
|
@ -47,8 +52,8 @@ export const sendSogsReactionOnionV4 = async (
|
|||
throw new Error(`Could not find sogs pubkey of url:${serverUrl}`);
|
||||
}
|
||||
|
||||
const canReact = await hasReactionSupport(reaction.id);
|
||||
if (!canReact) {
|
||||
const { supported, conversation } = await hasReactionSupport(reaction.id);
|
||||
if (!supported) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -56,6 +61,11 @@ export const sendSogsReactionOnionV4 = async (
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!conversation) {
|
||||
window.log.warn(`Conversation for ${reaction.id} not found in db`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The SOGS endpoint supports any text input so we need to make sure we are sending a valid unicode emoji
|
||||
// for an invalid input we use https://emojipedia.org/frame-with-an-x/ as a replacement since it cannot rendered as an emoji but is valid unicode
|
||||
const emoji = getEmojiDataFromNative(reaction.emoji) ? reaction.emoji : '🖾';
|
||||
|
@ -77,6 +87,15 @@ export const sendSogsReactionOnionV4 = async (
|
|||
|
||||
addToMutationCache(cacheEntry);
|
||||
|
||||
// Since responses can take a long time we immediately update the sender's UI and if there is a problem it is overwritten by handleOpenGroupMessageReactions later.
|
||||
const me = UserUtils.getOurPubKeyStrFromCache();
|
||||
await handleMessageReaction({
|
||||
reaction,
|
||||
sender: blinded ? getUsBlindedInThatServer(conversation) || me : me,
|
||||
you: true,
|
||||
isOpenGroup: true,
|
||||
});
|
||||
|
||||
// reaction endpoint requires an empty dict {}
|
||||
const stringifiedBody = null;
|
||||
const result = await OnionSending.sendJsonViaOnionV4ToSogs({
|
||||
|
|
|
@ -52,11 +52,12 @@ describe('ReactionMessage', () => {
|
|||
expect(reaction?.action, 'action should be 0').to.be.equal(0);
|
||||
|
||||
// Handling reaction
|
||||
const updatedMessage = await handleMessageReaction(
|
||||
reaction as SignalService.DataMessage.IReaction,
|
||||
ourNumber,
|
||||
true
|
||||
);
|
||||
const updatedMessage = await handleMessageReaction({
|
||||
reaction: reaction as SignalService.DataMessage.IReaction,
|
||||
sender: ourNumber,
|
||||
you: true,
|
||||
isOpenGroup: false,
|
||||
});
|
||||
|
||||
expect(updatedMessage?.get('reacts'), 'original message should have reacts').to.not.be
|
||||
.undefined;
|
||||
|
@ -84,11 +85,12 @@ describe('ReactionMessage', () => {
|
|||
expect(reaction?.action, 'action should be 1').to.be.equal(1);
|
||||
|
||||
// Handling reaction
|
||||
const updatedMessage = await handleMessageReaction(
|
||||
reaction as SignalService.DataMessage.IReaction,
|
||||
ourNumber,
|
||||
true
|
||||
);
|
||||
const updatedMessage = await handleMessageReaction({
|
||||
reaction: reaction as SignalService.DataMessage.IReaction,
|
||||
sender: ourNumber,
|
||||
you: true,
|
||||
isOpenGroup: false,
|
||||
});
|
||||
|
||||
expect(updatedMessage?.get('reacts'), 'original message reacts should be undefined').to.be
|
||||
.undefined;
|
||||
|
|
|
@ -37,23 +37,28 @@ export function hitRateLimit(): boolean {
|
|||
* Retrieves the original message of a reaction
|
||||
*/
|
||||
const getMessageByReaction = async (
|
||||
reaction: SignalService.DataMessage.IReaction
|
||||
reaction: SignalService.DataMessage.IReaction,
|
||||
isOpenGroup: boolean
|
||||
): Promise<MessageModel | null> => {
|
||||
let originalMessage = null;
|
||||
const originalMessageId = Number(reaction.id);
|
||||
const originalMessageAuthor = reaction.author;
|
||||
|
||||
const collection = await Data.getMessagesBySentAt(originalMessageId);
|
||||
originalMessage = collection.find((item: MessageModel) => {
|
||||
const messageTimestamp = item.get('sent_at');
|
||||
const author = item.get('source');
|
||||
return Boolean(
|
||||
messageTimestamp &&
|
||||
messageTimestamp === originalMessageId &&
|
||||
author &&
|
||||
author === originalMessageAuthor
|
||||
);
|
||||
});
|
||||
if (isOpenGroup) {
|
||||
originalMessage = await Data.getMessageByServerId(originalMessageId);
|
||||
} else {
|
||||
const collection = await Data.getMessagesBySentAt(originalMessageId);
|
||||
originalMessage = collection.find((item: MessageModel) => {
|
||||
const messageTimestamp = item.get('sent_at');
|
||||
const author = item.get('source');
|
||||
return Boolean(
|
||||
messageTimestamp &&
|
||||
messageTimestamp === originalMessageId &&
|
||||
author &&
|
||||
author === originalMessageAuthor
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (!originalMessage) {
|
||||
window?.log?.warn(`Cannot find the original reacted message ${originalMessageId}.`);
|
||||
|
@ -140,19 +145,25 @@ export const sendMessageReaction = async (messageId: string, emoji: string) => {
|
|||
|
||||
/**
|
||||
* Handle reactions on the client by updating the state of the source message
|
||||
* Do not use for Open Groups
|
||||
* Used in OpenGroups for sending reactions only, not handling responses
|
||||
*/
|
||||
export const handleMessageReaction = async (
|
||||
reaction: SignalService.DataMessage.IReaction,
|
||||
sender: string,
|
||||
you: boolean
|
||||
) => {
|
||||
export const handleMessageReaction = async ({
|
||||
reaction,
|
||||
sender,
|
||||
you,
|
||||
isOpenGroup,
|
||||
}: {
|
||||
reaction: SignalService.DataMessage.IReaction;
|
||||
sender: string;
|
||||
you: boolean;
|
||||
isOpenGroup: boolean;
|
||||
}) => {
|
||||
if (!reaction.emoji) {
|
||||
window?.log?.warn(`There is no emoji for the reaction ${reaction}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const originalMessage = await getMessageByReaction(reaction);
|
||||
const originalMessage = await getMessageByReaction(reaction, isOpenGroup);
|
||||
if (!originalMessage) {
|
||||
return;
|
||||
}
|
||||
|
@ -163,20 +174,15 @@ export const handleMessageReaction = async (
|
|||
const senders = details.senders;
|
||||
let count = details.count || 0;
|
||||
|
||||
if (originalMessage.get('isPublic')) {
|
||||
window.log.warn("handleMessageReaction() shouldn't be used in opengroups");
|
||||
return;
|
||||
} else {
|
||||
if (details.you && senders.includes(sender)) {
|
||||
if (reaction.action === Action.REACT) {
|
||||
window.log.warn('Received duplicate message for your reaction. Ignoring it');
|
||||
return;
|
||||
} else {
|
||||
details.you = false;
|
||||
}
|
||||
if (details.you && senders.includes(sender)) {
|
||||
if (reaction.action === Action.REACT) {
|
||||
window.log.warn('Received duplicate message for your reaction. Ignoring it');
|
||||
return;
|
||||
} else {
|
||||
details.you = you;
|
||||
details.you = false;
|
||||
}
|
||||
} else {
|
||||
details.you = you;
|
||||
}
|
||||
|
||||
switch (reaction.action) {
|
||||
|
@ -230,7 +236,34 @@ export const handleMessageReaction = async (
|
|||
};
|
||||
|
||||
/**
|
||||
* Handles all message reaction updates for opengroups
|
||||
* Handles updating the UI when clearing all reactions for a certain emoji
|
||||
* Only usable by moderators in opengroups and runs on their client
|
||||
*/
|
||||
export const handleClearReaction = async (serverId: number, emoji: string) => {
|
||||
const originalMessage = await Data.getMessageByServerId(serverId);
|
||||
if (!originalMessage) {
|
||||
window?.log?.warn(`Cannot find the original reacted message ${serverId}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const reacts: ReactionList | undefined = originalMessage.get('reacts');
|
||||
if (reacts) {
|
||||
// tslint:disable-next-line: no-dynamic-delete
|
||||
delete reacts[emoji];
|
||||
}
|
||||
|
||||
originalMessage.set({
|
||||
reacts: !isEmpty(reacts) ? reacts : undefined,
|
||||
});
|
||||
|
||||
await originalMessage.commit();
|
||||
|
||||
window.log.info(`You cleared all ${emoji} reactions on message ${serverId}`);
|
||||
return originalMessage;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles all message reaction updates/responses for opengroups
|
||||
*/
|
||||
export const handleOpenGroupMessageReactions = async (
|
||||
reactions: OpenGroupReactionList,
|
||||
|
|
Loading…
Reference in New Issue