diff --git a/lib/shared/dm-ops/dm-op-utils.js b/lib/shared/dm-ops/dm-op-utils.js --- a/lib/shared/dm-ops/dm-op-utils.js +++ b/lib/shared/dm-ops/dm-op-utils.js @@ -1,11 +1,14 @@ // @flow import invariant from 'invariant'; +import _groupBy from 'lodash/fp/groupBy.js'; import * as React from 'react'; import uuid from 'uuid'; +import { type ProcessDMOperationUtilities } from './dm-op-spec.js'; import { dmOpSpecs } from './dm-op-specs.js'; import { useProcessAndSendDMOperation } from './process-dm-ops.js'; +import { mergeUpdatesWithMessageInfos } from '../../reducers/message-reducer.js'; import type { CreateThickRawThreadInfoInput, DMAddMembersOperation, @@ -13,7 +16,9 @@ DMOperation, ComposableDMOperation, } from '../../types/dm-ops.js'; +import type { RawMessageInfo } from '../../types/message-types.js'; import type { + RawThreadInfo, ThickRawThreadInfo, ThreadInfo, } from '../../types/minimally-encoded-thread-permissions-types.js'; @@ -26,14 +31,17 @@ assertThickThreadType, thickThreadTypes, } from '../../types/thread-types-enum.js'; -import type { RawThreadInfos } from '../../types/thread-types.js'; +import type { LegacyRawThreadInfo } from '../../types/thread-types.js'; import { type DMOperationP2PMessage, userActionsP2PMessageTypes, } from '../../types/tunnelbroker/user-actions-peer-to-peer-message-types.js'; -import type { CurrentUserInfo } from '../../types/user-types.js'; +import { updateTypes } from '../../types/update-types-enum.js'; +import type { ClientUpdateInfo } from '../../types/update-types.js'; import { getContentSigningKey } from '../../utils/crypto-utils.js'; import { useSelector } from '../../utils/redux-utils.js'; +import { messageSpecs } from '../messages/message-specs.js'; +import { updateSpecs } from '../updates/update-specs.js'; function generateMessagesToPeers( message: DMOperation, @@ -113,17 +121,17 @@ +userID: string, +deviceID: string, }>, - currentUserInfo: ?CurrentUserInfo, - threadInfos: RawThreadInfos, + utilities: ProcessDMOperationUtilities, ): Promise<$ReadOnlyArray> { - if (!currentUserInfo?.id) { + const { viewerID, threadInfos } = utilities; + if (!viewerID) { return []; } let peerUserIDAndDeviceIDs = allPeerUserIDAndDeviceIDs; if (recipients.type === 'self_devices') { peerUserIDAndDeviceIDs = allPeerUserIDAndDeviceIDs.filter( - peer => peer.userID === currentUserInfo.id, + peer => peer.userID === viewerID, ); } else if (recipients.type === 'some_users') { const userIDs = new Set(recipients.userIDs); @@ -259,8 +267,93 @@ ); } +function getThreadUpdatesForNewMessages( + rawMessageInfos: $ReadOnlyArray, + updateInfos: $ReadOnlyArray, + utilities: ProcessDMOperationUtilities, +): Array { + const { threadInfos, viewerID } = utilities; + + const { rawMessageInfos: allNewMessageInfos } = mergeUpdatesWithMessageInfos( + rawMessageInfos, + updateInfos, + ); + const messagesByThreadID = _groupBy(message => message.threadID)( + allNewMessageInfos, + ); + + const updatedThreadInfosByThreadID: { + [string]: RawThreadInfo | LegacyRawThreadInfo, + } = {}; + for (const threadID in messagesByThreadID) { + updatedThreadInfosByThreadID[threadID] = threadInfos[threadID]; + } + for (const update of updateInfos) { + const updatedThreadInfo = updateSpecs[update.type].getUpdatedThreadInfo?.( + update, + updatedThreadInfosByThreadID, + ); + if (updatedThreadInfo) { + updatedThreadInfosByThreadID[updatedThreadInfo.id] = updatedThreadInfo; + } + } + + const newUpdateInfos: Array = []; + for (const threadID in messagesByThreadID) { + const repliesCountIncreasingMessages = messagesByThreadID[threadID].filter( + message => messageSpecs[message.type].includedInRepliesCount, + ); + + let threadInfo = updatedThreadInfosByThreadID[threadID]; + + if (repliesCountIncreasingMessages.length > 0) { + const repliesCountIncreaseTime = Math.max( + repliesCountIncreasingMessages.map(message => message.time), + ); + const newThreadInfo = { + ...threadInfo, + repliesCount: + threadInfo.repliesCount + repliesCountIncreasingMessages.length, + }; + newUpdateInfos.push({ + type: updateTypes.UPDATE_THREAD, + id: uuid.v4(), + time: repliesCountIncreaseTime, + threadInfo: newThreadInfo, + }); + threadInfo = newThreadInfo; + } + + const messagesFromOtherPeers = messagesByThreadID[threadID].filter( + message => message.creatorID !== viewerID, + ); + if (messagesFromOtherPeers.length === 0) { + continue; + } + // We take the most recent timestamp to make sure that + // change_thread_read_status operation older + // than it won't flip the status to read. + const time = Math.max(messagesFromOtherPeers.map(message => message.time)); + invariant(threadInfo.thick, 'Thread should be thick'); + + // We aren't checking if the unread timestamp is lower than the time. + // We're doing this because we want to flip the thread to unread after + // any new message from a non-viewer. + newUpdateInfos.push({ + type: updateTypes.UPDATE_THREAD_READ_STATUS, + id: uuid.v4(), + time, + threadID: threadInfo.id, + unread: true, + }); + } + + return newUpdateInfos; +} + export { createMessagesToPeersFromDMOp, useAddDMThreadMembers, getCreateThickRawThreadInfoInputFromThreadInfo, + getThreadUpdatesForNewMessages, }; diff --git a/lib/shared/dm-ops/process-dm-ops.js b/lib/shared/dm-ops/process-dm-ops.js --- a/lib/shared/dm-ops/process-dm-ops.js +++ b/lib/shared/dm-ops/process-dm-ops.js @@ -1,9 +1,7 @@ // @flow import invariant from 'invariant'; -import _groupBy from 'lodash/fp/groupBy.js'; import * as React from 'react'; -import uuid from 'uuid'; import type { ProcessDMOperationUtilities } from './dm-op-spec.js'; import { dmOpSpecs } from './dm-op-specs.js'; @@ -13,6 +11,7 @@ createMessagesToPeersFromDMOp, dmOperationSpecificationTypes, type OutboundComposableDMOperationSpecification, + getThreadUpdatesForNewMessages, } from './dm-op-utils.js'; import { processNewUserIDsActionType, @@ -21,7 +20,6 @@ import { useLoggedInUserInfo } from '../../hooks/account-hooks.js'; import { useGetLatestMessageEdit } from '../../hooks/latest-message-edit.js'; import { useDispatchWithMetadata } from '../../hooks/ops-hooks.js'; -import { mergeUpdatesWithMessageInfos } from '../../reducers/message-reducer.js'; import { getAllPeerUserIDAndDeviceIDs } from '../../selectors/user-selectors.js'; import { usePeerToPeerCommunication, @@ -32,16 +30,11 @@ queueDMOpsActionType, dmOperationValidator, } from '../../types/dm-ops.js'; -import type { RawThreadInfo } from '../../types/minimally-encoded-thread-permissions-types.js'; import type { NotificationsCreationData } from '../../types/notif-types.js'; import type { DispatchMetadata } from '../../types/redux-types.js'; import type { OutboundP2PMessage } from '../../types/sqlite-types.js'; -import type { LegacyRawThreadInfo } from '../../types/thread-types.js'; -import { updateTypes } from '../../types/update-types-enum.js'; import { extractUserIDsFromPayload } from '../../utils/conversion-utils.js'; import { useSelector, useDispatch } from '../../utils/redux-utils.js'; -import { messageSpecs } from '../messages/message-specs.js'; -import { updateSpecs } from '../updates/update-specs.js'; function useSendDMOperationUtils() { const fetchMessage = useGetLatestMessageEdit(); @@ -66,12 +59,9 @@ dmOperationSpecification: DMOperationSpecification, dmOpID: ?string, ) => Promise { - const threadInfos = useSelector(state => state.threadStore.threadInfos); const baseUtilities = useSendDMOperationUtils(); const dispatchWithMetadata = useDispatchWithMetadata(); const allPeerUserIDAndDeviceIDs = useSelector(getAllPeerUserIDAndDeviceIDs); - const currentUserInfo = useSelector(state => state.currentUserInfo); - const dispatch = useDispatch(); return React.useCallback( @@ -99,8 +89,7 @@ dmOp, dmOperationSpecification.recipients, allPeerUserIDAndDeviceIDs, - currentUserInfo, - threadInfos, + utilities, ); } @@ -226,75 +215,14 @@ notificationsCreationDataPromise, ]); - const { rawMessageInfos: allNewMessageInfos } = - mergeUpdatesWithMessageInfos(rawMessageInfos, updateInfos); - const messagesByThreadID = _groupBy(message => message.threadID)( - allNewMessageInfos, + const newUpdateInfos = getThreadUpdatesForNewMessages( + rawMessageInfos, + updateInfos, + utilities, ); - const updatedThreadInfosByThreadID: { - [string]: RawThreadInfo | LegacyRawThreadInfo, - } = {}; - for (const threadID in messagesByThreadID) { - updatedThreadInfosByThreadID[threadID] = threadInfos[threadID]; - } - for (const update of updateInfos) { - const updatedThreadInfo = updateSpecs[ - update.type - ].getUpdatedThreadInfo?.(update, updatedThreadInfosByThreadID); - if (updatedThreadInfo) { - updatedThreadInfosByThreadID[updatedThreadInfo.id] = - updatedThreadInfo; - } - } - - for (const threadID in messagesByThreadID) { - const repliesCountIncreasingMessages = messagesByThreadID[ - threadID - ].filter(message => messageSpecs[message.type].includedInRepliesCount); - - const threadInfo = updatedThreadInfosByThreadID[threadID]; - - if (repliesCountIncreasingMessages.length > 0) { - const repliesCountIncreaseTime = Math.max( - repliesCountIncreasingMessages.map(message => message.time), - ); - updateInfos.push({ - type: updateTypes.UPDATE_THREAD, - id: uuid.v4(), - time: repliesCountIncreaseTime, - threadInfo: { - ...threadInfo, - repliesCount: - threadInfo.repliesCount + repliesCountIncreasingMessages.length, - }, - }); - } - - const messagesFromOtherPeers = messagesByThreadID[threadID].filter( - message => message.creatorID !== viewerID, - ); - if (messagesFromOtherPeers.length === 0) { - continue; - } - // We take the most recent timestamp to make sure that - // change_thread_read_status operation older - // than it won't flip the status to read. - const time = Math.max( - messagesFromOtherPeers.map(message => message.time), - ); - invariant(threadInfo.thick, 'Thread should be thick'); - - // We aren't checking if the unread timestamp is lower than the time. - // We're doing this because we want to flip the thread to unread after - // any new message from a non-viewer. - updateInfos.push({ - type: updateTypes.UPDATE_THREAD_READ_STATUS, - id: uuid.v4(), - time, - threadID: threadInfo.id, - unread: true, - }); + if (newUpdateInfos.length > 0) { + updateInfos.push(...newUpdateInfos); } dispatchWithMetadata( @@ -311,14 +239,7 @@ dispatchMetadata, ); }, - [ - baseUtilities, - dispatchWithMetadata, - allPeerUserIDAndDeviceIDs, - currentUserInfo, - threadInfos, - dispatch, - ], + [baseUtilities, dispatchWithMetadata, allPeerUserIDAndDeviceIDs, dispatch], ); } @@ -341,11 +262,9 @@ function useSendComposableDMOperation(): ( dmOperationSpecification: OutboundComposableDMOperationSpecification, ) => Promise { - const threadInfos = useSelector(state => state.threadStore.threadInfos); const { getDMOpsSendingPromise } = usePeerToPeerCommunication(); const dispatchWithMetadata = useDispatchWithMetadata(); const allPeerUserIDAndDeviceIDs = useSelector(getAllPeerUserIDAndDeviceIDs); - const currentUserInfo = useSelector(state => state.currentUserInfo); const baseUtilities = useSendDMOperationUtils(); const { processOutboundMessages } = usePeerToPeerCommunication(); const localMessageInfos = useSelector(state => state.messageStore.local); @@ -396,8 +315,7 @@ op, recipients, allPeerUserIDAndDeviceIDs, - currentUserInfo, - threadInfos, + utilities, ); const notificationsCreationData = await dmOpSpecs[ @@ -434,12 +352,10 @@ }, [ allPeerUserIDAndDeviceIDs, - currentUserInfo, dispatchWithMetadata, getDMOpsSendingPromise, localMessageInfos, processOutboundMessages, - threadInfos, baseUtilities, ], );