diff --git a/lib/shared/dm-ops/change-thread-settings-spec.js b/lib/shared/dm-ops/change-thread-settings-spec.js --- a/lib/shared/dm-ops/change-thread-settings-spec.js +++ b/lib/shared/dm-ops/change-thread-settings-spec.js @@ -6,6 +6,7 @@ import type { DMOperationSpec, ProcessDMOperationUtilities, + ProcessingPossibilityCheckResult, } from './dm-op-spec.js'; import type { DMBlobOperation, @@ -198,16 +199,51 @@ dmOperation: DMChangeThreadSettingsOperation, utilities: ProcessDMOperationUtilities, ) => { - if (utilities.threadInfos[dmOperation.threadID]) { + const { fieldNameToMessageData } = + createChangeSettingsMessageDatasAndUpdate(dmOperation); + const messageIDConflictCheckPromises: $ReadOnlyArray< + Promise, + > = values(fieldNameToMessageData).map(async ({ rawMessageInfo }) => { + if (!rawMessageInfo.id) { + return { isProcessingPossible: true }; + } + const messageID = rawMessageInfo.id; + const message = await utilities.fetchMessage(messageID); + if (message) { + console.log( + `Discarded a ${dmOperation.type} operation because ` + + `message with the same ID ${messageID} already exists ` + + 'in the store', + ); + return { + isProcessingPossible: false, + reason: { + type: 'invalid', + }, + }; + } return { isProcessingPossible: true }; + }); + + const messageIDConflictCheckResults = await Promise.all( + messageIDConflictCheckPromises, + ); + for (const result of messageIDConflictCheckResults) { + if (!result.isProcessingPossible) { + return result; + } } - return { - isProcessingPossible: false, - reason: { - type: 'missing_thread', - threadID: dmOperation.threadID, - }, - }; + + if (!utilities.threadInfos[dmOperation.threadID]) { + return { + isProcessingPossible: false, + reason: { + type: 'missing_thread', + threadID: dmOperation.threadID, + }, + }; + } + return { isProcessingPossible: true }; }, supportsAutoRetry: true, }); diff --git a/lib/shared/dm-ops/create-entry-spec.js b/lib/shared/dm-ops/create-entry-spec.js --- a/lib/shared/dm-ops/create-entry-spec.js +++ b/lib/shared/dm-ops/create-entry-spec.js @@ -79,6 +79,18 @@ dmOperation: DMCreateEntryOperation, utilities: ProcessDMOperationUtilities, ) => { + if (utilities.entryInfos[dmOperation.entryID]) { + console.log( + 'Discarded a CREATE_ENTRY operation because entry with ' + + `the same ID ${dmOperation.entryID} already exists in the store`, + ); + return { + isProcessingPossible: false, + reason: { + type: 'invalid', + }, + }; + } if (utilities.threadInfos[dmOperation.threadID]) { return { isProcessingPossible: true }; } diff --git a/lib/shared/dm-ops/create-sidebar-spec.js b/lib/shared/dm-ops/create-sidebar-spec.js --- a/lib/shared/dm-ops/create-sidebar-spec.js +++ b/lib/shared/dm-ops/create-sidebar-spec.js @@ -188,9 +188,57 @@ dmOperation: DMCreateSidebarOperation, utilities: ProcessDMOperationUtilities, ) => { - const sourceMessage = await utilities.fetchMessage( - dmOperation.sourceMessageID, - ); + if (utilities.threadInfos[dmOperation.threadID]) { + console.log( + 'Discarded a CREATE_SIDEBAR operation because thread ' + + `with the same ID ${dmOperation.threadID} already exists ` + + 'in the store', + ); + return { + isProcessingPossible: false, + reason: { + type: 'invalid', + }, + }; + } + + const [createSidebarMessage, sidebarSourceMessage, sourceMessage] = + await Promise.all( + [ + dmOperation.newCreateSidebarMessageID, + dmOperation.newSidebarSourceMessageID, + dmOperation.sourceMessageID, + ].map(utilities.fetchMessage), + ); + + if (createSidebarMessage) { + console.log( + `Discarded a ${dmOperation.type} operation because ` + + `message with the same ID ${dmOperation.newCreateSidebarMessageID} ` + + 'already exists in the store', + ); + return { + isProcessingPossible: false, + reason: { + type: 'invalid', + }, + }; + } + + if (sidebarSourceMessage) { + console.log( + `Discarded a ${dmOperation.type} operation because ` + + `message with the same ID ${dmOperation.newSidebarSourceMessageID} ` + + 'already exists in the store', + ); + return { + isProcessingPossible: false, + reason: { + type: 'invalid', + }, + }; + } + if (!sourceMessage) { return { isProcessingPossible: false, diff --git a/lib/shared/dm-ops/create-thread-spec.js b/lib/shared/dm-ops/create-thread-spec.js --- a/lib/shared/dm-ops/create-thread-spec.js +++ b/lib/shared/dm-ops/create-thread-spec.js @@ -289,7 +289,23 @@ blobOps: [], }; }, - canBeProcessed: async () => { + canBeProcessed: async ( + dmOperation: DMCreateThreadOperation, + utilities: ProcessDMOperationUtilities, + ) => { + if (utilities.threadInfos[dmOperation.threadID]) { + console.log( + 'Discarded a CREATE_THREAD operation because thread ' + + `with the same ID ${dmOperation.threadID} already exists ` + + 'in the store', + ); + return { + isProcessingPossible: false, + reason: { + type: 'invalid', + }, + }; + } return { isProcessingPossible: true }; }, supportsAutoRetry: true, diff --git a/lib/shared/dm-ops/dm-op-spec.js b/lib/shared/dm-ops/dm-op-spec.js --- a/lib/shared/dm-ops/dm-op-spec.js +++ b/lib/shared/dm-ops/dm-op-spec.js @@ -18,7 +18,7 @@ ) => Promise, }; -type ProcessingPossibilityCheckResult = +export type ProcessingPossibilityCheckResult = | { +isProcessingPossible: true } | { +isProcessingPossible: false, 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 @@ -5,7 +5,10 @@ import * as React from 'react'; import uuid from 'uuid'; -import { type ProcessDMOperationUtilities } from './dm-op-spec.js'; +import { + type ProcessDMOperationUtilities, + type ProcessingPossibilityCheckResult, +} 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'; @@ -359,9 +362,39 @@ return newUpdateInfos; } +async function checkMessageIDConflict( + dmOperation: DMOperation, + utilities: ProcessDMOperationUtilities, +): Promise { + if (!dmOperation.messageID) { + return { + isProcessingPossible: true, + }; + } + const messageID = dmOperation.messageID; + const message = await utilities.fetchMessage(messageID); + if (message) { + console.log( + `Discarded a ${dmOperation.type} operation because ` + + `message with the same ID ${messageID} already exists ` + + 'in the store', + ); + return { + isProcessingPossible: false, + reason: { + type: 'invalid', + }, + }; + } + return { + isProcessingPossible: true, + }; +} + export { createMessagesToPeersFromDMOp, useAddDMThreadMembers, getCreateThickRawThreadInfoInputFromThreadInfo, getThreadUpdatesForNewMessages, + checkMessageIDConflict, }; 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 @@ -12,6 +12,7 @@ dmOperationSpecificationTypes, type OutboundComposableDMOperationSpecification, getThreadUpdatesForNewMessages, + checkMessageIDConflict, } from './dm-op-utils.js'; import { useProcessBlobHolders } from '../../actions/holder-actions.js'; import { @@ -146,6 +147,14 @@ return; } + const messageIDConflictCheckResult = await checkMessageIDConflict( + dmOp, + utilities, + ); + if (!messageIDConflictCheckResult.isProcessingPossible) { + return; + } + const processingCheckResult = await dmOpSpecs[dmOp.type].canBeProcessed( dmOp, utilities, diff --git a/lib/shared/dm-ops/send-edit-message-spec.js b/lib/shared/dm-ops/send-edit-message-spec.js --- a/lib/shared/dm-ops/send-edit-message-spec.js +++ b/lib/shared/dm-ops/send-edit-message-spec.js @@ -51,8 +51,10 @@ dmOperation: DMSendEditMessageOperation, utilities: ProcessDMOperationUtilities, ) => { - const message = await utilities.fetchMessage(dmOperation.targetMessageID); - if (!message) { + const targetMessage = await utilities.fetchMessage( + dmOperation.targetMessageID, + ); + if (!targetMessage) { return { isProcessingPossible: false, reason: { diff --git a/lib/shared/dm-ops/send-multimedia-message-spec.js b/lib/shared/dm-ops/send-multimedia-message-spec.js --- a/lib/shared/dm-ops/send-multimedia-message-spec.js +++ b/lib/shared/dm-ops/send-multimedia-message-spec.js @@ -89,16 +89,16 @@ dmOperation: DMSendMultimediaMessageOperation, utilities: ProcessDMOperationUtilities, ) => { - if (utilities.threadInfos[dmOperation.threadID]) { - return { isProcessingPossible: true }; + if (!utilities.threadInfos[dmOperation.threadID]) { + return { + isProcessingPossible: false, + reason: { + type: 'missing_thread', + threadID: dmOperation.threadID, + }, + }; } - return { - isProcessingPossible: false, - reason: { - type: 'missing_thread', - threadID: dmOperation.threadID, - }, - }; + return { isProcessingPossible: true }; }, supportsAutoRetry: false, }); diff --git a/lib/shared/dm-ops/send-reaction-message-spec.js b/lib/shared/dm-ops/send-reaction-message-spec.js --- a/lib/shared/dm-ops/send-reaction-message-spec.js +++ b/lib/shared/dm-ops/send-reaction-message-spec.js @@ -59,8 +59,10 @@ dmOperation: DMSendReactionMessageOperation, utilities: ProcessDMOperationUtilities, ) => { - const message = await utilities.fetchMessage(dmOperation.targetMessageID); - if (!message) { + const targetMessage = await utilities.fetchMessage( + dmOperation.targetMessageID, + ); + if (!targetMessage) { return { isProcessingPossible: false, reason: { diff --git a/lib/shared/dm-ops/send-text-message-spec.js b/lib/shared/dm-ops/send-text-message-spec.js --- a/lib/shared/dm-ops/send-text-message-spec.js +++ b/lib/shared/dm-ops/send-text-message-spec.js @@ -50,16 +50,16 @@ dmOperation: DMSendTextMessageOperation, utilities: ProcessDMOperationUtilities, ) => { - if (utilities.threadInfos[dmOperation.threadID]) { - return { isProcessingPossible: true }; + if (!utilities.threadInfos[dmOperation.threadID]) { + return { + isProcessingPossible: false, + reason: { + type: 'missing_thread', + threadID: dmOperation.threadID, + }, + }; } - return { - isProcessingPossible: false, - reason: { - type: 'missing_thread', - threadID: dmOperation.threadID, - }, - }; + return { isProcessingPossible: true }; }, supportsAutoRetry: false, });