diff --git a/lib/hooks/ens-cache.js b/lib/hooks/ens-cache.js --- a/lib/hooks/ens-cache.js +++ b/lib/hooks/ens-cache.js @@ -3,11 +3,59 @@ import invariant from 'invariant'; import * as React from 'react'; +import { useFCNames } from './fc-cache.js'; import { ENSCacheContext } from '../components/ens-cache-provider.react.js'; import { getETHAddressForUserInfo } from '../shared/account-utils.js'; +import { extractFIDFromUserID } from '../shared/id-utils.js'; import { stringForUser } from '../shared/user-utils.js'; import { getENSNames } from '../utils/ens-helpers.js'; +type BaseResolvableUserInfo = { + +id?: ?string, + +username?: ?string, + +fid?: ?string, + +farcasterUsername?: ?string, + ... +}; +type UseResolvableNamesOptions = { + +allAtOnce?: ?boolean, +}; +function useResolvableNames( + users: $ReadOnlyArray, + options?: ?UseResolvableNamesOptions, +): T[] { + const [fcUsers, ensUsers] = React.useMemo(() => { + const fcItems: Array = []; + const ensItems: Array = []; + for (const user of users) { + if (user?.fid) { + fcItems.push(user); + continue; + } + if (!user?.id) { + ensItems.push(user); + continue; + } + const fid = extractFIDFromUserID(user.id); + if (fid) { + fcItems.push({ ...user, fid }); + } else { + ensItems.push(user); + } + } + return [fcItems, ensItems]; + }, [users]); + + const resolvedFCNames = useFCNames(fcUsers, options).map(user => { + if (user?.username || !user?.farcasterUsername) { + return user; + } + return { ...user, username: user.farcasterUsername }; + }); + const resolvedENSNames = useENSNames(ensUsers, options); + return [...resolvedENSNames, ...resolvedFCNames]; +} + type BaseUserInfo = { +username?: ?string, ... }; type UseENSNamesOptions = { +allAtOnce?: ?boolean, @@ -225,4 +273,5 @@ useStringForUser, useENSAvatar, useSortedENSResolvedUsers, + useResolvableNames, }; diff --git a/lib/selectors/nav-selectors.js b/lib/selectors/nav-selectors.js --- a/lib/selectors/nav-selectors.js +++ b/lib/selectors/nav-selectors.js @@ -3,7 +3,7 @@ import * as React from 'react'; import { createSelector } from 'reselect'; -import { useENSNames } from '../hooks/ens-cache.js'; +import { useResolvableNames } from '../hooks/ens-cache.js'; import SearchIndex from '../shared/search-index.js'; import { threadTypeIsPrivate } from '../shared/threads/thread-specs.js'; import type { Platform } from '../types/device-types.js'; @@ -76,15 +76,18 @@ ); // Without allAtOnce, useThreadSearchIndex and useUserSearchIndex are very -// expensive. useENSNames would trigger their recalculation for each ENS name -// as it streams in, but we would prefer to trigger their recaculation just -// once for every update of the underlying Redux data. -const useENSNamesOptions = { allAtOnce: true }; +// expensive. useResolvableNames would trigger their recalculation for each +// ENS name as it streams in, but we would prefer to trigger their +// recaculation just once for every update of the underlying Redux data. +const useResolvableNamesOptions = { allAtOnce: true }; function useUserSearchIndex( userInfos: $ReadOnlyArray, ): SearchIndex { - const membersWithENSNames = useENSNames(userInfos, useENSNamesOptions); + const membersWithENSNames = useResolvableNames( + userInfos, + useResolvableNamesOptions, + ); const memberMap = React.useMemo(() => { const result = new Map(); @@ -144,9 +147,9 @@ return [...allMembersOfAllThreads.values()]; }, [threadInfos, userInfos, viewerID]); - const nonViewerMembersWithENSNames = useENSNames( + const nonViewerMembersWithENSNames = useResolvableNames( nonViewerMembers, - useENSNamesOptions, + useResolvableNamesOptions, ); const memberMap = React.useMemo(() => { diff --git a/lib/shared/markdown.js b/lib/shared/markdown.js --- a/lib/shared/markdown.js +++ b/lib/shared/markdown.js @@ -7,7 +7,7 @@ markdownUserMentionRegex, decodeChatMentionText, } from './mention-utils.js'; -import { useENSNames } from '../hooks/ens-cache.js'; +import { useResolvableNames } from '../hooks/ens-cache.js'; import type { RelativeMemberInfo, ResolvedThreadInfo, @@ -199,7 +199,7 @@ }; } -const useENSNamesOptions = { allAtOnce: true }; +const useResolvableNamesOptions = { allAtOnce: true }; function useMemberMapForUserMentions( members: $ReadOnlyArray, ): $ReadOnlyMap { @@ -208,7 +208,10 @@ [members], ); - const resolvedMembers = useENSNames(membersWithRole, useENSNamesOptions); + const resolvedMembers = useResolvableNames( + membersWithRole, + useResolvableNamesOptions, + ); const resolvedMembersMap: $ReadOnlyMap = React.useMemo( () => new Map(resolvedMembers.map(member => [member.id, member])), diff --git a/lib/shared/mention-utils.js b/lib/shared/mention-utils.js --- a/lib/shared/mention-utils.js +++ b/lib/shared/mention-utils.js @@ -6,7 +6,7 @@ import SentencePrefixSearchIndex from './sentence-prefix-search-index.js'; import { threadTypeIsSidebar } from './threads/thread-specs.js'; import { stringForUserExplicit } from './user-utils.js'; -import { useENSNames } from '../hooks/ens-cache.js'; +import { useResolvableNames } from '../hooks/ens-cache.js'; import { useUserSearchIndex } from '../selectors/nav-selectors.js'; import type { RelativeMemberInfo, @@ -103,13 +103,16 @@ return null; } -const useENSNamesOptions = { allAtOnce: true }; +const useResolvableNamesOptions = { allAtOnce: true }; function useMentionTypeaheadUserSuggestions( threadMembers: $ReadOnlyArray, typeaheadMatchedStrings: ?TypeaheadMatchedStrings, ): $ReadOnlyArray { const userSearchIndex = useUserSearchIndex(threadMembers); - const resolvedThreadMembers = useENSNames(threadMembers, useENSNamesOptions); + const resolvedThreadMembers = useResolvableNames( + threadMembers, + useResolvableNamesOptions, + ); const usernamePrefix: ?string = typeaheadMatchedStrings?.query; return React.useMemo(() => { diff --git a/lib/shared/reaction-utils.js b/lib/shared/reaction-utils.js --- a/lib/shared/reaction-utils.js +++ b/lib/shared/reaction-utils.js @@ -9,7 +9,7 @@ import { useThreadHasPermission } from './thread-utils.js'; import { threadSpecs } from './threads/thread-specs.js'; import { stringForUserExplicit } from './user-utils.js'; -import { useENSNames } from '../hooks/ens-cache.js'; +import { useResolvableNames } from '../hooks/ens-cache.js'; import { useSendReactionMessage } from '../hooks/message-hooks.js'; import type { ReactionInfo } from '../selectors/chat-selectors.js'; import type { @@ -75,7 +75,7 @@ >), )(result); }, [reactions]); - return useENSNames(withoutENSNames); + return useResolvableNames(withoutENSNames); } function useCanCreateReactionFromMessage( 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 @@ -3,7 +3,7 @@ import * as React from 'react'; import { roleIsAdminRole } from './thread-utils.js'; -import { useENSNames } from '../hooks/ens-cache.js'; +import { useResolvableNames } from '../hooks/ens-cache.js'; import type { ThreadInfo, RawThreadInfo, @@ -44,7 +44,7 @@ ); const adminUserInfo = admin ? userInfos[admin.id] : undefined; const adminUserInfos = React.useMemo(() => [adminUserInfo], [adminUserInfo]); - const [adminUserInfoWithENSName] = useENSNames(adminUserInfos); + const [adminUserInfoWithENSName] = useResolvableNames(adminUserInfos); return adminUserInfoWithENSName; } diff --git a/lib/utils/entity-text.js b/lib/utils/entity-text.js --- a/lib/utils/entity-text.js +++ b/lib/utils/entity-text.js @@ -7,7 +7,7 @@ import type { GetENSNames } from './ens-helpers.js'; import type { GetFCNames } from './farcaster-helpers.js'; import { tID, tShape, tString, tUserID } from './validation-utils.js'; -import { useENSNames } from '../hooks/ens-cache.js'; +import { useResolvableNames } from '../hooks/ens-cache.js'; import { useFCNames } from '../hooks/fc-cache.js'; import { threadNoun } from '../shared/thread-utils.js'; import { stringForUser } from '../shared/user-utils.js'; @@ -589,7 +589,7 @@ () => (entityText ? entityTextToObjects(entityText) : []), [entityText], ); - const objectsWithENSNames = useENSNames(allObjects, options); + const objectsWithENSNames = useResolvableNames(allObjects, options); const objectsWithFCNames = useFCNames(allObjects, options); return React.useMemo(() => { if (!entityText) { diff --git a/native/chat/compose-subchannel.react.js b/native/chat/compose-subchannel.react.js --- a/native/chat/compose-subchannel.react.js +++ b/native/chat/compose-subchannel.react.js @@ -8,7 +8,7 @@ import { Text, View } from 'react-native'; import { newThreadActionTypes } from 'lib/actions/thread-action-types.js'; -import { useENSNames } from 'lib/hooks/ens-cache.js'; +import { useResolvableNames } from 'lib/hooks/ens-cache.js'; import { useNewThinThread } from 'lib/hooks/thread-hooks.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; import { userInfoSelectorForPotentialMembers } from 'lib/selectors/user-selectors.js'; @@ -280,8 +280,8 @@ }), [onPressCreateThread], ); - const userSearchResultWithENSNames = useENSNames(userSearchResults); - const userInfoInputArrayWithENSNames = useENSNames(userInfoInputArray); + const userSearchResultWithENSNames = useResolvableNames(userSearchResults); + const userInfoInputArrayWithENSNames = useResolvableNames(userInfoInputArray); return ( @@ -111,7 +111,8 @@ userSelectionAdditionalStyles = null; } - const userInfoInputArrayWithENSNames = useENSNames(userInfoInputArray); + const userInfoInputArrayWithENSNames = + useResolvableNames(userInfoInputArray); return ( <> diff --git a/native/chat/settings/add-users-modal.react.js b/native/chat/settings/add-users-modal.react.js --- a/native/chat/settings/add-users-modal.react.js +++ b/native/chat/settings/add-users-modal.react.js @@ -4,7 +4,7 @@ import { ActivityIndicator, Text, View } from 'react-native'; import { changeThreadSettingsActionTypes } from 'lib/actions/thread-action-types.js'; -import { useENSNames } from 'lib/hooks/ens-cache.js'; +import { useResolvableNames } from 'lib/hooks/ens-cache.js'; import { useChangeThreadSettings } from 'lib/hooks/thread-hooks.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; @@ -229,8 +229,8 @@ }), [onPressAdd], ); - const userSearchResultWithENSNames = useENSNames(userSearchResults); - const userInfoInputArrayWithENSNames = useENSNames(userInfoInputArray); + const userSearchResultWithENSNames = useResolvableNames(userSearchResults); + const userInfoInputArrayWithENSNames = useResolvableNames(userInfoInputArray); return ( [otherUserInfoFromRedux], [otherUserInfoFromRedux], ); - const [otherUserInfo] = useENSNames(ensNames); + const [otherUserInfo] = useResolvableNames(ensNames); const updateRelationships = useUpdateRelationships(); const updateRelationship = React.useCallback( diff --git a/native/chat/settings/thread-settings-member.react.js b/native/chat/settings/thread-settings-member.react.js --- a/native/chat/settings/thread-settings-member.react.js +++ b/native/chat/settings/thread-settings-member.react.js @@ -14,7 +14,7 @@ changeThreadMemberRolesActionTypes, removeUsersFromThreadActionTypes, } from 'lib/actions/thread-action-types.js'; -import { useENSNames } from 'lib/hooks/ens-cache.js'; +import { useResolvableNames } from 'lib/hooks/ens-cache.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { useAvailableThreadMemberActions } from 'lib/shared/thread-utils.js'; import { stringForUser } from 'lib/shared/user-utils.js'; @@ -257,7 +257,7 @@ )(state), ); - const [memberInfo] = useENSNames([props.memberInfo]); + const [memberInfo] = useResolvableNames([props.memberInfo]); const colors = useColors(); const styles = useStyles(unboundStyles); diff --git a/native/profile/relationship-list.react.js b/native/profile/relationship-list.react.js --- a/native/profile/relationship-list.react.js +++ b/native/profile/relationship-list.react.js @@ -6,7 +6,7 @@ import { FlatList } from 'react-native-gesture-handler'; import { updateRelationshipsActionTypes } from 'lib/actions/relationship-actions.js'; -import { useENSNames } from 'lib/hooks/ens-cache.js'; +import { useResolvableNames } from 'lib/hooks/ens-cache.js'; import { useUpdateRelationships } from 'lib/hooks/relationship-hooks.js'; import { registerFetchKey } from 'lib/reducers/loading-reducer.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; @@ -389,7 +389,7 @@ userInfos, ]); - const displayUsers = useENSNames(usersWithoutENSNames); + const displayUsers = useResolvableNames(usersWithoutENSNames); const listData = React.useMemo(() => { let emptyItem; if (displayUsers.length === 0 && searchInputText === '') { @@ -413,7 +413,7 @@ }, [displayUsers, verticalBounds, searchInputText]); const indicatorStyle = useIndicatorStyle(); - const currentTagsWithENSNames = useENSNames(currentTags); + const currentTagsWithENSNames = useResolvableNames(currentTags); return ( 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 @@ -8,7 +8,7 @@ import { useModalContext } from 'lib/components/modal-provider.react.js'; import SWMansionIcon from 'lib/components/swmansion-icon.react.js'; import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js'; -import { useENSNames } from 'lib/hooks/ens-cache.js'; +import { useResolvableNames } from 'lib/hooks/ens-cache.js'; import { useUsersSupportThickThreads } from 'lib/hooks/user-identities-hooks.js'; import { userInfoSelectorForPotentialMembers } from 'lib/selectors/user-selectors.js'; import { @@ -76,7 +76,7 @@ includeServerSearchUsers: searchResults, }); - const userListItemsWithENSNames = useENSNames(userListItems); + const userListItemsWithENSNames = useResolvableNames(userListItems); const { pushModal } = useModalContext(); @@ -221,7 +221,7 @@ hideSearch('reset-active-thread-if-pending'); }, [hideSearch]); - const userInfoInputArrayWithENSNames = useENSNames(userInfoInputArray); + const userInfoInputArrayWithENSNames = useResolvableNames(userInfoInputArray); const tagsList = React.useMemo(() => { if (!userInfoInputArrayWithENSNames?.length) { return null; diff --git a/web/modals/history/history-entry.react.js b/web/modals/history/history-entry.react.js --- a/web/modals/history/history-entry.react.js +++ b/web/modals/history/history-entry.react.js @@ -8,7 +8,7 @@ restoreEntryActionTypes, useRestoreEntry, } from 'lib/actions/entry-actions.js'; -import { useENSNames } from 'lib/hooks/ens-cache.js'; +import { useResolvableNames } from 'lib/hooks/ens-cache.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; import { colorIsDark } from 'lib/shared/color-utils.js'; @@ -169,7 +169,7 @@ const dispatchActionPromise = useDispatchActionPromise(); const { creator } = props.entryInfo; - const [creatorWithENSName] = useENSNames([creator]); + const [creatorWithENSName] = useResolvableNames([creator]); return ( [authorUserInfo], [authorUserInfo], ); - const [authorWithENSName] = useENSNames(authorUserInfos); + const [authorWithENSName] = useResolvableNames(authorUserInfos); const author = authorWithENSName?.username ? ( {authorWithENSName.username} diff --git a/web/modals/threads/members/members-list.react.js b/web/modals/threads/members/members-list.react.js --- a/web/modals/threads/members/members-list.react.js +++ b/web/modals/threads/members/members-list.react.js @@ -5,7 +5,7 @@ import _toPairs from 'lodash/fp/toPairs.js'; import * as React from 'react'; -import { useENSNames } from 'lib/hooks/ens-cache.js'; +import { useResolvableNames } from 'lib/hooks/ens-cache.js'; import { stringForUser } from 'lib/shared/user-utils.js'; import type { RelativeMemberInfo, @@ -25,7 +25,7 @@ const [openMenu, setOpenMenu] = React.useState(null); const hasMembers = threadMembers.length > 0; - const threadMembersWithENSNames = useENSNames(threadMembers); + const threadMembersWithENSNames = useResolvableNames(threadMembers); const groupedByFirstLetterMembers = React.useMemo( () => diff --git a/web/modals/threads/settings/thread-settings-relationship-tab.react.js b/web/modals/threads/settings/thread-settings-relationship-tab.react.js --- a/web/modals/threads/settings/thread-settings-relationship-tab.react.js +++ b/web/modals/threads/settings/thread-settings-relationship-tab.react.js @@ -2,7 +2,7 @@ import * as React from 'react'; -import { useENSNames } from 'lib/hooks/ens-cache.js'; +import { useResolvableNames } from 'lib/hooks/ens-cache.js'; import { type SetState } from 'lib/types/hook-types.js'; import { type RelationshipButton } from 'lib/types/relationship-types.js'; import type { UserInfo } from 'lib/types/user-types.js'; @@ -19,7 +19,7 @@ function ThreadSettingsRelationshipTab(props: Props): React.Node { const { relationshipButtons, otherUserInfo, setErrorMessage } = props; const userInfos = React.useMemo(() => [otherUserInfo], [otherUserInfo]); - const [otherUserInfoWithENSName] = useENSNames(userInfos); + const [otherUserInfoWithENSName] = useResolvableNames(userInfos); const buttons = React.useMemo( () => relationshipButtons.map(action => ( diff --git a/web/settings/relationship/user-list.react.js b/web/settings/relationship/user-list.react.js --- a/web/settings/relationship/user-list.react.js +++ b/web/settings/relationship/user-list.react.js @@ -3,7 +3,7 @@ import classNames from 'classnames'; import * as React from 'react'; -import { useENSNames } from 'lib/hooks/ens-cache.js'; +import { useResolvableNames } from 'lib/hooks/ens-cache.js'; import { useUserSearchIndex } from 'lib/selectors/nav-selectors.js'; import type { AccountUserInfo, UserInfo } from 'lib/types/user-types.js'; import { values } from 'lib/utils/objects.js'; @@ -59,7 +59,7 @@ } return matchedUserInfos.sort(usersComparator); }, [userInfosArray, searchResult, searchText, userInfos, usersComparator]); - const usersWithENSNames = useENSNames(users); + const usersWithENSNames = useResolvableNames(users); const userRows = React.useMemo(() => { const UserRow = userRowComponent; diff --git a/web/tooltips/tooltip-action-utils.js b/web/tooltips/tooltip-action-utils.js --- a/web/tooltips/tooltip-action-utils.js +++ b/web/tooltips/tooltip-action-utils.js @@ -6,7 +6,7 @@ import * as React from 'react'; import { useModalContext } from 'lib/components/modal-provider.react.js'; -import { useENSNames } from 'lib/hooks/ens-cache.js'; +import { useResolvableNames } from 'lib/hooks/ens-cache.js'; import { useResettingState } from 'lib/hooks/use-resetting-state.js'; import type { ChatMessageInfoItem, @@ -515,7 +515,7 @@ }; } -const useENSNamesOptions = { allAtOnce: true }; +const useResolvableNamesOptions = { allAtOnce: true }; type UseReactionTooltipArgs = { +reaction: string, @@ -530,7 +530,7 @@ }: UseReactionTooltipArgs): UseTooltipResult { const { users } = reactions[reaction]; - const resolvedUsers = useENSNames(users, useENSNamesOptions); + const resolvedUsers = useResolvableNames(users, useResolvableNamesOptions); const showSeeMoreText = resolvedUsers.length > 5;