diff --git a/lib/shared/user-utils.js b/lib/shared/user-utils.js --- a/lib/shared/user-utils.js +++ b/lib/shared/user-utils.js @@ -2,13 +2,14 @@ import * as React from 'react'; +import { extractFIDFromUserID } from './id-utils.js'; import { roleIsAdminRole } from './thread-utils.js'; import { useResolvableNames } from '../hooks/names-cache.js'; import type { ThreadInfo, RawThreadInfo, } from '../types/minimally-encoded-thread-permissions-types.js'; -import type { UserInfo } from '../types/user-types.js'; +import type { AccountUserInfo, UserInfo } from '../types/user-types.js'; import { useSelector } from '../utils/redux-utils.js'; function ensNameForFarcasterUsername(farcasterUsername: string): string { @@ -68,9 +69,46 @@ return adminUserInfoWithENSName; } +function useFindExistingUserForFid(): ( + userInfo: AccountUserInfo, +) => ?AccountUserInfo { + const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos); + const userInfos = useSelector(state => state.userStore.userInfos); + + const fidToExistingUserIDMap: Map = React.useMemo(() => { + const map = new Map(); + for (const userID of Object.keys(auxUserInfos)) { + if (auxUserInfos[userID].fid) { + map.set(auxUserInfos[userID].fid, userID); + } + } + return map; + }, [auxUserInfos]); + + return React.useCallback( + (userInfo: AccountUserInfo) => { + const fid = extractFIDFromUserID(userInfo.id); + if (!fid) { + return null; + } + const userID = fidToExistingUserIDMap.get(fid); + if (!userID) { + return null; + } + return { + ...userInfo, + ...userInfos[userID], + username: userInfos[userID].username ?? userInfo.username, + }; + }, + [fidToExistingUserIDMap, userInfos], + ); +} + export { ensNameForFarcasterUsername, stringForUser, stringForUserExplicit, useKeyserverAdmin, + useFindExistingUserForFid, }; diff --git a/native/chat/message-list-thread-search.react.js b/native/chat/message-list-thread-search.react.js --- a/native/chat/message-list-thread-search.react.js +++ b/native/chat/message-list-thread-search.react.js @@ -6,6 +6,7 @@ import { useResolvableNames } from 'lib/hooks/names-cache.js'; import { extractFIDFromUserID } from 'lib/shared/id-utils.js'; import { notFriendNotice } from 'lib/shared/search-utils.js'; +import { useFindExistingUserForFid } from 'lib/shared/user-utils.js'; import type { AccountUserInfo, UserListItem } from 'lib/types/user-types.js'; import { useIsFarcasterDCsIntegrationEnabled } from 'lib/utils/services-utils.js'; @@ -63,17 +64,22 @@ }, [userSearchResults, userInfoInputArray]); const viewerID = useSelector(state => state.currentUserInfo?.id); + const findExistingUserForFid = useFindExistingUserForFid(); const onUserSelect = React.useCallback( - async (userInfo: AccountUserInfo) => { + async (selectedUserInfo: AccountUserInfo) => { for (const existingUserInfo of userInfoInputArray) { - if (userInfo.id === existingUserInfo.id) { + if (selectedUserInfo.id === existingUserInfo.id) { return; } } - const isFarcasterUser = - supportsFarcasterDCs && !!extractFIDFromUserID(userInfo.id); + const isFarcasterOnlyUser = + supportsFarcasterDCs && !!extractFIDFromUserID(selectedUserInfo.id); + let userInfo: AccountUserInfo = selectedUserInfo; + if (isFarcasterOnlyUser) { + userInfo = findExistingUserForFid(userInfo) ?? userInfo; + } if ( - (!isFarcasterUser && nonFriends.has(userInfo.id)) || + (!isFarcasterOnlyUser && nonFriends.has(userInfo.id)) || userInfo.id === viewerID ) { await resolveToUser(userInfo); @@ -84,13 +90,14 @@ updateTagInput(newUserInfoInputArray); }, [ - userInfoInputArray, + supportsFarcasterDCs, nonFriends, + viewerID, + userInfoInputArray, + updateUsernameInput, updateTagInput, + findExistingUserForFid, resolveToUser, - updateUsernameInput, - viewerID, - supportsFarcasterDCs, ], ); diff --git a/web/chat/chat-thread-composer.react.js b/web/chat/chat-thread-composer.react.js --- a/web/chat/chat-thread-composer.react.js +++ b/web/chat/chat-thread-composer.react.js @@ -25,6 +25,7 @@ } from 'lib/shared/thread-utils.js'; import { dmThreadProtocol } from 'lib/shared/threads/protocols/dm-thread-protocol.js'; import { getProtocolByName } from 'lib/shared/threads/protocols/thread-protocols.js'; +import { useFindExistingUserForFid } from 'lib/shared/user-utils.js'; import type { AccountUserInfo, UserListItem } from 'lib/types/user-types.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { useIsFarcasterDCsIntegrationEnabled } from 'lib/utils/services-utils.js'; @@ -109,22 +110,29 @@ selectedProtocol, ); + const findExistingUserForFid = useFindExistingUserForFid(); const onSelectUserFromSearch = React.useCallback( async (userListItem: UserListItem) => { - const { alert, notice, disabled, ...user } = userListItem; + const { alert, notice, disabled, supportedProtocols, ...user } = + userListItem; setUsernameInputText(''); - const isFarcasterUser = - isFarcasterDCsIntegrationEnabled && !!extractFIDFromUserID(user.id); + const fid = extractFIDFromUserID(user.id); + const isFarcasterOnlyUser = isFarcasterDCsIntegrationEnabled && !!fid; + let selectedUser: AccountUserInfo = user; + if (isFarcasterOnlyUser) { + selectedUser = findExistingUserForFid(user) ?? selectedUser; + } if ( - ((!isFarcasterUser && notice === notFriendNotice) || + ((!isFarcasterOnlyUser && notice === notFriendNotice) || user.id === viewerID) && userInfoInputArray.length === 0 ) { const newUserInfo = { - id: userListItem.id, - username: userListItem.username, + id: selectedUser.id, + username: selectedUser.username, }; - const newUserInfoInputArray = user.id === viewerID ? [] : [newUserInfo]; + const newUserInfoInputArray = + selectedUser.id === viewerID ? [] : [newUserInfo]; const threadInfo = existingThreadInfoFinderForCreatingThread({ searching: true, @@ -142,7 +150,7 @@ dispatch({ type: updateNavInfoActionType, payload: { - selectedUserList: [...userInfoInputArray, user], + selectedUserList: [...userInfoInputArray, selectedUser], }, }); } else { @@ -150,12 +158,13 @@ } }, [ + isFarcasterDCsIntegrationEnabled, viewerID, userInfoInputArray, + findExistingUserForFid, existingThreadInfoFinderForCreatingThread, dispatch, pushModal, - isFarcasterDCsIntegrationEnabled, ], );