diff --git a/keyserver/src/keyserver.js b/keyserver/src/keyserver.js --- a/keyserver/src/keyserver.js +++ b/keyserver/src/keyserver.js @@ -44,6 +44,7 @@ import { createAuthoritativeKeyserverConfigFiles } from './user/create-configs.js'; import { verifyUserLoggedIn } from './user/login.js'; import { initENSCache } from './utils/ens-cache.js'; +import { initFCCache } from './utils/fc-cache.js'; import { getContentSigningKey } from './utils/olm-utils.js'; import { prefetchAllURLFacts, @@ -61,6 +62,7 @@ olm.init(), prefetchAllURLFacts(), initENSCache(), + initFCCache(), ]); const keyserverURLFacts = getKeyserverURLFacts(); diff --git a/keyserver/src/push/send.js b/keyserver/src/push/send.js --- a/keyserver/src/push/send.js +++ b/keyserver/src/push/send.js @@ -81,6 +81,7 @@ import type { Viewer } from '../session/viewer.js'; import { thisKeyserverID } from '../user/identity.js'; import { getENSNames } from '../utils/ens-cache.js'; +import { getFCNames } from '../utils/fc-cache.js'; import { validateOutput } from '../utils/validation-utils.js'; export type Device = { @@ -287,6 +288,7 @@ parentThreadInfo, notifTargetUserInfo, getENSNames, + getFCNames, ); if (!notifTexts) { return null; diff --git a/keyserver/src/utils/fc-cache.js b/keyserver/src/utils/fc-cache.js new file mode 100644 --- /dev/null +++ b/keyserver/src/utils/fc-cache.js @@ -0,0 +1,30 @@ +// @flow + +import { getCommConfig } from 'lib/utils/comm-config.js'; +import { + getFCNames as baseGetFCNames, + type GetFCNames, + type BaseFCInfo, +} from 'lib/utils/farcaster-helpers.js'; +import { FCCache } from 'lib/utils/fc-cache.js'; +import { NeynarClient } from 'lib/utils/neynar-client.js'; + +type NeynarConfig = { +key: string }; + +let getFCNames: ?GetFCNames; +async function initFCCache() { + const neynarSecret = await getCommConfig({ + folder: 'secrets', + name: 'neynar', + }); + const neynarKey = neynarSecret?.key; + if (!neynarKey) { + return; + } + const neynarClient = new NeynarClient(neynarKey); + const fcCache = new FCCache(neynarClient); + getFCNames = (users: $ReadOnlyArray): Promise => + baseGetFCNames(fcCache, users); +} + +export { initFCCache, getFCNames }; diff --git a/lib/components/neynar-client-provider.react.js b/lib/components/neynar-client-provider.react.js --- a/lib/components/neynar-client-provider.react.js +++ b/lib/components/neynar-client-provider.react.js @@ -2,12 +2,18 @@ import * as React from 'react'; +import { + getFCNames as baseGetFCNames, + type BaseFCInfo, + type GetFCNames, +} from '../utils/farcaster-helpers.js'; import { FCCache } from '../utils/fc-cache.js'; import { NeynarClient } from '../utils/neynar-client.js'; type NeynarClientContextType = { +client: NeynarClient, +fcCache: FCCache, + +getFCNames: GetFCNames, }; const NeynarClientContext: React.Context = @@ -31,9 +37,14 @@ if (!neynarClient) { return null; } + const fcCache = new FCCache(neynarClient); + const getFCNames: GetFCNames = ( + users: $ReadOnlyArray, + ): Promise => baseGetFCNames(fcCache, users); return { client: neynarClient, - fcCache: new FCCache(neynarClient), + fcCache, + getFCNames, }; }, [neynarClient]); diff --git a/lib/shared/notif-utils.js b/lib/shared/notif-utils.js --- a/lib/shared/notif-utils.js +++ b/lib/shared/notif-utils.js @@ -29,6 +29,7 @@ getEntityTextAsString, type ThreadEntity, } from '../utils/entity-text.js'; +import type { GetFCNames } from '../utils/farcaster-helpers.js'; import { promiseAll } from '../utils/promises.js'; import { trimText } from '../utils/text-utils.js'; @@ -38,6 +39,7 @@ parentThreadInfo: ?ThreadInfo, notifTargetUserInfo: UserInfo, getENSNames: ?GetENSNames, + getFCNames: ?GetFCNames, ): Promise { const fullNotifTexts = await fullNotifTextsForMessageInfo( messageInfos, @@ -45,6 +47,7 @@ parentThreadInfo, notifTargetUserInfo, getENSNames, + getFCNames, ); if (!fullNotifTexts) { return fullNotifTexts; @@ -218,6 +221,7 @@ parentThreadInfo: ?ThreadInfo, notifTargetUserInfo: UserInfo, getENSNames: ?GetENSNames, + getFCNames: ?GetFCNames, ): Promise { const mostRecentType = mostRecentMessageInfoType(messageInfos); const messageSpec = messageSpecs[mostRecentType]; @@ -240,9 +244,11 @@ if (typeof entityText === 'string') { return entityText; } - const notifString = await getEntityTextAsString(entityText, getENSNames, { - prefixThisThreadNounWith: 'your', - }); + const notifString = await getEntityTextAsString( + entityText, + { getENSNames, getFCNames }, + { prefixThisThreadNounWith: 'your' }, + ); invariant( notifString !== null && notifString !== undefined, 'getEntityTextAsString only returns falsey when passed falsey', 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 @@ -123,6 +123,7 @@ type ThreadEntity, type UserEntity, } from '../utils/entity-text.js'; +import type { GetFCNames } from '../utils/farcaster-helpers.js'; import { entries, values } from '../utils/objects.js'; import { useDispatchActionPromise, @@ -664,6 +665,7 @@ ...SharedCreatePendingSidebarInput, +markdownRules: ParserRules, +getENSNames: ?GetENSNames, + +getFCNames: ?GetFCNames, }; async function createPendingSidebar( @@ -675,6 +677,7 @@ loggedInUserInfo, markdownRules, getENSNames, + getFCNames, } = input; const messageTitleEntityText = getMessageTitle( @@ -685,7 +688,7 @@ ); const messageTitle = await getEntityTextAsString( messageTitleEntityText, - getENSNames, + { getENSNames, getFCNames }, { ignoreViewer: true }, ); invariant( 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 @@ -5,6 +5,7 @@ import t, { type TInterface, type TUnion } from 'tcomb'; import type { GetENSNames } from './ens-helpers.js'; +import type { GetFCNames } from './farcaster-helpers.js'; import { tID, tShape, tString } from './validation-utils.js'; import { useENSNames } from '../hooks/ens-cache.js'; import { useFCNames } from '../hooks/fc-cache.js'; @@ -619,22 +620,55 @@ }, [withENSNames, params]); } +type Fetchers = { + +getENSNames?: ?GetENSNames, + +getFCNames?: ?GetFCNames, +}; async function getEntityTextAsString( entityText: ?EntityText, - getENSNames: ?GetENSNames, + fetchers?: Fetchers, params?: EntityTextToRawStringParams, ): Promise { if (!entityText) { return entityText; } - let resolvedEntityText = entityText; - if (getENSNames) { - const allObjects = entityTextToObjects(entityText); - const objectsWithENSNames = await getENSNames(allObjects); - resolvedEntityText = entityTextFromObjects(objectsWithENSNames); + const getENSNames = fetchers?.getENSNames; + const getFCNames = fetchers?.getFCNames; + + if (!getENSNames && !getFCNames) { + return entityTextToRawString(entityText, params); + } + + const allObjects = entityTextToObjects(entityText); + + const objectsWithENSNamesPromise = (async () => { + if (!getENSNames) { + return allObjects; + } + return await getENSNames(allObjects); + })(); + const objectsWithFCNamesPromise = (async () => { + if (!getFCNames) { + return allObjects; + } + return await getFCNames(allObjects); + })(); + const [objectsWithENSNames, objectsWithFCNames] = await Promise.all([ + objectsWithENSNamesPromise, + objectsWithFCNamesPromise, + ]); + + const mergedObjects = []; + for (let i = 0; i < allObjects.length; i++) { + const originalObject = allObjects[i]; + const updatedObject = originalObject.fid + ? objectsWithFCNames[i] + : objectsWithENSNames[i]; + mergedObjects.push(updatedObject); } + const resolvedEntityText = entityTextFromObjects(mergedObjects); return entityTextToRawString(resolvedEntityText, params); } diff --git a/lib/utils/farcaster-helpers.js b/lib/utils/farcaster-helpers.js --- a/lib/utils/farcaster-helpers.js +++ b/lib/utils/farcaster-helpers.js @@ -2,7 +2,7 @@ import { FCCache } from './fc-cache.js'; -type BaseFCInfo = { +export type BaseFCInfo = { +fid?: ?string, +farcasterUsername?: ?string, ... diff --git a/native/chat/sidebar-navigation.js b/native/chat/sidebar-navigation.js --- a/native/chat/sidebar-navigation.js +++ b/native/chat/sidebar-navigation.js @@ -4,6 +4,7 @@ import * as React from 'react'; import { ENSCacheContext } from 'lib/components/ens-cache-provider.react.js'; +import { NeynarClientContext } from 'lib/components/neynar-client-provider.react.js'; import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js'; import { useThreadChatMentionCandidates } from 'lib/hooks/chat-mention-hooks.js'; import { @@ -14,6 +15,7 @@ import type { ChatMentionCandidates } from 'lib/types/thread-types.js'; import type { LoggedInUserInfo } from 'lib/types/user-types.js'; import type { GetENSNames } from 'lib/utils/ens-helpers.js'; +import type { GetFCNames } from 'lib/utils/farcaster-helpers.js'; import { ChatContext } from './chat-context.js'; import { useNavigateToThread } from './message-list-types.js'; @@ -51,6 +53,7 @@ type GetSidebarThreadInfoInput = { ...GetUnresolvedSidebarThreadInfoInput, +getENSNames: ?GetENSNames, + +getFCNames: ?GetFCNames, }; async function getSidebarThreadInfo( input: GetSidebarThreadInfoInput, @@ -59,6 +62,7 @@ sourceMessage, loggedInUserInfo, getENSNames, + getFCNames, chatMentionCandidates, } = input; const threadCreatedFromMessage = sourceMessage.threadCreatedFromMessage; @@ -78,6 +82,7 @@ markdownRules: getDefaultTextMessageRules(chatMentionCandidates) .simpleMarkdownRules, getENSNames, + getFCNames, }); } @@ -86,14 +91,20 @@ ): () => mixed { const loggedInUserInfo = useLoggedInUserInfo(); const navigateToThread = useNavigateToThread(); - const cacheContext = React.useContext(ENSCacheContext); + + const { getENSNames } = React.useContext(ENSCacheContext); + const chatMentionCandidates = useThreadChatMentionCandidates(item.threadInfo); - const { getENSNames } = cacheContext; + + const neynarClientContext = React.useContext(NeynarClientContext); + const getFCNames = neynarClientContext?.getFCNames; + return React.useCallback(async () => { const threadInfo = await getSidebarThreadInfo({ sourceMessage: item, loggedInUserInfo, getENSNames, + getFCNames, chatMentionCandidates, }); invariant(threadInfo, 'threadInfo should be set'); @@ -102,6 +113,7 @@ item, loggedInUserInfo, getENSNames, + getFCNames, chatMentionCandidates, navigateToThread, ]); diff --git a/web/selectors/thread-selectors.js b/web/selectors/thread-selectors.js --- a/web/selectors/thread-selectors.js +++ b/web/selectors/thread-selectors.js @@ -5,6 +5,7 @@ import { createSelector } from 'reselect'; import { ENSCacheContext } from 'lib/components/ens-cache-provider.react.js'; +import { NeynarClientContext } from 'lib/components/neynar-client-provider.react.js'; import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js'; import { useThreadChatMentionCandidates } from 'lib/hooks/chat-mention-hooks.js'; import { @@ -71,8 +72,10 @@ const dispatch = useDispatch(); const loggedInUserInfo = useLoggedInUserInfo(); - const cacheContext = React.useContext(ENSCacheContext); - const { getENSNames } = cacheContext; + const { getENSNames } = React.useContext(ENSCacheContext); + + const neynarClientContext = React.useContext(NeynarClientContext); + const getFCNames = neynarClientContext?.getFCNames; const chatMentionCandidates = useThreadChatMentionCandidates(threadInfo); return React.useCallback( @@ -88,6 +91,7 @@ markdownRules: getDefaultTextMessageRules(chatMentionCandidates) .simpleMarkdownRules, getENSNames, + getFCNames, }); dispatch({ type: updateNavInfoActionType, @@ -103,6 +107,7 @@ threadInfo, messageInfo, getENSNames, + getFCNames, dispatch, ], );