diff --git a/lib/hooks/message-author.js b/lib/hooks/message-author.js new file mode 100644 --- /dev/null +++ b/lib/hooks/message-author.js @@ -0,0 +1,59 @@ +// @flow + +import * as React from 'react'; + +import { useBaseGetLatestMessageEdit } from './latest-message-edit.js'; +import type { MessageStore } from '../types/message-types.js'; +import { messageIDIsThick } from '../types/message-types.js'; +import { useSelector } from '../utils/redux-utils.js'; + +const messageAuthorCache = new Map(); + +function useGetMessageAuthor(): ( + messageID: string, +) => ?string | Promise { + const messageStore = useSelector(state => state.messageStore); + const baseGetMessageAuthor = useBaseGetMessageAuthor(); + return React.useCallback( + (messageID: string) => baseGetMessageAuthor(messageID, messageStore), + [baseGetMessageAuthor, messageStore], + ); +} + +function useBaseGetMessageAuthor(): ( + messageID: string, + messageStore: MessageStore, +) => ?string | Promise { + const baseGetLatestMessageEdit = useBaseGetLatestMessageEdit(); + return React.useCallback( + (messageID: string, messageStore: MessageStore) => { + const messageFromRedux = messageStore.messages[messageID]; + if (messageFromRedux) { + return messageFromRedux.creatorID; + } + if (!messageIDIsThick(messageID)) { + // For thin threads, everything in SQLite should also be in Redux, so + // there is no point trying to query SQLite if it's not in Redux + return null; + } + const messageAuthorFromCache = messageAuthorCache.get(messageID); + if (messageAuthorFromCache) { + return messageAuthorFromCache; + } + return (async () => { + const rawMessageInfo = await baseGetLatestMessageEdit( + messageID, + messageStore, + ); + const creatorID = rawMessageInfo?.creatorID; + if (creatorID) { + messageAuthorCache.set(messageID, creatorID); + } + return creatorID; + })(); + }, + [baseGetLatestMessageEdit], + ); +} + +export { useGetMessageAuthor, useBaseGetMessageAuthor }; diff --git a/lib/hooks/message-hooks.js b/lib/hooks/message-hooks.js --- a/lib/hooks/message-hooks.js +++ b/lib/hooks/message-hooks.js @@ -2,7 +2,7 @@ import * as React from 'react'; -import { useGetLatestMessageEdit } from './latest-message-edit.js'; +import { useGetMessageAuthor } from './message-author.js'; import { messageInfoSelector } from '../selectors/chat-selectors.js'; import { getOldestNonLocalMessageID, @@ -40,7 +40,7 @@ const messageInfos = useSelector(messageInfoSelector); const messageStore = useSelector(state => state.messageStore); const viewerID = useSelector(state => state.currentUserInfo?.id); - const fetchMessage = useGetLatestMessageEdit(); + const getMessageAuthor = useGetMessageAuthor(); return React.useCallback( async threadInfo => { if (!viewerID) { @@ -53,7 +53,7 @@ const showInMessagePreviewParams = { threadInfo, viewerID, - fetchMessage, + getMessageAuthor, }; let mostRecentMessageInfo; for (const messageID of thread.messageIDs) { @@ -92,7 +92,7 @@ shouldFetchOlderMessages: true, }; }, - [messageInfos, messageStore, viewerID, fetchMessage], + [messageInfos, messageStore, viewerID, getMessageAuthor], ); } diff --git a/lib/hooks/thread-time.js b/lib/hooks/thread-time.js --- a/lib/hooks/thread-time.js +++ b/lib/hooks/thread-time.js @@ -2,7 +2,7 @@ import * as React from 'react'; -import { useBaseGetLatestMessageEdit } from './latest-message-edit.js'; +import { useBaseGetMessageAuthor } from './message-author.js'; import { messageSpecs } from '../shared/messages/message-specs.js'; import type { MessageInfo, @@ -19,7 +19,7 @@ messages: { +[id: string]: ?MessageInfo | RawMessageInfo }, ) => LastUpdatedTimes { const viewerID = useSelector(state => state.currentUserInfo?.id); - const baseGetLatestMessageEdit = useBaseGetLatestMessageEdit(); + const baseGetMessageAuthor = useBaseGetMessageAuthor(); return React.useCallback( (threadInfo, messageStore, messages) => { // This callback returns three variables: @@ -47,8 +47,8 @@ const getLastUpdatedTimeParams = { threadInfo, viewerID, - fetchMessage: (messageID: string) => - baseGetLatestMessageEdit(messageID, messageStore), + getMessageAuthor: (messageID: string) => + baseGetMessageAuthor(messageID, messageStore), }; let lastUpdatedTime: ?() => Promise; @@ -128,7 +128,7 @@ lastUpdatedTime: lastUpdatedWithFallback, }; }, - [viewerID, baseGetLatestMessageEdit], + [viewerID, baseGetMessageAuthor], ); } diff --git a/lib/shared/messages/message-spec.js b/lib/shared/messages/message-spec.js --- a/lib/shared/messages/message-spec.js +++ b/lib/shared/messages/message-spec.js @@ -85,7 +85,7 @@ export type ShowInMessagePreviewParams = { +threadInfo: ThreadInfo, +viewerID: string, - +fetchMessage: (messageID: string) => Promise, + +getMessageAuthor: (messageID: string) => ?string | Promise, }; export type MessageSpec = { diff --git a/lib/shared/messages/reaction-message-spec.js b/lib/shared/messages/reaction-message-spec.js --- a/lib/shared/messages/reaction-message-spec.js +++ b/lib/shared/messages/reaction-message-spec.js @@ -232,17 +232,23 @@ validator: rawReactionMessageInfoValidator, - showInMessagePreview: async ( + showInMessagePreview: ( messageInfo: ReactionMessageInfo, params: ShowInMessagePreviewParams, ) => { - const originalMessage = await params.fetchMessage( + const getOriginalMessageAuthorResult = params.getMessageAuthor( messageInfo.targetMessageID, ); - if (!originalMessage) { + if (!getOriginalMessageAuthorResult) { return false; } - return originalMessage.creatorID === params.viewerID; + if (typeof getOriginalMessageAuthorResult === 'string') { + return getOriginalMessageAuthorResult === params.viewerID; + } + return (async () => { + const originalMessageAuthor = await getOriginalMessageAuthorResult; + return originalMessageAuthor === params.viewerID; + })(); }, getLastUpdatedTime: @@ -251,13 +257,19 @@ params: ShowInMessagePreviewParams, ) => async () => { - const originalMessage = await params.fetchMessage( + const getOriginalMessageAuthorResult = params.getMessageAuthor( messageInfo.targetMessageID, ); - if (!originalMessage) { + if (!getOriginalMessageAuthorResult) { return null; } - return originalMessage.creatorID === params.viewerID + if (typeof getOriginalMessageAuthorResult === 'string') { + return getOriginalMessageAuthorResult === params.viewerID + ? messageInfo.time + : null; + } + const originalMessageAuthor = await getOriginalMessageAuthorResult; + return originalMessageAuthor === params.viewerID ? messageInfo.time : null; }, diff --git a/lib/types/message-types.js b/lib/types/message-types.js --- a/lib/types/message-types.js +++ b/lib/types/message-types.js @@ -144,7 +144,13 @@ import { rawUpdateRelationshipMessageInfoValidator } from './messages/update-relationship.js'; import { type RelativeUserInfo, type UserInfos } from './user-types.js'; import { values } from '../utils/objects.js'; -import { tID, tNumber, tShape, tUserID } from '../utils/validation-utils.js'; +import { + tID, + tNumber, + tShape, + tUserID, + thickIDRegex, +} from '../utils/validation-utils.js'; const composableMessageTypes = new Set([ messageTypes.TEXT, @@ -709,3 +715,7 @@ +messages: $ReadOnlyArray, +endReached: boolean, }; + +export function messageIDIsThick(messageID: string): boolean { + return thickIDRegex.test(messageID); +}