From ce9e87df450e8aad3ec8440b25708092a668fc6b Mon Sep 17 00:00:00 2001 From: xsanm Date: Thu, 4 Dec 2025 16:05:27 +0100 Subject: [PATCH] [lib] support E2EE DMs only for Vodozemac clients Summary: Solution #3 from [ENG-11545](https://linear.app/comm/issue/ENG-11545/handle-olm-x3dh-bug). I had to move `useGetDeviceListsForUsers` because of dependency cycle. Test Plan: Set `NEXT_CODE_VERSION` for something different and test what `useUsersSupportThickThreads` returns for devices during searching users Reviewers: ashoat --- lib/hooks/peer-list-hooks.js | 34 +++---------------- lib/hooks/use-get-device-lists-for-users.js | 37 +++++++++++++++++++++ lib/hooks/user-identities-hooks.js | 32 ++++++++++++++---- 3 files changed, 67 insertions(+), 36 deletions(-) create mode 100644 lib/hooks/use-get-device-lists-for-users.js diff --git a/lib/hooks/peer-list-hooks.js b/lib/hooks/peer-list-hooks.js index 15a57ff928..1a919bbfb7 100644 --- a/lib/hooks/peer-list-hooks.js +++ b/lib/hooks/peer-list-hooks.js @@ -3,6 +3,7 @@ import invariant from 'invariant'; import * as React from 'react'; +import { useGetDeviceListsForUsers } from './use-get-device-lists-for-users.js'; import { setPeerDeviceListsActionType } from '../actions/aux-user-actions.js'; import { usePeerOlmSessionsCreatorContext } from '../components/peer-olm-session-creator-provider.react.js'; import { @@ -14,23 +15,21 @@ import { usePeerToPeerCommunication } from '../tunnelbroker/peer-to-peer-context import { useTunnelbroker } from '../tunnelbroker/tunnelbroker-context.js'; import { useResendPeerToPeerMessages } from '../tunnelbroker/use-resend-peer-to-peer-messages.js'; import type { - UsersRawDeviceLists, - UsersDevicesPlatformDetails, - SignedDeviceList, RawDeviceList, + SignedDeviceList, UserDevicesPlatformDetails, + UsersRawDeviceLists, } from '../types/identity-service-types.js'; import { type DeviceListUpdated, peerToPeerMessageTypes, } from '../types/tunnelbroker/peer-to-peer-message-types.js'; import { - userActionsP2PMessageTypes, type AccountDeletionP2PMessage, + userActionsP2PMessageTypes, } from '../types/tunnelbroker/user-actions-peer-to-peer-message-types.js'; import { getConfig } from '../utils/config.js'; import { getContentSigningKey } from '../utils/crypto-utils.js'; -import { convertSignedDeviceListsToRawDeviceLists } from '../utils/device-list-utils.js'; import { identityServiceQueryTimeout } from '../utils/identity-service.js'; import { values } from '../utils/objects.js'; import { useDispatch, useSelector } from '../utils/redux-utils.js'; @@ -42,30 +41,6 @@ export type PrimaryDeviceChange = { +newPrimaryDeviceID: string, }; -function useGetDeviceListsForUsers(): ( - userIDs: $ReadOnlyArray, -) => Promise<{ - +deviceLists: UsersRawDeviceLists, - +usersPlatformDetails: UsersDevicesPlatformDetails, -}> { - const client = React.useContext(IdentityClientContext); - const identityClient = client?.identityClient; - invariant(identityClient, 'Identity client should be set'); - return React.useCallback( - async (userIDs: $ReadOnlyArray) => { - const peersDeviceLists = - await identityClient.getDeviceListsForUsers(userIDs); - return { - deviceLists: convertSignedDeviceListsToRawDeviceLists( - peersDeviceLists.usersSignedDeviceLists, - ), - usersPlatformDetails: peersDeviceLists.usersDevicesPlatformDetails, - }; - }, - [identityClient], - ); -} - async function throwOnTimeout(userIDs: $ReadOnlyArray) { await sleep(identityServiceQueryTimeout); throw new Error(`Device list fetch for ${JSON.stringify(userIDs)} timed out`); @@ -350,7 +325,6 @@ function useResetRatchetState(): (userID: ?string) => Promise { } export { - useGetDeviceListsForUsers, useBroadcastDeviceListUpdates, useGetAndUpdateDeviceListsForUsers, useBroadcastAccountDeletion, diff --git a/lib/hooks/use-get-device-lists-for-users.js b/lib/hooks/use-get-device-lists-for-users.js new file mode 100644 index 0000000000..8d04fced98 --- /dev/null +++ b/lib/hooks/use-get-device-lists-for-users.js @@ -0,0 +1,37 @@ +// @flow + +import invariant from 'invariant'; +import * as React from 'react'; + +import { IdentityClientContext } from '../shared/identity-client-context.js'; +import type { + UsersDevicesPlatformDetails, + UsersRawDeviceLists, +} from '../types/identity-service-types.js'; +import { convertSignedDeviceListsToRawDeviceLists } from '../utils/device-list-utils.js'; + +function useGetDeviceListsForUsers(): ( + userIDs: $ReadOnlyArray, +) => Promise<{ + +deviceLists: UsersRawDeviceLists, + +usersPlatformDetails: UsersDevicesPlatformDetails, +}> { + const client = React.useContext(IdentityClientContext); + const identityClient = client?.identityClient; + invariant(identityClient, 'Identity client should be set'); + return React.useCallback( + async (userIDs: $ReadOnlyArray) => { + const peersDeviceLists = + await identityClient.getDeviceListsForUsers(userIDs); + return { + deviceLists: convertSignedDeviceListsToRawDeviceLists( + peersDeviceLists.usersSignedDeviceLists, + ), + usersPlatformDetails: peersDeviceLists.usersDevicesPlatformDetails, + }; + }, + [identityClient], + ); +} + +export { useGetDeviceListsForUsers }; diff --git a/lib/hooks/user-identities-hooks.js b/lib/hooks/user-identities-hooks.js index a8d60b3b83..4131598cbc 100644 --- a/lib/hooks/user-identities-hooks.js +++ b/lib/hooks/user-identities-hooks.js @@ -3,6 +3,7 @@ import invariant from 'invariant'; import * as React from 'react'; +import { useGetDeviceListsForUsers } from './use-get-device-lists-for-users.js'; import { useFindUserIdentities } from '../actions/find-user-identities-actions.js'; import { logTypes, useDebugLogs } from '../components/debug-logs-context.js'; import { extractFIDFromUserID } from '../shared/id-utils.js'; @@ -10,6 +11,7 @@ import { IdentityClientContext } from '../shared/identity-client-context.js'; import type { FarcasterUser } from '../types/identity-service-types.js'; import type { AccountUserInfo } from '../types/user-types.js'; import { getMessageForException } from '../utils/errors.js'; +import { olmSupportRequired } from '../utils/olm-support-required.js'; import { useSelector } from '../utils/redux-utils.js'; import { useIsFarcasterDCsIntegrationEnabled } from '../utils/services-utils.js'; @@ -17,6 +19,7 @@ function useUsersSupportThickThreads(): ( userIDs: $ReadOnlyArray, ) => Promise<$ReadOnlyMap> { const findUserIdentities = useFindUserIdentities(); + const getDeviceListsForUsers = useGetDeviceListsForUsers(); const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos); return React.useCallback( @@ -25,20 +28,37 @@ function useUsersSupportThickThreads(): ( const usersNeedingFetch = []; for (const userID of userIDs) { - if (auxUserInfos[userID]?.deviceList) { - usersSupportingThickThreads.set(userID, true); + const peerDeviceList = auxUserInfos[userID]?.deviceList?.devices; + const peerPlatformDetails = + auxUserInfos[userID]?.devicesPlatformDetails; + if (peerDeviceList && peerPlatformDetails) { + const supportsVodozemac = peerDeviceList.every( + deviceID => !olmSupportRequired(peerPlatformDetails?.[deviceID]), + ); + usersSupportingThickThreads.set(userID, supportsVodozemac); } else { usersNeedingFetch.push(userID); } } if (usersNeedingFetch.length > 0) { - const { identities, reservedUserIdentifiers } = - await findUserIdentities(usersNeedingFetch); + const [ + { identities, reservedUserIdentifiers }, + { deviceLists, usersPlatformDetails }, + ] = await Promise.all([ + findUserIdentities(usersNeedingFetch), + getDeviceListsForUsers(usersNeedingFetch), + ]); + for (const userID of usersNeedingFetch) { const isReserved = !!reservedUserIdentifiers[userID]; const doesNotExist = identities[userID] === undefined && !isReserved; if (identities[userID]) { - usersSupportingThickThreads.set(userID, true); + const peerDeviceList = deviceLists[userID]?.devices ?? []; + const peerPlatformDetails = usersPlatformDetails[userID]; + const supportsVodozemac = peerDeviceList.every( + deviceID => !olmSupportRequired(peerPlatformDetails?.[deviceID]), + ); + usersSupportingThickThreads.set(userID, supportsVodozemac); } else if (doesNotExist) { usersSupportingThickThreads.set(userID, undefined); } else { @@ -48,7 +68,7 @@ function useUsersSupportThickThreads(): ( } return usersSupportingThickThreads; }, - [auxUserInfos, findUserIdentities], + [auxUserInfos, findUserIdentities, getDeviceListsForUsers], ); }