diff --git a/keyserver/src/push/send.js b/keyserver/src/push/send.js --- a/keyserver/src/push/send.js +++ b/keyserver/src/push/send.js @@ -12,7 +12,10 @@ import genesis from 'lib/facts/genesis.js'; import { oldValidUsernameRegex } from 'lib/shared/account-utils.js'; -import { isUserMentioned } from 'lib/shared/mention-utils.js'; +import { + isUserMentioned, + extractChatMentions, +} from 'lib/shared/mention-utils.js'; import { createMessageInfo, sortMessageInfoList, @@ -23,6 +26,7 @@ import { rawThreadInfoFromServerThreadInfo, threadInfoFromRawThreadInfo, + extractThreadID, } from 'lib/shared/thread-utils.js'; import type { Platform, PlatformDetails } from 'lib/types/device-types.js'; import { messageTypes } from 'lib/types/message-types-enum.js'; @@ -108,7 +112,12 @@ const [ unreadCounts, - { usersToCollapsableNotifInfo, serverThreadInfos, userInfos }, + { + usersToCollapsableNotifInfo, + serverThreadInfos, + userInfos, + userThreadIDs, + }, dbIDs, ] = await Promise.all([ getUnreadCounts(Object.keys(pushInfo)), @@ -180,13 +189,19 @@ continue; } const badgeOnly = !displayBanner && !userWasMentioned; - + const mentionableThreadIDs = await getMentionableThreads( + threadID, + userThreadIDs[userID], + userID, + ); const notifTargetUserInfo = { id: userID, username }; const notifTexts = await notifTextsForMessageInfo( allMessageInfos, - threadInfo, + threadID, notifTargetUserInfo, getENSNames, + mentionableThreadIDs, + threadInfos, ); if (!notifTexts) { continue; @@ -477,7 +492,6 @@ } } -// eslint-disable-next-line no-unused-vars async function getMentionableThreads( messageThreadID: string, mentionedThreadIDs: $ReadOnlySet<string>, @@ -596,8 +610,13 @@ const usersToCollapsableNotifInfo = await fetchCollapsableNotifs(pushInfo); const threadIDs = new Set(); + const userThreadIDs = {}; + const mentionThreadIDs = new Set(); const threadWithChangedNamesToMessages = new Map(); - const addThreadIDsFromMessageInfos = (rawMessageInfo: RawMessageInfo) => { + const addThreadIDsFromMessageInfos = ( + rawMessageInfo: RawMessageInfo, + userID: string, + ) => { const threadID = rawMessageInfo.threadID; threadIDs.add(threadID); const messageSpec = messageSpecs[rawMessageInfo.type]; @@ -617,14 +636,33 @@ threadWithChangedNamesToMessages.set(threadID, [rawMessageInfo.id]); } } + const unwrappedMessageInfo = + rawMessageInfo.type === messageTypes.SIDEBAR_SOURCE + ? rawMessageInfo.sourceMessage + : rawMessageInfo; + if ( + unwrappedMessageInfo && + unwrappedMessageInfo.type === messageTypes.TEXT + ) { + for (const { threadID: mentionedThreadID } of extractChatMentions( + unwrappedMessageInfo.text, + )) { + const transformedMentionedThreadID = extractThreadID(mentionedThreadID); + mentionThreadIDs.add(transformedMentionedThreadID); + userThreadIDs[userID].add(transformedMentionedThreadID); + } + } }; for (const userID in usersToCollapsableNotifInfo) { + if (!userThreadIDs[userID]) { + userThreadIDs[userID] = new Set(); + } for (const notifInfo of usersToCollapsableNotifInfo[userID]) { for (const rawMessageInfo of notifInfo.existingMessageInfos) { - addThreadIDsFromMessageInfos(rawMessageInfo); + addThreadIDsFromMessageInfos(rawMessageInfo, userID); } for (const rawMessageInfo of notifInfo.newMessageInfos) { - addThreadIDsFromMessageInfos(rawMessageInfo); + addThreadIDsFromMessageInfos(rawMessageInfo, userID); } } } @@ -632,6 +670,9 @@ const promises = {}; // These threadInfos won't have currentUser set promises.threadResult = fetchServerThreadInfos({ threadIDs }); + promises.mentionThreadResult = fetchServerThreadInfos({ + threadIDs: mentionThreadIDs, + }); if (threadWithChangedNamesToMessages.size > 0) { const typesThatAffectName = [ messageTypes.CHANGE_SETTINGS, @@ -664,7 +705,9 @@ promises.oldNames = dbQuery(oldNameQuery); } - const { threadResult, oldNames } = await promiseAll(promises); + const { threadResult, oldNames, mentionThreadResult } = await promiseAll( + promises, + ); const serverThreadInfos = threadResult.threadInfos; if (oldNames) { const [result] = oldNames; @@ -679,7 +722,15 @@ usersToCollapsableNotifInfo, ); - return { usersToCollapsableNotifInfo, serverThreadInfos, userInfos }; + return { + usersToCollapsableNotifInfo, + serverThreadInfos: { + ...serverThreadInfos, + ...mentionThreadResult.threadInfos, + }, + userInfos, + userThreadIDs, + }; } async function fetchNotifUserInfos( 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 @@ -51,6 +51,8 @@ threadInfo: ThreadInfo, ) => Promise<?NotifTexts>, +notifTargetUserInfo: UserInfo, + +threadInfos: { +[id: string]: ThreadInfo }, + +mentionableThreadIDs: $ReadOnlySet<string>, }; export type GeneratesNotifsParams = { diff --git a/lib/shared/messages/text-message-spec.js b/lib/shared/messages/text-message-spec.js --- a/lib/shared/messages/text-message-spec.js +++ b/lib/shared/messages/text-message-spec.js @@ -40,7 +40,7 @@ stripSpoilersFromNotifications, stripSpoilersFromMarkdownAST, } from '../markdown.js'; -import { isUserMentioned } from '../mention-utils.js'; +import { isUserMentioned, renderChatMentions } from '../mention-utils.js'; import { notifTextsForSidebarCreation } from '../notif-utils.js'; import { threadIsGroupChat, @@ -244,8 +244,13 @@ messageInfo.type === messageTypes.TEXT, 'messageInfo should be messageTypes.TEXT!', ); - const notificationTextWithoutSpoilers = stripSpoilersFromNotifications( + const notificationTextWithRenderedChatMentions = await renderChatMentions( messageInfo.text, + params.threadInfos, + params.mentionableThreadIDs, + ); + const notificationTextWithoutSpoilers = stripSpoilersFromNotifications( + notificationTextWithRenderedChatMentions, ); if (!threadInfo.name && !threadIsGroupChat(threadInfo)) { const thread = ET.thread({ display: 'uiName', threadInfo }); diff --git a/lib/shared/notif-utils.js b/lib/shared/notif-utils.js --- a/lib/shared/notif-utils.js +++ b/lib/shared/notif-utils.js @@ -34,15 +34,19 @@ async function notifTextsForMessageInfo( messageInfos: MessageInfo[], - threadInfo: ThreadInfo, + threadID: string, notifTargetUserInfo: UserInfo, getENSNames: ?GetENSNames, + mentionableThreadIDs: Set<string>, + threadInfos: { +[id: string]: ThreadInfo }, ): Promise<?ResolvedNotifTexts> { const fullNotifTexts = await fullNotifTextsForMessageInfo( messageInfos, - threadInfo, + threadID, notifTargetUserInfo, getENSNames, + mentionableThreadIDs, + threadInfos, ); if (!fullNotifTexts) { return fullNotifTexts; @@ -209,9 +213,11 @@ async function fullNotifTextsForMessageInfo( messageInfos: $ReadOnlyArray<MessageInfo>, - threadInfo: ThreadInfo, + threadID: string, notifTargetUserInfo: UserInfo, getENSNames: ?GetENSNames, + mentionableThreadIDs: $ReadOnlySet<string>, + threadInfos: { +[id: string]: ThreadInfo }, ): Promise<?ResolvedNotifTexts> { const mostRecentType = mostRecentMessageInfoType(messageInfos); const messageSpec = messageSpecs[mostRecentType]; @@ -225,14 +231,21 @@ ) => fullNotifTextsForMessageInfo( innerMessageInfos, - innerThreadInfo, + innerThreadInfo.id, notifTargetUserInfo, getENSNames, + mentionableThreadIDs, + threadInfos, ); const unresolvedNotifTexts = await messageSpec.notificationTexts( messageInfos, - threadInfo, - { notifTargetUserInfo, notificationTexts: innerNotificationTexts }, + threadInfos[threadID], + { + notifTargetUserInfo, + notificationTexts: innerNotificationTexts, + threadInfos, + mentionableThreadIDs, + }, ); if (!unresolvedNotifTexts) { return unresolvedNotifTexts;