diff --git a/lib/shared/dm-ops/add-members-spec.js b/lib/shared/dm-ops/add-members-spec.js index 10769ddec..73399fdeb 100644 --- a/lib/shared/dm-ops/add-members-spec.js +++ b/lib/shared/dm-ops/add-members-spec.js @@ -1,155 +1,144 @@ // @flow import invariant from 'invariant'; import uuid from 'uuid'; import { createRoleAndPermissionForThickThreads } from './create-thread-spec.js'; import type { DMOperationSpec, ProcessDMOperationUtilities, } from './dm-op-spec.js'; import type { DMAddMembersOperation } from '../../types/dm-ops.js'; import { messageTypes } from '../../types/message-types-enum.js'; -import { type RawMessageInfo } from '../../types/message-types.js'; import type { AddMembersMessageData } from '../../types/messages/add-members.js'; -import { - minimallyEncodeMemberInfo, - type ThickRawThreadInfo, -} from '../../types/minimally-encoded-thread-permissions-types.js'; +import { minimallyEncodeMemberInfo } from '../../types/minimally-encoded-thread-permissions-types.js'; import { joinThreadSubscription } from '../../types/subscription-types.js'; import type { ThickMemberInfo } from '../../types/thread-types.js'; import { updateTypes } from '../../types/update-types-enum.js'; -import type { ClientUpdateInfo } from '../../types/update-types.js'; import { values } from '../../utils/objects.js'; import { rawMessageInfoFromMessageData } from '../message-utils.js'; import { roleIsDefaultRole, userIsMember } from '../thread-utils.js'; -export type AddMembersResult = { - rawMessageInfos: Array, - updateInfos: Array, - threadInfo: ?ThickRawThreadInfo, -}; - function createAddNewMembersMessageDataFromDMOperation( dmOperation: DMAddMembersOperation, ): AddMembersMessageData { const { editorID, time, addedUserIDs, threadID } = dmOperation; return { type: messageTypes.ADD_MEMBERS, threadID, creatorID: editorID, time, addedUserIDs: [...addedUserIDs], }; } const addMembersSpec: DMOperationSpec = Object.freeze({ notificationsCreationData: async (dmOperation: DMAddMembersOperation) => { const messageData = createAddNewMembersMessageDataFromDMOperation(dmOperation); return { messageDatas: [messageData] }; }, processDMOperation: async ( dmOperation: DMAddMembersOperation, viewerID: string, utilities: ProcessDMOperationUtilities, ) => { const { editorID, time, messageID, addedUserIDs, threadID } = dmOperation; const messageData = createAddNewMembersMessageDataFromDMOperation(dmOperation); const rawMessageInfos = [ rawMessageInfoFromMessageData(messageData, messageID), ]; const currentThreadInfo = utilities.threadInfos[threadID]; if (!currentThreadInfo.thick) { return { rawMessageInfos: [], updateInfos: [], }; } const defaultRoleID = values(currentThreadInfo.roles).find(role => roleIsDefaultRole(role), )?.id; invariant(defaultRoleID, 'Default role ID must exist'); const { membershipPermissions } = createRoleAndPermissionForThickThreads( currentThreadInfo.type, currentThreadInfo.id, defaultRoleID, ); const memberTimestamps = { ...currentThreadInfo.timestamps.members }; const newMembers = []; for (const userID of addedUserIDs) { if (!memberTimestamps[userID]) { memberTimestamps[userID] = { isMember: time, subscription: currentThreadInfo.creationTime, }; } if (memberTimestamps[userID].isMember > time) { continue; } memberTimestamps[userID] = { ...memberTimestamps[userID], isMember: time, }; if (userIsMember(currentThreadInfo, userID)) { continue; } newMembers.push( minimallyEncodeMemberInfo({ id: userID, role: defaultRoleID, permissions: membershipPermissions, isSender: editorID === viewerID, subscription: joinThreadSubscription, }), ); } const resultThreadInfo = { ...currentThreadInfo, members: [...currentThreadInfo.members, ...newMembers], timestamps: { ...currentThreadInfo.timestamps, members: memberTimestamps, }, }; const updateInfos = [ { type: updateTypes.UPDATE_THREAD, id: uuid.v4(), time, threadInfo: resultThreadInfo, }, ]; return { rawMessageInfos, updateInfos, }; }, canBeProcessed( dmOperation: DMAddMembersOperation, viewerID: string, utilities: ProcessDMOperationUtilities, ) { if (utilities.threadInfos[dmOperation.threadID]) { return { isProcessingPossible: true }; } return { isProcessingPossible: false, reason: { type: 'missing_thread', threadID: dmOperation.threadID, }, }; }, supportsAutoRetry: true, }); export { addMembersSpec, createAddNewMembersMessageDataFromDMOperation }; diff --git a/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js b/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js index b7831ffeb..1a41711ae 100644 --- a/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js +++ b/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js @@ -1,111 +1,146 @@ // @flow import uuid from 'uuid'; -import type { AddMembersResult } from './add-members-spec.js'; import { createThickRawThreadInfo } from './create-thread-spec.js'; -import type { DMOperationSpec } from './dm-op-spec.js'; +import type { + DMOperationSpec, + ProcessDMOperationUtilities, +} from './dm-op-spec.js'; import type { DMAddViewerToThreadMembersOperation } from '../../types/dm-ops.js'; import { messageTypes } from '../../types/message-types-enum.js'; import { messageTruncationStatus } from '../../types/message-types.js'; import type { AddMembersMessageData } from '../../types/messages/add-members.js'; import { joinThreadSubscription } from '../../types/subscription-types.js'; import { updateTypes } from '../../types/update-types-enum.js'; import { rawMessageInfoFromMessageData } from '../message-utils.js'; +import { userIsMember } from '../thread-utils.js'; function createAddViewerToThreadMembersMessageDataFromDMOp( dmOperation: DMAddViewerToThreadMembersOperation, ): AddMembersMessageData { const { editorID, time, addedUserIDs, existingThreadDetails } = dmOperation; return { type: messageTypes.ADD_MEMBERS, threadID: existingThreadDetails.threadID, creatorID: editorID, time, addedUserIDs: [...addedUserIDs], }; } -function createAddViewerToThreadMembersResults( - dmOperation: DMAddViewerToThreadMembersOperation, - viewerID: string, -): AddMembersResult { - const { time, messageID, addedUserIDs, existingThreadDetails } = dmOperation; - const messageData = - createAddViewerToThreadMembersMessageDataFromDMOp(dmOperation); - - const rawMessageInfos = [ - rawMessageInfoFromMessageData(messageData, messageID), - ]; - - const resultThreadInfo = createThickRawThreadInfo( - { - ...existingThreadDetails, - allMemberIDsWithSubscriptions: [ - ...existingThreadDetails.allMemberIDsWithSubscriptions, - ...addedUserIDs.map(id => ({ - id, - subscription: joinThreadSubscription, - })), - ], - }, - viewerID, - ); - const updateInfos = [ - { - type: updateTypes.JOIN_THREAD, - id: uuid.v4(), - time, - threadInfo: resultThreadInfo, - rawMessageInfos, - truncationStatus: messageTruncationStatus.EXHAUSTIVE, - rawEntryInfos: [], - }, - ]; - return { - rawMessageInfos: [], - updateInfos, - threadInfo: resultThreadInfo, - }; -} - const addViewerToThreadMembersSpec: DMOperationSpec = Object.freeze({ notificationsCreationData: async ( dmOperation: DMAddViewerToThreadMembersOperation, ) => { const messageData = createAddViewerToThreadMembersMessageDataFromDMOp(dmOperation); return { messageDatas: [messageData] }; }, processDMOperation: async ( dmOperation: DMAddViewerToThreadMembersOperation, viewerID: string, + utilities: ProcessDMOperationUtilities, ) => { - const { rawMessageInfos, updateInfos } = - createAddViewerToThreadMembersResults(dmOperation, viewerID); + const { time, messageID, addedUserIDs, existingThreadDetails } = + dmOperation; + const messageData = + createAddViewerToThreadMembersMessageDataFromDMOp(dmOperation); + + const rawMessageInfos = [ + rawMessageInfoFromMessageData(messageData, messageID), + ]; + + const threadID = existingThreadDetails.threadID; + const currentThreadInfo = utilities.threadInfos[threadID]; + if (currentThreadInfo && !currentThreadInfo.thick) { + return { + rawMessageInfos: [], + updateInfos: [], + }; + } + + const memberTimestamps = { + ...currentThreadInfo?.timestamps?.members, + }; + const newMembers = []; + for (const userID of addedUserIDs) { + if (!memberTimestamps[userID]) { + memberTimestamps[userID] = { + isMember: time, + subscription: existingThreadDetails.creationTime, + }; + } + + if (memberTimestamps[userID].isMember > time) { + continue; + } + + memberTimestamps[userID] = { + ...memberTimestamps[userID], + isMember: time, + }; + + if (!userIsMember(currentThreadInfo, userID)) { + newMembers.push(userID); + } + } + + const resultThreadInfo = createThickRawThreadInfo( + { + ...existingThreadDetails, + allMemberIDsWithSubscriptions: [ + ...existingThreadDetails.allMemberIDsWithSubscriptions, + ...newMembers.map(id => ({ + id, + subscription: joinThreadSubscription, + })), + ], + }, + viewerID, + ); + const updateInfos = [ + { + type: updateTypes.JOIN_THREAD, + id: uuid.v4(), + time, + threadInfo: { + ...resultThreadInfo, + timestamps: { + ...resultThreadInfo.timestamps, + members: { + ...resultThreadInfo.timestamps.members, + ...memberTimestamps, + }, + }, + }, + rawMessageInfos, + truncationStatus: messageTruncationStatus.EXHAUSTIVE, + rawEntryInfos: [], + }, + ]; return { rawMessageInfos, updateInfos }; }, canBeProcessed( dmOperation: DMAddViewerToThreadMembersOperation, viewerID: string, ) { if (dmOperation.addedUserIDs.includes(viewerID)) { return { isProcessingPossible: true }; } console.log('Invalid DM operation', dmOperation); return { isProcessingPossible: false, reason: { type: 'invalid', }, }; }, supportsAutoRetry: true, }); export { addViewerToThreadMembersSpec, - createAddViewerToThreadMembersResults, createAddViewerToThreadMembersMessageDataFromDMOp, };