diff --git a/lib/hooks/ens-cache.js b/lib/hooks/ens-cache.js new file mode 100644 --- /dev/null +++ b/lib/hooks/ens-cache.js @@ -0,0 +1,55 @@ +// @flow + +import * as React from 'react'; + +import { ENSCacheContext } from '../components/ens-cache-provider.react'; +import { userIdentifiedByETHAddress } from '../shared/account-utils'; +import { stringForUser } from '../shared/user-utils'; +import type { RelativeMemberInfo } from '../types/thread-types'; +import type { RelativeUserInfo } from '../types/user-types'; + +function useStringForUser( + user: ?(RelativeUserInfo | RelativeMemberInfo), +): ?string { + const rawStringForUser = user ? stringForUser(user) : null; + const [ensName, setENSName] = React.useState(null); + + const ethAddress = React.useMemo(() => { + if ( + !user || + user.isViewer || + !user.username || + !userIdentifiedByETHAddress(user) + ) { + return null; + } + return user.username; + }, [user]); + + React.useEffect(() => { + // Whenever the ETH address changes, clear out ENS name before requery below + setENSName(null); + }, [ethAddress]); + + const cacheContext = React.useContext(ENSCacheContext); + const { ensCache } = cacheContext; + React.useEffect(() => { + if (!ethAddress || !ensCache) { + return; + } + let cancelled = false; + (async () => { + const result = await ensCache.getNameForAddress(ethAddress); + if (result && !cancelled) { + setENSName(result); + } + })(); + return () => { + cancelled = true; + }; + }, [ethAddress, ensCache]); + + return ensName ?? rawStringForUser; +} + +export { useStringForUser }; diff --git a/lib/shared/account-utils.js b/lib/shared/account-utils.js --- a/lib/shared/account-utils.js +++ b/lib/shared/account-utils.js @@ -81,6 +81,14 @@ : false; } +function userIdentifiedByETHAddress( + userInfo: ?{ +username?: ?string, ... }, +): boolean { + return userInfo?.username + ? isValidEthereumAddress(userInfo?.username) + : false; +} + export { usernameMaxLength, oldValidUsernameRegexString, @@ -91,4 +99,5 @@ invalidSessionRecovery, validHexColorRegex, accountHasPassword, + userIdentifiedByETHAddress, }; diff --git a/lib/shared/message-utils.js b/lib/shared/message-utils.js --- a/lib/shared/message-utils.js +++ b/lib/shared/message-utils.js @@ -4,6 +4,7 @@ import _maxBy from 'lodash/fp/maxBy'; import _orderBy from 'lodash/fp/orderBy'; +import { useStringForUser } from '../hooks/ens-cache'; import { userIDsToRelativeUserInfos } from '../selectors/user-selectors'; import type { PlatformDetails } from '../types/device-types'; import type { Media } from '../types/media-types'; @@ -33,7 +34,6 @@ import { codeBlockRegex, type ParserRules } from './markdown'; import { messageSpecs } from './messages/message-specs'; import { threadIsGroupChat } from './thread-utils'; -import { stringForUser } from './user-utils'; const localIDPrefix = 'local'; @@ -524,14 +524,23 @@ threadInfo.name !== '' || messageInfo?.creator.isViewer; + const stringForUser = useStringForUser( + messageInfo?.type === messageTypes.TEXT && hasUsername + ? messageInfo?.creator + : null, + ); if (!messageInfo) { return messageInfo; } let username = null; if (messageInfo.type === messageTypes.TEXT && hasUsername) { + invariant( + stringForUser, + 'useStringForUser should only return falsey if pass null or undefined', + ); username = { - text: stringForUser(messageInfo.creator), + text: stringForUser, style: 'secondary', }; }