diff --git a/keyserver/src/utils/fc-cache.js b/keyserver/src/utils/fc-cache.js index 3476ffba2..ff48c2dff 100644 --- a/keyserver/src/utils/fc-cache.js +++ b/keyserver/src/utils/fc-cache.js @@ -1,31 +1,31 @@ // @flow import { getCommConfig } from 'lib/utils/comm-config.js'; import { getFCNames as baseGetFCNames, type GetFCNames, - type BaseFCInfo, + type BaseFCNamesInfo, } 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; let neynarClient: ?NeynarClient; async function initFCCache() { const neynarSecret = await getCommConfig({ folder: 'secrets', name: 'neynar', }); const neynarKey = neynarSecret?.key; if (!neynarKey) { return; } neynarClient = new NeynarClient(neynarKey); const fcCache = new FCCache(neynarClient); - getFCNames = (users: $ReadOnlyArray): Promise => + getFCNames = (users: $ReadOnlyArray): Promise => baseGetFCNames(fcCache, users); } export { initFCCache, getFCNames, neynarClient }; diff --git a/lib/components/neynar-client-provider.react.js b/lib/components/neynar-client-provider.react.js index 457fe5dba..54e29c24b 100644 --- a/lib/components/neynar-client-provider.react.js +++ b/lib/components/neynar-client-provider.react.js @@ -1,58 +1,66 @@ // @flow import * as React from 'react'; import { getFCNames as baseGetFCNames, - type BaseFCInfo, + getFCAvatarURLs as baseGetFCAvatarURLs, + type BaseFCNamesInfo, + type BaseFCAvatarInfo, type GetFCNames, + type GetFCAvatarURLs, } 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, + +getFCAvatarURLs: GetFCAvatarURLs, }; const NeynarClientContext: React.Context = React.createContext(); type Props = { +apiKey: ?string, +children: React.Node, }; function NeynarClientProvider(props: Props): React.Node { const { apiKey, children } = props; const neynarClient = React.useMemo(() => { if (!apiKey) { return null; } return new NeynarClient(apiKey); }, [apiKey]); const context = React.useMemo(() => { if (!neynarClient) { return null; } const fcCache = new FCCache(neynarClient); - const getFCNames: GetFCNames = ( + const getFCNames: GetFCNames = ( users: $ReadOnlyArray, ): Promise => baseGetFCNames(fcCache, users); + const getFCAvatarURLs: GetFCAvatarURLs = ( + fids: $ReadOnlyArray, + ): Promise => baseGetFCAvatarURLs(fcCache, fids); return { client: neynarClient, fcCache, getFCNames, + getFCAvatarURLs, }; }, [neynarClient]); return ( {children} ); } export { NeynarClientContext, NeynarClientProvider }; diff --git a/lib/utils/farcaster-helpers.js b/lib/utils/farcaster-helpers.js index 579f88efa..2c1ca5b2e 100644 --- a/lib/utils/farcaster-helpers.js +++ b/lib/utils/farcaster-helpers.js @@ -1,79 +1,137 @@ // @flow import { FCCache } from './fc-cache.js'; -export type BaseFCInfo = { +export type BaseFCNamesInfo = { +fid?: ?string, +farcasterUsername?: ?string, ... }; -export type GetFCNames = ( +export type GetFCNames = ( users: $ReadOnlyArray, ) => Promise; -async function getFCNames( +async function getFCNames( fcCache: FCCache, users: $ReadOnlyArray, ): Promise { const info = users.map(user => { if (!user) { return user; } const { fid, farcasterUsername } = user; let cachedResult = null; if (farcasterUsername) { cachedResult = farcasterUsername; } else if (fid) { cachedResult = fcCache.getCachedFarcasterUserForFID(fid)?.username; } return { input: user, fid, cachedResult, }; }); const needFetch = info .map(user => { if (!user) { return null; } const { fid, cachedResult } = user; if (cachedResult || !fid) { return null; } return fid; }) .filter(Boolean); const farcasterUsernames = new Map(); if (needFetch.length > 0) { const results = await fcCache.getFarcasterUsersForFIDs(needFetch); for (let i = 0; i < needFetch.length; i++) { const fid = needFetch[i]; const result = results[i]; if (result) { farcasterUsernames.set(fid, result.username); } } } return info.map(user => { if (!user) { return user; } const { input, fid, cachedResult } = user; if (cachedResult) { return { ...input, farcasterUsername: cachedResult }; } else if (!fid) { return input; } const farcasterUsername = farcasterUsernames.get(fid); if (farcasterUsername) { return { ...input, farcasterUsername }; } return input; }); } -export { getFCNames }; +export type BaseFCAvatarInfo = { + +fid: string, + +pfpURL: ?string, +}; +export type GetFCAvatarURLs = ( + fids: $ReadOnlyArray, +) => Promise; + +async function getFCAvatarURLs( + fcCache: FCCache, + fids: $ReadOnlyArray, +): Promise { + const info = fids.map(fid => { + const cachedResult = fcCache.getCachedFarcasterUserForFID(fid)?.pfpURL; + return { + fid, + cachedResult, + }; + }); + + const needFetch = info + .map(user => { + if (!user) { + return null; + } + const { fid, cachedResult } = user; + if (cachedResult) { + return null; + } + return fid; + }) + .filter(Boolean); + + const pfpURLs = new Map(); + if (needFetch.length > 0) { + const results = await fcCache.getFarcasterUsersForFIDs(needFetch); + for (let i = 0; i < needFetch.length; i++) { + const fid = needFetch[i]; + const result = results[i]; + if (result) { + pfpURLs.set(fid, result.pfpURL); + } + } + } + + return info.map(user => { + const { fid, cachedResult } = user; + if (cachedResult) { + return { fid, pfpURL: cachedResult }; + } + const pfpURL = pfpURLs.get(fid); + if (pfpURL) { + return { fid, pfpURL }; + } + return { fid, pfpURL: null }; + }); +} + +export { getFCNames, getFCAvatarURLs };