diff --git a/lib/actions/user-actions.js b/lib/actions/user-actions.js --- a/lib/actions/user-actions.js +++ b/lib/actions/user-actions.js @@ -4,7 +4,6 @@ import * as React from 'react'; import { useClearAllHolders } from './holder-actions.js'; -import { useUserIdentityCache } from '../components/user-identity-cache.react.js'; import { useBroadcastDeviceListUpdates, useBroadcastAccountDeletion, @@ -60,10 +59,7 @@ } from '../types/avatar-types.js'; import type { DMChangeThreadSubscriptionOperation } from '../types/dm-ops'; import type { RawEntryInfo, CalendarQuery } from '../types/entry-types.js'; -import type { - UserIdentitiesResponse, - IdentityAuthResult, -} from '../types/identity-service-types.js'; +import type { IdentityAuthResult } from '../types/identity-service-types.js'; import type { RawMessageInfo, MessageTruncationStatuses, @@ -1378,19 +1374,6 @@ const processNewUserIDsActionType = 'PROCESS_NEW_USER_IDS'; -const findUserIdentitiesActionTypes = Object.freeze({ - started: 'FIND_USER_IDENTITIES_STARTED', - success: 'FIND_USER_IDENTITIES_SUCCESS', - failed: 'FIND_USER_IDENTITIES_FAILED', -}); - -function useFindUserIdentities(): ( - userIDs: $ReadOnlyArray, -) => Promise { - const userIdentityCache = useUserIdentityCache(); - return userIdentityCache.getUserIdentities; -} - const versionSupportedByIdentityActionTypes = Object.freeze({ started: 'VERSION_SUPPORTED_BY_IDENTITY_STARTED', success: 'VERSION_SUPPORTED_BY_IDENTITY_SUCCESS', @@ -1458,8 +1441,6 @@ identityGenerateNonceActionTypes, useIdentityGenerateNonce, processNewUserIDsActionType, - findUserIdentitiesActionTypes, - useFindUserIdentities, versionSupportedByIdentityActionTypes, useVersionSupportedByIdentity, }; diff --git a/lib/components/farcaster-data-handler.react.js b/lib/components/farcaster-data-handler.react.js --- a/lib/components/farcaster-data-handler.react.js +++ b/lib/components/farcaster-data-handler.react.js @@ -5,10 +5,10 @@ import { setAuxUserFIDsActionType } from '../actions/aux-user-actions.js'; import { updateRelationshipsActionTypes } from '../actions/relationship-actions.js'; import { setSyncedMetadataEntryActionType } from '../actions/synced-metadata-actions.js'; -import { useFindUserIdentities } from '../actions/user-actions.js'; import { NeynarClientContext } from '../components/neynar-client-provider.react.js'; import { useIsLoggedInToIdentityAndAuthoritativeKeyserver } from '../hooks/account-hooks.js'; import { useUpdateRelationships } from '../hooks/relationship-hooks.js'; +import { useFindUserIdentities } from '../hooks/user-identities-hooks.js'; import { IdentityClientContext } from '../shared/identity-client-context.js'; import { relationshipActions } from '../types/relationship-types.js'; import { syncedMetadataNames } from '../types/synced-metadata-types.js'; diff --git a/lib/handlers/user-infos-handler.react.js b/lib/handlers/user-infos-handler.react.js --- a/lib/handlers/user-infos-handler.react.js +++ b/lib/handlers/user-infos-handler.react.js @@ -4,13 +4,13 @@ import * as React from 'react'; import { updateRelationshipsActionTypes } from '../actions/relationship-actions.js'; -import { - useFindUserIdentities, - findUserIdentitiesActionTypes, -} from '../actions/user-actions.js'; import { useIsLoggedInToAuthoritativeKeyserver } from '../hooks/account-hooks.js'; import { useGetAndUpdateDeviceListsForUsers } from '../hooks/peer-list-hooks.js'; import { useUpdateRelationships } from '../hooks/relationship-hooks.js'; +import { + findUserIdentitiesActionTypes, + useFindUserIdentities, +} from '../hooks/user-identities-hooks.js'; import { usersWithMissingDeviceListSelector } from '../selectors/user-selectors.js'; import { IdentityClientContext } from '../shared/identity-client-context.js'; import { useTunnelbroker } from '../tunnelbroker/tunnelbroker-context.js'; diff --git a/lib/hooks/thread-search-hooks.js b/lib/hooks/thread-search-hooks.js --- a/lib/hooks/thread-search-hooks.js +++ b/lib/hooks/thread-search-hooks.js @@ -2,6 +2,7 @@ import * as React from 'react'; +import { useUserSupportThickThread } from './user-identities-hooks.js'; import { searchUsers as searchUserCall } from '../actions/user-actions.js'; import { useLegacyAshoatKeyserverCall } from '../keyserver-conn/legacy-keyserver-call.js'; import { useGlobalThreadSearchIndex } from '../selectors/nav-selectors.js'; @@ -14,9 +15,14 @@ import { useSelector } from '../utils/redux-utils.js'; import { usingCommServicesAccessToken } from '../utils/services-utils.js'; +export type UserSearchResult = $ReadOnly<{ + ...GlobalAccountUserInfo, + +supportThickThreads: boolean, +}>; + type ThreadListSearchResult = { +threadSearchResults: $ReadOnlySet, - +usersSearchResults: $ReadOnlyArray, + +usersSearchResults: $ReadOnlyArray, }; function useThreadListSearch( @@ -27,7 +33,7 @@ const forwardLookupSearchText = useForwardLookupSearchText(searchText); const filterAndSetUserResults = React.useCallback( - (userInfos: $ReadOnlyArray) => { + (userInfos: $ReadOnlyArray) => { const usersResults = userInfos.filter( info => !usersWithPersonalThread.has(info.id) && info.id !== viewerID, ); @@ -45,7 +51,12 @@ } const { userInfos } = await legacyCallSearchUsers(usernamePrefix); - filterAndSetUserResults(userInfos); + filterAndSetUserResults( + userInfos.map(userInfo => ({ + ...userInfo, + supportThickThreads: false, + })), + ); }, [filterAndSetUserResults, legacyCallSearchUsers], ); @@ -54,7 +65,7 @@ new Set(), ); const [usersSearchResults, setUsersSearchResults] = React.useState< - $ReadOnlyArray, + $ReadOnlyArray, >([]); const threadSearchIndex = useGlobalThreadSearchIndex(); React.useEffect(() => { @@ -72,12 +83,24 @@ legacySearchUsers, ]); + const usersSupportsThickThreads = useUserSupportThickThread(); const identitySearchUsers = useSearchUsers(forwardLookupSearchText); React.useEffect(() => { - if (usingCommServicesAccessToken) { - filterAndSetUserResults(identitySearchUsers); - } - }, [filterAndSetUserResults, identitySearchUsers]); + void (async () => { + if (usingCommServicesAccessToken) { + const userIDsSupportingThickThreads = await usersSupportsThickThreads( + identitySearchUsers.map(user => user.id), + ); + const set = new Set(userIDsSupportingThickThreads); + filterAndSetUserResults( + identitySearchUsers.map(search => ({ + ...search, + supportThickThreads: set.has(search.id), + })), + ); + } + })(); + }, [filterAndSetUserResults, identitySearchUsers, usersSupportsThickThreads]); return { threadSearchResults, usersSearchResults }; } diff --git a/lib/hooks/user-identities-hooks.js b/lib/hooks/user-identities-hooks.js new file mode 100644 --- /dev/null +++ b/lib/hooks/user-identities-hooks.js @@ -0,0 +1,63 @@ +// @flow + +import * as React from 'react'; + +import { useAllowOlmViaTunnelbrokerForDMs } from './flag-hooks.js'; +import { useUserIdentityCache } from '../components/user-identity-cache.react.js'; +import { userHasDeviceList } from '../shared/thread-utils.js'; +import type { UserIdentitiesResponse } from '../types/identity-service-types.js'; +import { useSelector } from '../utils/redux-utils.js'; + +const findUserIdentitiesActionTypes = Object.freeze({ + started: 'FIND_USER_IDENTITIES_STARTED', + success: 'FIND_USER_IDENTITIES_SUCCESS', + failed: 'FIND_USER_IDENTITIES_FAILED', +}); + +function useFindUserIdentities(): ( + userIDs: $ReadOnlyArray, +) => Promise { + const userIdentityCache = useUserIdentityCache(); + return userIdentityCache.getUserIdentities; +} + +function useUserSupportThickThread(): ( + userIDs: $ReadOnlyArray, +) => Promise<$ReadOnlyArray> { + const findUserIdentities = useFindUserIdentities(); + const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos); + const allowOlmViaTunnelbrokerForDMs = useAllowOlmViaTunnelbrokerForDMs(); + + return React.useCallback( + async (userIDs: $ReadOnlyArray) => { + if (!allowOlmViaTunnelbrokerForDMs) { + return []; + } + const usersSupportingThickThreads = []; + const usersNeedsFetch = []; + for (const userID of userIDs) { + if (userHasDeviceList(userID, auxUserInfos)) { + usersSupportingThickThreads.push(userID); + } else { + usersNeedsFetch.push(userID); + } + } + if (usersNeedsFetch.length > 0) { + const { identities } = await findUserIdentities(usersNeedsFetch); + for (const userID of usersNeedsFetch) { + if (identities[userID]) { + usersSupportingThickThreads.push(userID); + } + } + } + return usersSupportingThickThreads; + }, + [allowOlmViaTunnelbrokerForDMs, auxUserInfos, findUserIdentities], + ); +} + +export { + useUserSupportThickThread, + useFindUserIdentities, + findUserIdentitiesActionTypes, +}; diff --git a/lib/reducers/user-reducer.js b/lib/reducers/user-reducer.js --- a/lib/reducers/user-reducer.js +++ b/lib/reducers/user-reducer.js @@ -12,7 +12,6 @@ } from '../actions/thread-actions.js'; import { fetchPendingUpdatesActionTypes } from '../actions/update-actions.js'; import { - findUserIdentitiesActionTypes, processNewUserIDsActionType, identityLogInActionTypes, identityRegisterActionTypes, @@ -24,6 +23,7 @@ setUserSettingsActionTypes, updateUserAvatarActionTypes, } from '../actions/user-actions.js'; +import { findUserIdentitiesActionTypes } from '../hooks/user-identities-hooks.js'; import { extractKeyserverIDFromIDOptional } from '../keyserver-conn/keyserver-call-utils.js'; import { setNewSessionActionType } from '../keyserver-conn/keyserver-conn-types.js'; import { 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 @@ -8,10 +8,10 @@ import { type ProcessDMOperationUtilities } from './dm-op-spec.js'; import { dmOpSpecs } from './dm-op-specs.js'; import { useProcessAndSendDMOperation } from './process-dm-ops.js'; -import { useFindUserIdentities } from '../../actions/user-actions.js'; import { useLoggedInUserInfo } from '../../hooks/account-hooks.js'; import { useGetLatestMessageEdit } from '../../hooks/latest-message-edit.js'; import { useGetAndUpdateDeviceListsForUsers } from '../../hooks/peer-list-hooks.js'; +import { useFindUserIdentities } from '../../hooks/user-identities-hooks.js'; import { mergeUpdatesWithMessageInfos } from '../../reducers/message-reducer.js'; import { getAllPeerUserIDAndDeviceIDs } from '../../selectors/user-selectors.js'; import { type P2PMessageRecipient } from '../../tunnelbroker/peer-to-peer-context.js'; diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js --- a/lib/shared/thread-utils.js +++ b/lib/shared/thread-utils.js @@ -16,6 +16,8 @@ import genesis from '../facts/genesis.js'; import { useLoggedInUserInfo } from '../hooks/account-hooks.js'; import { useAllowOlmViaTunnelbrokerForDMs } from '../hooks/flag-hooks.js'; +import { type UserSearchResult } from '../hooks/thread-search-hooks.js'; +import { useUserSupportThickThread } from '../hooks/user-identities-hooks.js'; import { extractKeyserverIDFromIDOptional } from '../keyserver-conn/keyserver-call-utils.js'; import { hasPermission, @@ -93,7 +95,6 @@ import { updateTypes } from '../types/update-types-enum.js'; import { type ClientUpdateInfo } from '../types/update-types.js'; import type { - GlobalAccountUserInfo, UserInfos, AccountUserInfo, LoggedInUserInfo, @@ -602,14 +603,14 @@ loggedInUserInfo: LoggedInUserInfo, userID: string, username: ?string, - allowOlmViaTunnelbrokerForDMs: boolean, + supportThickThreads: boolean, ): PendingPersonalThread { const pendingPersonalThreadUserInfo = { id: userID, username: username, }; - const threadType = allowOlmViaTunnelbrokerForDMs + const threadType = supportThickThreads ? threadTypes.PERSONAL : threadTypes.GENESIS_PERSONAL; @@ -625,14 +626,14 @@ function createPendingThreadItem( loggedInUserInfo: LoggedInUserInfo, user: UserIDAndUsername, - allowOlmViaTunnelbrokerForDMs: boolean, + supportThickThreads: boolean, ): ChatThreadItem { const { threadInfo, pendingPersonalThreadUserInfo } = createPendingPersonalThread( loggedInUserInfo, user.id, user.username, - allowOlmViaTunnelbrokerForDMs, + supportThickThreads, ); return { @@ -1438,9 +1439,8 @@ searchText: string, threadFilter: ThreadInfo => boolean, threadSearchResults: $ReadOnlySet, - usersSearchResults: $ReadOnlyArray, + usersSearchResults: $ReadOnlyArray, loggedInUserInfo: ?LoggedInUserInfo, - allowOlmViaTunnelbrokerForDMs: boolean, ): $ReadOnlyArray { if (!searchText) { return chatListData.filter( @@ -1476,7 +1476,7 @@ createPendingThreadItem( loggedInUserInfo, user, - allowOlmViaTunnelbrokerForDMs, + user.supportThickThreads, ), ), ); @@ -1710,7 +1710,19 @@ const usersWithPersonalThread = useSelector(usersWithPersonalThreadSelector); - const allowOlmViaTunnelbrokerForDMs = useAllowOlmViaTunnelbrokerForDMs(); + const [supportThickThreads, setSupportThickThreads] = React.useState(false); + const usersSupportsThickThreads = useUserSupportThickThread(); + + React.useEffect(() => { + void (async () => { + if (!userInfo) { + setSupportThickThreads(false); + return; + } + const [result] = await usersSupportsThickThreads([userInfo.id]); + setSupportThickThreads(!!result); + })(); + }, [userInfo, usersSupportsThickThreads]); return React.useMemo(() => { if (!loggedInUserInfo || !userID || !username) { @@ -1736,16 +1748,16 @@ loggedInUserInfo, userID, username, - allowOlmViaTunnelbrokerForDMs, + supportThickThreads, ); return pendingPersonalThreadInfo; }, [ - allowOlmViaTunnelbrokerForDMs, isViewerProfile, loggedInUserInfo, personalThreadInfos, privateThreadInfos, + supportThickThreads, userID, username, usersWithPersonalThread, diff --git a/native/chat/chat-thread-list.react.js b/native/chat/chat-thread-list.react.js --- a/native/chat/chat-thread-list.react.js +++ b/native/chat/chat-thread-list.react.js @@ -279,8 +279,6 @@ ], ); - const allowOlmViaTunnelbrokerForDMs = useAllowOlmViaTunnelbrokerForDMs(); - const listData: $ReadOnlyArray = React.useMemo(() => { const chatThreadItems = getThreadListSearchResults( boundChatListData, @@ -289,7 +287,6 @@ threadSearchResults, usersSearchResults, loggedInUserInfo, - allowOlmViaTunnelbrokerForDMs, ); const chatItems: Item[] = [...chatThreadItems]; @@ -304,7 +301,6 @@ return chatItems; }, [ - allowOlmViaTunnelbrokerForDMs, boundChatListData, emptyItem, filterThreads, diff --git a/web/chat/thread-list-provider.js b/web/chat/thread-list-provider.js --- a/web/chat/thread-list-provider.js +++ b/web/chat/thread-list-provider.js @@ -4,7 +4,6 @@ import * as React from 'react'; import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js'; -import { useAllowOlmViaTunnelbrokerForDMs } from 'lib/hooks/flag-hooks.js'; import { useThreadListSearch } from 'lib/hooks/thread-search-hooks.js'; import { type ChatThreadItem, @@ -180,7 +179,6 @@ ); const threadFilter = activeTab === 'Muted' ? threadInBackgroundChatList : threadInHomeChatList; - const allowOlmViaTunnelbrokerForDMs = useAllowOlmViaTunnelbrokerForDMs(); const chatListDataWithoutFilter = getThreadListSearchResults( chatListData, searchText, @@ -188,7 +186,6 @@ threadSearchResults, usersSearchResults, loggedInUserInfo, - allowOlmViaTunnelbrokerForDMs, ); const activeTopLevelChatThreadItem = useChatThreadItem( activeTopLevelThreadInfo,