diff --git a/lib/hooks/relationship-hooks.js b/lib/hooks/relationship-hooks.js --- a/lib/hooks/relationship-hooks.js +++ b/lib/hooks/relationship-hooks.js @@ -4,17 +4,14 @@ import uuid from 'uuid'; import { useAllowOlmViaTunnelbrokerForDMs } from './flag-hooks.js'; -import { useGetAndUpdateDeviceListsForUsers } from './peer-list-hooks.js'; import { useNewThickThread } from './thread-hooks.js'; +import { useFindUserIdentities } from '../actions/find-user-identities-actions.js'; import { updateRelationships as serverUpdateRelationships } from '../actions/relationship-actions.js'; import { useLegacyAshoatKeyserverCall } from '../keyserver-conn/legacy-keyserver-call.js'; import { pendingToRealizedThreadIDsSelector } from '../selectors/thread-selectors.js'; import { dmOperationSpecificationTypes } from '../shared/dm-ops/dm-op-utils.js'; import { useProcessAndSendDMOperation } from '../shared/dm-ops/process-dm-ops.js'; -import { - userHasDeviceList, - getPendingThreadID, -} from '../shared/thread-utils.js'; +import { getPendingThreadID } from '../shared/thread-utils.js'; import type { RelationshipOperation } from '../types/messages/update-relationship.js'; import type { AppState } from '../types/redux-types.js'; import { @@ -27,7 +24,6 @@ } from '../types/relationship-types.js'; import { threadTypes } from '../types/thread-types-enum.js'; import { useSelector } from '../utils/redux-utils.js'; -import sleep from '../utils/sleep.js'; type RobotextPlanForUser = | { +plan: 'send_to_thin_thread' } @@ -37,23 +33,6 @@ } | { +plan: 'send_to_new_thick_thread' }; -// We can't call processAndSendDMOperation until device lists are in -// AuxUserStore, but this hook needs to support users who haven't been fetched -// yet. We implement an effect that watches AuxUserStore after a fetch, so we -// know when we're ready to call processAndSendDMOperation. -type Step = - | { +step: 'ongoing' } - | { - +step: 'waiting_for_updated_device_lists', - +action: RelationshipAction, - +userIDs: $ReadOnlyArray, - +waitingForUserIDs: $ReadOnlyArray, - +resolve: RelationshipErrors => void, - +reject: Error => mixed, - }; - -const deviceListTimeout = 10 * 1000; // ten seconds - function useUpdateRelationships(): ( action: RelationshipAction, userIDs: $ReadOnlyArray, @@ -100,7 +79,6 @@ const updateRelationships = useLegacyAshoatKeyserverCall( serverUpdateRelationships, ); - const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos); const pendingToRealizedThreadIDs = useSelector((state: AppState) => pendingToRealizedThreadIDsSelector(state.threadStore.threadInfos), ); @@ -110,9 +88,8 @@ ); const createNewThickThread = useNewThickThread(); - // This callback contains the core of the logic. We extract it here because - // before we run it, we need to make sure auxUserInfos is correctly populated, - // and that might require waiting on a Redux action to be reduced + const findUserIdentities = useFindUserIdentities(); + const updateRelationshipsAndSendRobotext = React.useCallback( async (action: RelationshipAction, userIDs: $ReadOnlyArray) => { if (!viewerID) { @@ -121,10 +98,10 @@ ); return {}; } + const { identities } = await findUserIdentities(userIDs); const planForUsers = new Map(); for (const userID of userIDs) { - const supportsThickThreads = userHasDeviceList(userID, auxUserInfos); - if (!supportsThickThreads) { + if (!identities[userID]) { planForUsers.set(userID, { plan: 'send_to_thin_thread' }); continue; } @@ -243,7 +220,7 @@ [ viewerID, updateRelationships, - auxUserInfos, + findUserIdentities, pendingToRealizedThreadIDs, sendRobotextToThickThread, userInfos, @@ -252,131 +229,64 @@ ], ); - const [step, setStep] = React.useState(); - - // This hook watches AuxUserStore after a fetch to make sure we're ready to - // call processAndSendDMOperation. We can't do that from the returned - // callback, as it will have an old version of auxUserInfos bound into it. - React.useEffect(() => { - if (step?.step !== 'waiting_for_updated_device_lists') { - return; - } - const { action, userIDs, waitingForUserIDs, resolve, reject } = step; - for (const userID of waitingForUserIDs) { - const supportsThickThreads = userHasDeviceList(userID, auxUserInfos); - if (!supportsThickThreads) { - // It's safe to wait until every single user ID in waitingForUserIDs - // passes this check because we make the same check when populating - // waitingForUserIDs in the callback below - return; - } - } - setStep({ step: 'ongoing' }); - updateRelationshipsAndSendRobotext(action, userIDs).then(resolve, reject); - }, [step, auxUserInfos, updateRelationshipsAndSendRobotext]); + const [inProgress, setInProgress] = React.useState(false); const usingOlmViaTunnelbrokerForDMs = useAllowOlmViaTunnelbrokerForDMs(); - const getAndUpdateDeviceListsForUsers = useGetAndUpdateDeviceListsForUsers(); const coreFunctionality = React.useCallback( async (action: RelationshipAction, userIDs: $ReadOnlyArray) => { - // We only need to create robotext for FRIEND and FARCASTER_MUTUAL, - // so we skip the complexity below for other RelationshipActions if ( - !usingOlmViaTunnelbrokerForDMs || - (action !== relationshipActions.FRIEND && - action !== relationshipActions.FARCASTER_MUTUAL) + usingOlmViaTunnelbrokerForDMs && + (action === relationshipActions.FRIEND || + action === relationshipActions.FARCASTER_MUTUAL) ) { - let request: RelationshipRequest; - if (action === 'farcaster' || action === 'friend') { - const users = Object.fromEntries( - userIDs.map(userID => [ - userID, - { - createRobotextInThinThread: true, - }, - ]), - ); - request = { action, users }; - } else { - const users = Object.fromEntries(userIDs.map(userID => [userID, {}])); - request = { action, users }; - } - return await updateRelationships(request); - } - - const missingDeviceListsUserIDs: Array = []; - for (const userID of userIDs) { - const supportsThickThreads = userHasDeviceList(userID, auxUserInfos); - if (!supportsThickThreads) { - missingDeviceListsUserIDs.push(userID); - } + // We only need to create robotext for FRIEND and FARCASTER_MUTUAL, so + // we skip the complexity of updateRelationshipsAndSendRobotext for + // other RelationshipActions + return await updateRelationshipsAndSendRobotext(action, userIDs); } - if (missingDeviceListsUserIDs.length > 0) { - const deviceLists = await getAndUpdateDeviceListsForUsers( - missingDeviceListsUserIDs, - true, - ); - - const waitingForUserIDs: Array = []; - for (const userID of missingDeviceListsUserIDs) { - if (deviceLists[userID] && deviceLists[userID].devices.length > 0) { - waitingForUserIDs.push(userID); - } - } - - if (waitingForUserIDs.length > 0) { - const nextStepPromise = new Promise( - (resolve, reject) => { - setStep({ - step: 'waiting_for_updated_device_lists', - action, - userIDs, - waitingForUserIDs, - resolve, - reject, - }); + let request: RelationshipRequest; + if (action === 'farcaster' || action === 'friend') { + const users = Object.fromEntries( + userIDs.map(userID => [ + userID, + { + createRobotextInThinThread: true, }, - ); - return await Promise.race([ - nextStepPromise, - (async () => { - await sleep(deviceListTimeout); - throw new Error(`Fetch device lists timed out`); - })(), - ]); - } + ]), + ); + request = { action, users }; + } else { + const users = Object.fromEntries(userIDs.map(userID => [userID, {}])); + request = { action, users }; } - - return await updateRelationshipsAndSendRobotext(action, userIDs); + return await updateRelationships(request); }, [ - getAndUpdateDeviceListsForUsers, updateRelationshipsAndSendRobotext, updateRelationships, - auxUserInfos, usingOlmViaTunnelbrokerForDMs, ], ); return React.useCallback( async (action: RelationshipAction, userIDs: $ReadOnlyArray) => { - if (step) { + if (inProgress) { console.log( 'updateRelationships called from same component before last call ' + 'finished. ignoring', ); return {}; } - setStep({ step: 'ongoing' }); + setInProgress(true); try { return await coreFunctionality(action, userIDs); } finally { - setStep(null); + setInProgress(false); } }, - [step, coreFunctionality], + [inProgress, coreFunctionality], ); }