diff --git a/lib/shared/dm-ops/dm-ops-queue-handler.react.js b/lib/shared/dm-ops/dm-ops-queue-handler.react.js --- a/lib/shared/dm-ops/dm-ops-queue-handler.react.js +++ b/lib/shared/dm-ops/dm-ops-queue-handler.react.js @@ -4,11 +4,19 @@ import { dmOperationSpecificationTypes } from './dm-op-utils.js'; import { useProcessDMOperation } from './process-dm-ops.js'; -import { threadInfoSelector } from '../../selectors/thread-selectors.js'; +import { messageInfoSelector } from '../../selectors/chat-selectors.js'; import { + entryInfoSelector, + threadInfoSelector, +} from '../../selectors/thread-selectors.js'; +import { + clearQueuedEntryDMOpsActionType, + clearQueuedMembershipDMOpsActionType, + clearQueuedMessageDMOpsActionType, clearQueuedThreadDMOpsActionType, pruneDMOpsQueueActionType, } from '../../types/dm-ops.js'; +import { threadTypeIsThick } from '../../types/thread-types-enum.js'; import { useDispatch, useSelector } from '../../utils/redux-utils.js'; const PRUNING_FREQUENCY = 60 * 60 * 1000; @@ -40,12 +48,17 @@ const threadInfos = useSelector(threadInfoSelector); const threadIDs = React.useMemo( - () => new Set(Object.keys(threadInfos)), + () => + new Set( + Object.entries(threadInfos) + .filter(([, threadInfo]) => threadTypeIsThick(threadInfo.type)) + .map(([id]) => id), + ), [threadInfos], ); const prevThreadIDsRef = React.useRef<$ReadOnlySet>(new Set()); - const queuedOperations = useSelector( + const queuedThreadOperations = useSelector( state => state.queuedDMOperations.threadQueue, ); @@ -55,11 +68,11 @@ const prevThreadIDs = prevThreadIDsRef.current; prevThreadIDsRef.current = threadIDs; - for (const threadID in queuedOperations) { + for (const threadID in queuedThreadOperations) { if (!threadIDs.has(threadID) || prevThreadIDs.has(threadID)) { continue; } - for (const dmOp of queuedOperations[threadID]) { + for (const dmOp of queuedThreadOperations[threadID]) { void processDMOperation({ // This is `INBOUND` because we assume that when generating // `dmOperationSpecificationTypes.OUBOUND` it should be possible @@ -79,7 +92,161 @@ }, }); } - }, [dispatch, processDMOperation, queuedOperations, threadIDs]); + }, [dispatch, processDMOperation, queuedThreadOperations, threadIDs]); + + const messageInfos = useSelector(messageInfoSelector); + const messageIDs = React.useMemo( + () => + new Set( + Object.entries(messageInfos) + .filter( + ([, messageInfo]) => + messageInfo && + threadTypeIsThick(threadInfos[messageInfo.threadID]?.type), + ) + .map(([id]) => id), + ), + [messageInfos, threadInfos], + ); + const prevMessageIDsRef = React.useRef<$ReadOnlySet>(new Set()); + + const queuedMessageOperations = useSelector( + state => state.queuedDMOperations.messageQueue, + ); + + React.useEffect(() => { + const prevMessageIDs = prevMessageIDsRef.current; + prevMessageIDsRef.current = messageIDs; + + for (const messageID in queuedMessageOperations) { + if (!messageIDs.has(messageID) || prevMessageIDs.has(messageID)) { + continue; + } + + for (const dmOp of queuedMessageOperations[messageID]) { + void processDMOperation({ + // This is `INBOUND` because we assume that when generating + // `dmOperationSpecificationTypes.OUBOUND` it should be possible + // to be processed and never queued up. + type: dmOperationSpecificationTypes.INBOUND, + op: dmOp.operation, + // There is no metadata, because messages were confirmed when + // adding to the queue. + metadata: null, + }); + } + + dispatch({ + type: clearQueuedMessageDMOpsActionType, + payload: { + messageID, + }, + }); + } + }, [dispatch, messageIDs, processDMOperation, queuedMessageOperations]); + + const entryInfos = useSelector(entryInfoSelector); + const entryIDs = React.useMemo( + () => + new Set( + Object.entries(entryInfos) + .filter(([, entryInfo]) => + threadTypeIsThick(threadInfos[entryInfo.threadID]?.type), + ) + .map(([id]) => id), + ), + [entryInfos, threadInfos], + ); + const prevEntryIDsRef = React.useRef<$ReadOnlySet>(new Set()); + + const queuedEntryOperations = useSelector( + state => state.queuedDMOperations.entryQueue, + ); + + React.useEffect(() => { + const prevEntryIDs = prevEntryIDsRef.current; + prevEntryIDsRef.current = entryIDs; + + for (const entryID in queuedEntryOperations) { + if (!entryIDs.has(entryID) || prevEntryIDs.has(entryID)) { + continue; + } + + for (const dmOp of queuedEntryOperations[entryID]) { + void processDMOperation({ + // This is `INBOUND` because we assume that when generating + // `dmOperationSpecificationTypes.OUBOUND` it should be possible + // to be processed and never queued up. + type: dmOperationSpecificationTypes.INBOUND, + op: dmOp.operation, + // There is no metadata, because messages were confirmed when + // adding to the queue. + metadata: null, + }); + } + + dispatch({ + type: clearQueuedEntryDMOpsActionType, + payload: { + entryID, + }, + }); + } + }, [dispatch, entryIDs, processDMOperation, queuedEntryOperations]); + + const queuedMembershipOperations = useSelector( + state => state.queuedDMOperations.membershipQueue, + ); + + const runningMembershipOperations = React.useRef>>( + new Map(), + ); + React.useEffect(() => { + for (const threadID in queuedMembershipOperations) { + if (!threadInfos[threadID]) { + continue; + } + + const queuedMemberIDs = new Set( + Object.keys(queuedMembershipOperations[threadID]), + ); + if (!runningMembershipOperations.current.has(threadID)) { + runningMembershipOperations.current.set(threadID, new Set()); + } + for (const member of threadInfos[threadID].members) { + if ( + !queuedMemberIDs.has(member.id) || + runningMembershipOperations.current.get(threadID)?.has(member.id) + ) { + continue; + } + + runningMembershipOperations.current.get(threadID)?.add(member.id); + + for (const dmOp of queuedMembershipOperations[threadID][member.id]) { + void processDMOperation({ + // This is `INBOUND` because we assume that when generating + // `dmOperationSpecificationTypes.OUBOUND` it should be possible + // to be processed and never queued up. + type: dmOperationSpecificationTypes.INBOUND, + op: dmOp.operation, + // There is no metadata, because messages were confirmed when + // adding to the queue. + metadata: null, + }); + } + + dispatch({ + type: clearQueuedMembershipDMOpsActionType, + payload: { + threadID, + userID: member.id, + }, + }); + runningMembershipOperations.current.get(threadID)?.delete(member.id); + } + } + }, [dispatch, processDMOperation, queuedMembershipOperations, threadInfos]); return null; }