diff --git a/ts/services/MessageCache.ts b/ts/services/MessageCache.ts index 887727fc1..e9a485c12 100644 --- a/ts/services/MessageCache.ts +++ b/ts/services/MessageCache.ts @@ -2,6 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import cloneDeep from 'lodash/cloneDeep'; +import { throttle } from 'lodash'; +import LRU from 'lru-cache'; import type { MessageAttributesType } from '../model-types.d'; import type { MessageModel } from '../models/messages'; import * as Errors from '../types/errors'; @@ -17,6 +19,7 @@ import { softAssert, strictAssert } from '../util/assert'; import { isStory } from '../messages/helpers'; import { getStoryDataFromMessageAttributes } from './storyLoader'; +const MAX_THROTTLED_REDUX_UPDATERS = 200; export class MessageCache { private state = { messages: new Map(), @@ -198,28 +201,7 @@ export class MessageCache { this.markModelStale(nextMessageAttributes); - if (window.reduxActions) { - if (isStory(nextMessageAttributes)) { - const storyData = getStoryDataFromMessageAttributes({ - ...nextMessageAttributes, - }); - - if (!storyData) { - return; - } - - window.reduxActions.stories.storyChanged(storyData); - - // We don't want messageChanged to run - return; - } - - window.reduxActions.conversations.messageChanged( - messageId, - nextMessageAttributes.conversationId, - nextMessageAttributes - ); - } + this.throttledUpdateRedux(nextMessageAttributes); if (skipSaveToDatabase) { return; @@ -231,6 +213,49 @@ export class MessageCache { ); } + private throttledReduxUpdaters = new LRU({ + max: MAX_THROTTLED_REDUX_UPDATERS, + }); + + private throttledUpdateRedux(attributes: MessageAttributesType) { + let updater = this.throttledReduxUpdaters.get(attributes.id); + if (!updater) { + updater = throttle(this.updateRedux.bind(this), 200, { + leading: true, + trailing: true, + }); + this.throttledReduxUpdaters.set(attributes.id, updater); + } + + updater(attributes); + } + + private updateRedux(attributes: MessageAttributesType) { + if (!window.reduxActions) { + return; + } + if (isStory(attributes)) { + const storyData = getStoryDataFromMessageAttributes({ + ...attributes, + }); + + if (!storyData) { + return; + } + + window.reduxActions.stories.storyChanged(storyData); + + // We don't want messageChanged to run + return; + } + + window.reduxActions.conversations.messageChanged( + attributes.id, + attributes.conversationId, + attributes + ); + } + // When you already have the message attributes from the db and want to // ensure that they're added to the cache. The latest attributes from cache // are returned if they exist, if not the attributes passed in are returned.