diff --git a/lib/shared/farcaster/farcaster-hooks.js b/lib/shared/farcaster/farcaster-hooks.js --- a/lib/shared/farcaster/farcaster-hooks.js +++ b/lib/shared/farcaster/farcaster-hooks.js @@ -8,6 +8,7 @@ type ProcessFarcasterOpsPayload, } from './farcaster-actions.js'; import { + type FetchFarcasterConversationResult, useFetchFarcasterConversation, useFetchFarcasterInbox, useFetchFarcasterMessages, @@ -21,7 +22,14 @@ import { useGetCommFCUsersForFIDs } from '../../hooks/user-identities-hooks.js'; import { isLoggedIn } from '../../selectors/user-selectors.js'; import type { RawMessageInfo } from '../../types/message-types.js'; -import { messageTruncationStatus } from '../../types/message-types.js'; +import { + messageTruncationStatus, + defaultNumberPerThread, +} from '../../types/message-types.js'; +import type { + MemberInfoSansPermissions, + FarcasterRawThreadInfo, +} from '../../types/minimally-encoded-thread-permissions-types'; import type { Dispatch } from '../../types/redux-types.js'; import { updateTypes } from '../../types/update-types-enum.js'; import type { ClientUpdateInfo } from '../../types/update-types.js'; @@ -186,6 +194,52 @@ return results; } +type ConversationFetchResult = { + +farcasterConversation: FarcasterConversation, + +thread: FarcasterRawThreadInfo, + +threadMembers: Array, +}; +async function fetchAndProcessConversation( + conversationID: string, + fetchFarcasterConversation: (input: { + conversationId: string, + }) => Promise, + fetchUsersByFIDs: (fids: Array) => Promise<$ReadOnlyMap>, +): Promise { + const conversationResult = await withRetry( + () => + fetchFarcasterConversation({ + conversationId: conversationID, + }), + MAX_RETRIES, + RETRY_DELAY_MS, + ); + + if (!conversationResult) { + return null; + } + + const farcasterConversation = conversationResult.result.conversation; + let thread = createFarcasterRawThreadInfo(farcasterConversation); + const fids = thread.members.map(member => member.id); + const commFCUsersForFIDs = await fetchUsersByFIDs(fids); + const threadMembers = thread.members.map( + member => + ({ + ...member, + id: + commFCUsersForFIDs.get(member.id)?.userID ?? userIDFromFID(member.id), + }: MemberInfoSansPermissions), + ); + + thread = { + ...thread, + members: threadMembers, + }; + + return { farcasterConversation, thread, threadMembers }; +} + function useFetchConversationWithBatching(): ( conversationID: string, batchedUpdates: BatchedUpdates, @@ -199,34 +253,17 @@ batchedUpdates: BatchedUpdates, ): Promise => { try { - const conversationResult = await withRetry( - () => - fetchFarcasterConversation({ - conversationId: conversationID, - }), - MAX_RETRIES, - RETRY_DELAY_MS, + const result = await fetchAndProcessConversation( + conversationID, + fetchFarcasterConversation, + fetchUsersByFIDs, ); - if (!conversationResult) { + if (!result) { return null; } - const farcasterConversation = conversationResult.result.conversation; - let thread = createFarcasterRawThreadInfo(farcasterConversation); - const fids = thread.members.map(member => member.id); - const commFCUsersForFIDs = await fetchUsersByFIDs(fids); - const threadMembers = thread.members.map(member => ({ - ...member, - id: - commFCUsersForFIDs.get(member.id)?.userID ?? - userIDFromFID(member.id), - })); - - thread = { - ...thread, - members: threadMembers, - }; + const { farcasterConversation, thread, threadMembers } = result; const update = { type: updateTypes.JOIN_THREAD, @@ -253,11 +290,88 @@ ); } +function useFetchConversationWithMessages(): ( + conversationID: string, + messagesLimit: number, + batchedUpdates: BatchedUpdates, +) => Promise { + const fetchUsersByFIDs = useGetCommFCUsersForFIDs(); + const fetchFarcasterConversation = useFetchFarcasterConversation(); + const fetchFarcasterMessages = useFetchMessagesForConversation(); + + return React.useCallback( + async ( + conversationID: string, + messagesLimit: number, + batchedUpdates: BatchedUpdates, + ): Promise => { + try { + const result = await fetchAndProcessConversation( + conversationID, + fetchFarcasterConversation, + fetchUsersByFIDs, + ); + + if (!result) { + return null; + } + + const { farcasterConversation, thread, threadMembers } = result; + + if (threadMembers.length > 0) { + threadMembers.forEach(member => batchedUpdates.addUserID(member.id)); + } + + const messagesResult = await fetchFarcasterMessages( + conversationID, + messagesLimit, + ); + batchedUpdates.addUserIDs(messagesResult.userIDs); + + const reduxMessages = messagesResult.messages.slice( + 0, + defaultNumberPerThread, + ); + const dbMessages = messagesResult.messages.slice( + defaultNumberPerThread, + ); + const truncationStatus = + dbMessages.length > 0 + ? messageTruncationStatus.UNCHANGED + : messageTruncationStatus.EXHAUSTIVE; + batchedUpdates.addAdditionalMessageInfos(dbMessages); + + const update = { + type: updateTypes.JOIN_THREAD, + id: uuid.v4(), + time: thread.creationTime, + threadInfo: thread, + rawMessageInfos: reduxMessages, + truncationStatus, + rawEntryInfos: [], + }; + batchedUpdates.addUpdateInfo(update); + + return farcasterConversation; + } catch (e) { + console.error( + `Failed fetching conversation ${conversationID} with messages:`, + e, + ); + return null; + } + }, + [fetchFarcasterConversation, fetchFarcasterMessages, fetchUsersByFIDs], + ); +} + function useFetchMessagesForConversation(): ( conversationID: string, messagesNumberLimit?: number, - batchedUpdates: BatchedUpdates, -) => Promise { +) => Promise<{ + +messages: Array, + +userIDs: Array, +}> { const fetchFarcasterMessages = useFetchFarcasterMessages(); const fetchUsersByFIDs = useGetCommFCUsersForFIDs(); @@ -265,15 +379,19 @@ async ( conversationID: string, messagesNumberLimit: number = 20, - batchedUpdates: BatchedUpdates, - ): Promise => { + ): Promise<{ + +messages: Array, + +userIDs: Array, + }> => { + const result: Array = []; + const userIDs: Array = []; try { let cursor: ?string = null; let totalMessagesFetched = 0; do { const batchLimit = Math.min( - 20, + 50, messagesNumberLimit - totalMessagesFetched, ); @@ -308,18 +426,12 @@ convertFarcasterMessageToCommMessages(message, fcUserInfos), ); - if (fcUserInfos.size > 0) { - Array.from(fcUserInfos.entries()).forEach(([fid, user]) => - batchedUpdates.addUserID(user?.userID ?? userIDFromFID(fid)), - ); - } - if (rawMessageInfos.length > 0) { - if (totalMessagesFetched === 0) { - batchedUpdates.addMessageInfos(rawMessageInfos); - } else { - batchedUpdates.addAdditionalMessageInfos(rawMessageInfos); - } - } + userIDs.push( + ...Array.from(fcUserInfos.entries()).map( + ([fid, user]) => user?.userID ?? userIDFromFID(fid), + ), + ); + result.push(...rawMessageInfos); totalMessagesFetched += farcasterMessages.length; cursor = messagesResult.next?.cursor; @@ -332,6 +444,10 @@ } catch (e) { console.error(`Failed fetching messages for ${conversationID}:`, e); } + return { + messages: result, + userIDs: Array.from(new Set(userIDs)), + }; }, [fetchFarcasterMessages, fetchUsersByFIDs], ); @@ -340,16 +456,14 @@ function useRefreshFarcasterConversation(): ( conversationID: string, ) => Promise { - const fetchConversation = useFetchConversationWithBatching(); - const fetchMessagesForConversation = useFetchMessagesForConversation(); + const fetchConversationWithMessages = useFetchConversationWithMessages(); const dispatch = useDispatch(); return React.useCallback( async (conversationID: string) => { const batchedUpdates = new BatchedUpdates(); - await fetchConversation(conversationID, batchedUpdates); - await fetchMessagesForConversation(conversationID, 20, batchedUpdates); + await fetchConversationWithMessages(conversationID, 20, batchedUpdates); if (!batchedUpdates.isEmpty()) { dispatch({ @@ -358,22 +472,17 @@ }); } }, - [fetchConversation, fetchMessagesForConversation, dispatch], + [fetchConversationWithMessages, dispatch], ); } function useFarcasterConversationsSync(): ( limit: number, - onProgress?: ( - completed: number, - total: number, - phase: 'conversations' | 'messages', - ) => void, + onProgress?: (completed: number, total: number) => void, ) => Promise { const fetchFarcasterInbox = useFetchFarcasterInbox(); const dispatch = useDispatch(); - const fetchConversation = useFetchConversationWithBatching(); - const fetchMessagesForConversation = useFetchMessagesForConversation(); + const fetchConversationWithMessages = useFetchConversationWithMessages(); const setFarcasterDCsLoaded = useSetFarcasterDCsLoaded(); const fetchInboxes = React.useCallback( @@ -420,11 +529,7 @@ return React.useCallback( async ( limit: number, - onProgress?: ( - completed: number, - total: number, - phase: 'conversations' | 'messages', - ) => void, + onProgress?: (completed: number, total: number) => void, ) => { try { const conversations = await fetchInboxes(null); @@ -434,33 +539,18 @@ return; } - const farcasterConversations: Array = []; - - onProgress?.(0, conversations.length, 'conversations'); - const conversationResults = await processInBatchesWithReduxBatching( + onProgress?.(0, conversations.length); + await processInBatchesWithReduxBatching( conversations, FARCASTER_DATA_BATCH_SIZE, (conversationID, batchedUpdates) => - fetchConversation(conversationID, batchedUpdates), - dispatch, - (completed, total) => onProgress?.(completed, total, 'conversations'), - ); - - const successfulConversations = conversationResults.filter(Boolean); - farcasterConversations.push(...successfulConversations); - - onProgress?.(0, conversations.length, 'messages'); - await processInBatchesWithReduxBatching( - farcasterConversations, - FARCASTER_DATA_BATCH_SIZE, - (conversation, batchedUpdates) => - fetchMessagesForConversation( - conversation.conversationId, + fetchConversationWithMessages( + conversationID, limit, batchedUpdates, ), dispatch, - (completed, total) => onProgress?.(completed, total, 'messages'), + (completed, total) => onProgress?.(completed, total), ); setFarcasterDCsLoaded(true); @@ -471,8 +561,7 @@ }, [ fetchInboxes, - fetchConversation, - fetchMessagesForConversation, + fetchConversationWithMessages, dispatch, setFarcasterDCsLoaded, ], @@ -534,7 +623,6 @@ +progress: ?{ completed: number, total: number, - phase: 'conversations' | 'messages', }, } { const syncFarcasterConversations = useFarcasterConversationsSync(); @@ -547,12 +635,11 @@ const [progress, setProgress] = React.useState(null); const handleProgress = React.useCallback( - (completed: number, total: number, phase: 'conversations' | 'messages') => { - const progressInfo = { completed, total, phase }; + (completed: number, total: number) => { + const progressInfo = { completed, total }; setProgress(progressInfo); }, [], @@ -597,6 +684,7 @@ export { useFarcasterConversationsSync, useFetchConversationWithBatching, + useFetchConversationWithMessages, useFetchConversation, useFetchMessagesForConversation, useRefreshFarcasterConversation, diff --git a/native/farcaster/farcaster-sync-loading-screen.react.js b/native/farcaster/farcaster-sync-loading-screen.react.js --- a/native/farcaster/farcaster-sync-loading-screen.react.js +++ b/native/farcaster/farcaster-sync-loading-screen.react.js @@ -30,17 +30,6 @@ ? progress.completed / progress.total : undefined; - const phase = React.useMemo(() => { - if (!progress?.phase) { - return null; - } - const phaseText = - progress.phase === 'conversations' - ? 'Loading conversations...' - : 'Loading messages...'; - return {phaseText}; - }, [progress?.phase, styles.section]); - return ( Fetching Farcaster threads @@ -50,7 +39,6 @@ This could take a while depending on how many conversations you have. - {phase} {progress ? ( -

- {progress.phase === 'conversations' - ? 'Loading conversations...' - : 'Loading messages...'} -

+

Loading conversations...

{progress.completed} of {progress.total} ( {Math.round((progress.completed / progress.total) * 100)}%)