From d77c9fa824385e980c355a15980fa90aec6349da Mon Sep 17 00:00:00 2001 From: William Grant Date: Thu, 25 Aug 2022 15:10:12 +1000 Subject: [PATCH] fix: sogs reactions work again and added rate limiting --- .../open_group_api/sogsv3/sogsV3BatchPoll.ts | 6 +-- .../sogsv3/sogsV3SendReaction.ts | 8 ++- ts/session/sending/MessageQueue.ts | 19 +++++-- ts/session/sending/MessageSender.ts | 28 +++------- ts/util/reactions.ts | 53 +++++++++++++------ 5 files changed, 68 insertions(+), 46 deletions(-) diff --git a/ts/session/apis/open_group_api/sogsv3/sogsV3BatchPoll.ts b/ts/session/apis/open_group_api/sogsv3/sogsV3BatchPoll.ts index 829fd9191..d3492417d 100644 --- a/ts/session/apis/open_group_api/sogsv3/sogsV3BatchPoll.ts +++ b/ts/session/apis/open_group_api/sogsv3/sogsV3BatchPoll.ts @@ -8,6 +8,7 @@ import { import { addJsonContentTypeToHeaders } from './sogsV3SendMessage'; import { AbortSignal } from 'abort-controller'; import { roomHasBlindEnabled } from './sogsV3Capabilities'; +import { SOGSReactorsFetchCount } from '../../../../util/reactions'; type BatchFetchRequestOptions = { method: 'POST' | 'PUT' | 'GET' | 'DELETE'; @@ -238,10 +239,9 @@ const makeBatchRequestPayload = ( if (options.messages) { return { method: 'GET', - // TODO Consistency across platforms with fetching reactors path: isNumber(options.messages.sinceSeqNo) - ? `/room/${options.messages.roomId}/messages/since/${options.messages.sinceSeqNo}?t=r` - : `/room/${options.messages.roomId}/messages/recent`, + ? `/room/${options.messages.roomId}/messages/since/${options.messages.sinceSeqNo}?t=r&reactors=${SOGSReactorsFetchCount}` + : `/room/${options.messages.roomId}/messages/recent?reactors=${SOGSReactorsFetchCount}`, }; } break; diff --git a/ts/session/apis/open_group_api/sogsv3/sogsV3SendReaction.ts b/ts/session/apis/open_group_api/sogsv3/sogsV3SendReaction.ts index 60f7b49d6..01cae53fe 100644 --- a/ts/session/apis/open_group_api/sogsv3/sogsV3SendReaction.ts +++ b/ts/session/apis/open_group_api/sogsv3/sogsV3SendReaction.ts @@ -2,6 +2,7 @@ import { AbortSignal } from 'abort-controller'; import { Data } from '../../../../data/data'; import { Action, OpenGroupReactionResponse, Reaction } from '../../../../types/Reaction'; import { getEmojiDataFromNative } from '../../../../util/emoji'; +import { hitRateLimit } from '../../../../util/reactions'; import { OnionSending } from '../../../onions/onionSend'; import { OpenGroupPollingUtils } from '../opengroupV2/OpenGroupPollingUtils'; import { batchGlobalIsSuccess, parseBatchGlobalStatusCode } from './sogsV3BatchPoll'; @@ -45,7 +46,12 @@ export const sendSogsReactionOnionV4 = async ( return false; } - // for an invalid reaction we use https://emojipedia.org/frame-with-an-x/ as a replacement since it cannot rendered as an emoji + if (hitRateLimit()) { + 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 : '🖾'; const endpoint = `/room/${room}/reaction/${reaction.id}/${emoji}`; const method = reaction.action === Action.REACT ? 'PUT' : 'DELETE'; diff --git a/ts/session/sending/MessageQueue.ts b/ts/session/sending/MessageQueue.ts index ba536ebc3..273c87220 100644 --- a/ts/session/sending/MessageQueue.ts +++ b/ts/session/sending/MessageQueue.ts @@ -23,6 +23,8 @@ import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/Ope import { UnsendMessage } from '../messages/outgoing/controlMessage/UnsendMessage'; import { CallMessage } from '../messages/outgoing/controlMessage/CallMessage'; import { OpenGroupMessageV2 } from '../apis/open_group_api/opengroupV2/OpenGroupMessageV2'; +import { AbortController } from 'abort-controller'; +import { sendSogsReactionOnionV4 } from '../apis/open_group_api/sogsv3/sogsV3SendReaction'; type ClosedGroupMessageType = | ClosedGroupVisibleMessage @@ -75,6 +77,18 @@ export class MessageQueue { // Skipping the queue for Open Groups v2; the message is sent directly try { + // NOTE Reactions are handled separately + if (message.reaction) { + await sendSogsReactionOnionV4( + roomInfos.serverUrl, + roomInfos.roomId, + new AbortController().signal, + message.reaction, + blinded + ); + return; + } + const result = await MessageSender.sendToOpenGroupV2( message, roomInfos, @@ -82,11 +96,6 @@ export class MessageQueue { filesToLink ); - // NOTE Reactions are handled in the MessageSender - if (message.reaction) { - return; - } - const { sentTimestamp, serverId } = result as OpenGroupMessageV2; if (!serverId || serverId === -1) { throw new Error(`Invalid serverId returned by server: ${serverId}`); diff --git a/ts/session/sending/MessageSender.ts b/ts/session/sending/MessageSender.ts index 92bb682a8..57fadc9ed 100644 --- a/ts/session/sending/MessageSender.ts +++ b/ts/session/sending/MessageSender.ts @@ -26,7 +26,6 @@ import { sendSogsMessageOnionV4, } from '../apis/open_group_api/sogsv3/sogsV3SendMessage'; import { AbortController } from 'abort-controller'; -import { sendSogsReactionOnionV4 } from '../apis/open_group_api/sogsv3/sogsV3SendReaction'; const DEFAULT_CONNECTIONS = 1; @@ -288,25 +287,14 @@ export async function sendToOpenGroupV2( filesToLink, }); - if (rawMessage.reaction) { - const msg = await sendSogsReactionOnionV4( - roomInfos.serverUrl, - roomInfos.roomId, - new AbortController().signal, - rawMessage.reaction, - blinded - ); - return msg; - } else { - const msg = await sendSogsMessageOnionV4( - roomInfos.serverUrl, - roomInfos.roomId, - new AbortController().signal, - v2Message, - blinded - ); - return msg; - } + const msg = await sendSogsMessageOnionV4( + roomInfos.serverUrl, + roomInfos.roomId, + new AbortController().signal, + v2Message, + blinded + ); + return msg; } /** diff --git a/ts/util/reactions.ts b/ts/util/reactions.ts index c3c0d59cd..60e1c1e0e 100644 --- a/ts/util/reactions.ts +++ b/ts/util/reactions.ts @@ -11,10 +11,28 @@ import { UserUtils } from '../session/utils'; import { Action, OpenGroupReactionList, ReactionList, RecentReactions } from '../types/Reaction'; import { getRecentReactions, saveRecentReations } from '../util/storage'; +export const SOGSReactorsFetchCount = 5; const rateCountLimit = 20; const rateTimeLimit = 60 * 1000; const latestReactionTimestamps: Array = []; +export function hitRateLimit(): boolean { + const timestamp = Date.now(); + latestReactionTimestamps.push(timestamp); + + if (latestReactionTimestamps.length > rateCountLimit) { + const firstTimestamp = latestReactionTimestamps[0]; + if (timestamp - firstTimestamp < rateTimeLimit) { + latestReactionTimestamps.pop(); + window.log.warn('Only 20 reactions are allowed per minute'); + return true; + } else { + latestReactionTimestamps.shift(); + } + } + return false; +} + /** * Retrieves the original message of a reaction */ @@ -46,7 +64,7 @@ const getMessageByReaction = async ( }; /** - * Sends a Reaction Data Message, don't use for OpenGroups + * Sends a Reaction Data Message */ export const sendMessageReaction = async (messageId: string, emoji: string) => { const found = await Data.getMessageById(messageId); @@ -62,27 +80,23 @@ export const sendMessageReaction = async (messageId: string, emoji: string) => { return; } - // TODO need to add rate limiting to SOGS function - const timestamp = Date.now(); - latestReactionTimestamps.push(timestamp); + if (hitRateLimit()) { + return; + } - if (latestReactionTimestamps.length > rateCountLimit) { - const firstTimestamp = latestReactionTimestamps[0]; - if (timestamp - firstTimestamp < rateTimeLimit) { - latestReactionTimestamps.pop(); - return; + let me = UserUtils.getOurPubKeyStrFromCache(); + let id = Number(found.get('sent_at')); + + if (found.get('isPublic')) { + if (found.get('serverId')) { + id = found.get('serverId') || id; + me = getUsBlindedInThatServer(conversationModel) || me; } else { - latestReactionTimestamps.shift(); + window.log.warn(`Server Id was not found in message ${messageId} for opengroup reaction`); + return; } } - if (found?.get('isPublic')) { - window.log.warn("sendMessageReaction() shouldn't be used in opengroups"); - return; - } - - const id = Number(found.get('sent_at')); - const me = UserUtils.getOurPubKeyStrFromCache(); const author = found.get('source'); let action: Action = Action.REACT; @@ -231,6 +245,11 @@ export const handleOpenGroupMessageReactions = async ( reactor => !isUsAnySogsFromCache(reactor) ); + // If you aren't included in the reactors then remove the extra reactor to match with the SOGSReactorsFetchCount. + if (reactorsWithoutMe.length === SOGSReactorsFetchCount) { + reactorsWithoutMe.pop(); + } + const conversationModel = originalMessage?.getConversation(); if (conversationModel) { const me =