diff --git a/lib/handlers/initial-state-sharing-handler.react.js b/lib/handlers/initial-state-sharing-handler.react.js new file mode 100644 --- /dev/null +++ b/lib/handlers/initial-state-sharing-handler.react.js @@ -0,0 +1,89 @@ +// @flow + +import invariant from 'invariant'; +import * as React from 'react'; + +import { getOwnPeerDevices } from '../selectors/user-selectors.js'; +import { + dmOperationSpecificationTypes, + getCreateThickRawThreadInfoInputFromThreadInfo, +} from '../shared/dm-ops/dm-op-utils.js'; +import { useProcessAndSendDMOperation } from '../shared/dm-ops/process-dm-ops.js'; +import { IdentityClientContext } from '../shared/identity-client-context.js'; +import { values } from '../utils/objects.js'; +import { useSelector } from '../utils/redux-utils.js'; + +function InitialStateSharingHandler(): React.Node { + const userDeviceIDs = useSelector(state => + getOwnPeerDevices(state).map(peer => peer.deviceID), + ); + const threadInfos = useSelector(state => state.threadStore.threadInfos); + const viewerID = useSelector( + state => state.currentUserInfo && state.currentUserInfo.id, + ); + const identityContext = React.useContext(IdentityClientContext); + const processAndSendDMOperation = useProcessAndSendDMOperation(); + + const deviceIDs = React.useRef<$ReadOnlySet>(new Set(userDeviceIDs)); + + React.useEffect(() => { + void (async () => { + const oldDeviceIDs = deviceIDs.current; + const newDeviceIDs = new Set(userDeviceIDs); + deviceIDs.current = newDeviceIDs; + + invariant(identityContext, 'Identity context should be set'); + const authMetadata = await identityContext.getAuthMetadata(); + if (authMetadata.deviceID !== userDeviceIDs[0] || !viewerID) { + return; + } + + const recipients = new Set(); + for (const deviceID of newDeviceIDs) { + if (!oldDeviceIDs.has(deviceID)) { + recipients.add(deviceID); + } + } + + if (recipients.size === 0) { + return; + } + + for (const threadInfo of values(threadInfos)) { + if (!threadInfo.thick) { + continue; + } + + const existingThreadDetails = + getCreateThickRawThreadInfoInputFromThreadInfo(threadInfo); + const operation = { + type: 'add_viewer_to_thread_members', + existingThreadDetails, + editorID: viewerID, + time: threadInfo.creationTime, + messageID: null, + addedUserIDs: [], + }; + void processAndSendDMOperation({ + type: dmOperationSpecificationTypes.OUTBOUND, + op: operation, + recipients: { + type: 'some_devices', + deviceIDs: [...recipients], + }, + sendOnly: true, + }); + } + })(); + }, [ + identityContext, + processAndSendDMOperation, + threadInfos, + userDeviceIDs, + viewerID, + ]); + + return null; +} + +export { InitialStateSharingHandler }; 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 --- a/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js +++ b/lib/shared/dm-ops/add-viewer-to-thread-members-spec.js @@ -124,7 +124,13 @@ dmOperation: DMAddViewerToThreadMembersOperation, viewerID: string, ) { - if (dmOperation.addedUserIDs.includes(viewerID)) { + // We expect the viewer to be in the added users when the DM op + // is processed. An exception is for ops generated + // by InitialStateSharingHandler, which won't contain a messageID + if ( + dmOperation.addedUserIDs.includes(viewerID) || + !dmOperation.messageID + ) { return { isProcessingPossible: true }; } console.log('Invalid DM operation', dmOperation); 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 @@ -245,4 +245,8 @@ ); } -export { createMessagesToPeersFromDMOp, useAddDMThreadMembers }; +export { + createMessagesToPeersFromDMOp, + useAddDMThreadMembers, + getCreateThickRawThreadInfoInputFromThreadInfo, +}; diff --git a/native/root.react.js b/native/root.react.js --- a/native/root.react.js +++ b/native/root.react.js @@ -36,6 +36,7 @@ import { StaffContextProvider } from 'lib/components/staff-provider.react.js'; import { UserIdentityCacheProvider } from 'lib/components/user-identity-cache.react.js'; import { DBOpsHandler } from 'lib/handlers/db-ops-handler.react.js'; +import { InitialStateSharingHandler } from 'lib/handlers/initial-state-sharing-handler.react.js'; import { TunnelbrokerDeviceTokenHandler } from 'lib/handlers/tunnelbroker-device-token-handler.react.js'; import { UserInfosHandler } from 'lib/handlers/user-infos-handler.react.js'; import { IdentitySearchProvider } from 'lib/identity-search/identity-search-context.js'; @@ -381,6 +382,7 @@ + {navigation}