diff --git a/lib/actions/holder-actions.js b/lib/actions/holder-actions.js --- a/lib/actions/holder-actions.js +++ b/lib/actions/holder-actions.js @@ -1,11 +1,26 @@ // @flow +import invariant from 'invariant'; +import * as React from 'react'; + import blobService from '../facts/blob-service.js'; -import type { AuthMetadata } from '../shared/identity-client-context.js'; -import type { BlobHashAndHolder } from '../types/holder-types.js'; +import { + type AuthMetadata, + IdentityClientContext, +} from '../shared/identity-client-context.js'; +import type { + BlobHashAndHolder, + BlobOperation, +} from '../types/holder-types.js'; import { toBase64URL } from '../utils/base64.js'; -import { makeBlobServiceEndpointURL } from '../utils/blob-service.js'; +import { + makeBlobServiceEndpointURL, + generateBlobHolder, +} from '../utils/blob-service.js'; +import { useDispatchActionPromise } from '../utils/redux-promise-utils.js'; +import { useSelector } from '../utils/redux-utils.js'; import { createDefaultHTTPRequestHeaders } from '../utils/services-utils.js'; + type MultipleBlobHolders = $ReadOnlyArray; export const storeEstablishedHolderActionType = 'STORE_ESTABLISHED_HOLDER'; @@ -117,4 +132,69 @@ return { added, notAdded, removed, notRemoved }; } -export { processHoldersAction }; +function useProcessBlobHolders(): ( + blobOperations: $ReadOnlyArray, +) => Promise { + const identityContext = React.useContext(IdentityClientContext); + const getAuthMetadata = identityContext?.getAuthMetadata; + + const storedHolders = useSelector(state => state.holderStore.storedHolders); + const dispatchActionPromise = useDispatchActionPromise(); + + return React.useCallback( + async (ops: $ReadOnlyArray) => { + if (ops.length === 0) { + return; + } + + invariant(getAuthMetadata, 'Identity context not set'); + const authMetadata = await getAuthMetadata(); + + const holdersToAdd = ops + .map(({ blobHash, type }) => { + const status = storedHolders[blobHash]?.status; + if ( + type !== 'establish_holder' || + status === 'ESTABLISHED' || + status === 'PENDING_ESTABLISHMENT' + ) { + return null; + } + return { + blobHash, + holder: generateBlobHolder(authMetadata.deviceID), + }; + }) + .filter(Boolean); + + const holdersToRemove = ops + .map(({ blobHash, type }) => { + const holderInfo = storedHolders[blobHash]; + if ( + !holderInfo || + type !== 'remove_holder' || + holderInfo.status === 'PENDING_REMOVAL' + ) { + return null; + } + return { blobHash, holder: holderInfo.holder }; + }) + .filter(Boolean); + + const input = { + holdersToAdd, + holdersToRemove, + }; + const promise = processHoldersAction(input, authMetadata); + void dispatchActionPromise( + processHoldersActionTypes, + promise, + undefined, + input, + ); + }, + [dispatchActionPromise, getAuthMetadata, storedHolders], + ); +} + +export { useProcessBlobHolders }; 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 @@ -14,6 +14,7 @@ dmOperationSpecificationTypes, type OutboundComposableDMOperationSpecification, } from './dm-op-utils.js'; +import { useProcessBlobHolders } from '../../actions/holder-actions.js'; import { processNewUserIDsActionType } from '../../actions/user-actions.js'; import { useLoggedInUserInfo } from '../../hooks/account-hooks.js'; import { useGetLatestMessageEdit } from '../../hooks/latest-message-edit.js'; @@ -64,6 +65,7 @@ const viewerID = loggedInUserInfo?.id; const allPeerUserIDAndDeviceIDs = useSelector(getAllPeerUserIDAndDeviceIDs); const currentUserInfo = useSelector(state => state.currentUserInfo); + const processBlobHolders = useProcessBlobHolders(); const dispatch = useDispatch(); @@ -201,11 +203,30 @@ return await dmOpSpec.notificationsCreationData?.(dmOp, utilities); })(); - const [{ rawMessageInfos, updateInfos }, notificationsCreationData] = - await Promise.all([ - dmOpSpec.processDMOperation(dmOp, viewerID, utilities), - notificationsCreationDataPromise, - ]); + const [ + { rawMessageInfos, updateInfos, blobOps }, + notificationsCreationData, + ] = await Promise.all([ + dmOpSpec.processDMOperation(dmOp, viewerID, utilities), + notificationsCreationDataPromise, + ]); + + const holderOps = blobOps + .map(({ dmOpType, ...holderOp }) => { + if ( + (dmOpType === 'inbound_only' && + dmOperationSpecification.type === + dmOperationSpecificationTypes.OUTBOUND) || + (dmOpType === 'outbound_only' && + dmOperationSpecification.type === + dmOperationSpecificationTypes.INBOUND) + ) { + return null; + } + return holderOp; + }) + .filter(Boolean); + void processBlobHolders(holderOps); const { rawMessageInfos: allNewMessageInfos } = mergeUpdatesWithMessageInfos(rawMessageInfos, updateInfos); @@ -329,6 +350,7 @@ currentUserInfo, threadInfos, dispatch, + processBlobHolders, ], ); }