Page MenuHomePhorge

D15375.1765060982.diff
No OneTemporary

Size
22 KB
Referenced Files
None
Subscribers
None

D15375.1765060982.diff

diff --git a/lib/reducers/message-reducer.js b/lib/reducers/message-reducer.js
--- a/lib/reducers/message-reducer.js
+++ b/lib/reducers/message-reducer.js
@@ -1942,12 +1942,17 @@
]),
};
} else if (action.type === processFarcasterOpsActionType) {
- const { rawMessageInfos, updateInfos, additionalMessageInfos } =
- action.payload;
+ const {
+ rawMessageInfos,
+ updateInfos,
+ additionalMessageInfos,
+ truncationStatuses,
+ } = action.payload;
const messagesResult = mergeUpdatesWithMessageInfos(
rawMessageInfos,
updateInfos,
+ truncationStatuses,
);
const { messageStoreOperations, messageStore: newMessageStore } =
diff --git a/lib/shared/farcaster/farcaster-actions.js b/lib/shared/farcaster/farcaster-actions.js
--- a/lib/shared/farcaster/farcaster-actions.js
+++ b/lib/shared/farcaster/farcaster-actions.js
@@ -1,6 +1,9 @@
// @flow
-import type { RawMessageInfo } from '../../types/message-types.js';
+import type {
+ MessageTruncationStatuses,
+ RawMessageInfo,
+} from '../../types/message-types.js';
import type { ClientUpdateInfo } from '../../types/update-types.js';
export const processFarcasterOpsActionType = 'PROCESS_FARCASTER_OPS';
@@ -10,4 +13,5 @@
+additionalMessageInfos?: $ReadOnlyArray<RawMessageInfo>,
+updateInfos: $ReadOnlyArray<ClientUpdateInfo>,
+userIDs?: $ReadOnlyArray<string>,
+ +truncationStatuses?: MessageTruncationStatuses,
};
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
@@ -14,6 +14,7 @@
useFetchFarcasterMessages,
} from './farcaster-api.js';
import type { FarcasterConversation } from './farcaster-conversation-types.js';
+import { useFarcasterMessageFetching } from './farcaster-message-fetching-context.js';
import {
type FarcasterMessage,
farcasterMessageValidator,
@@ -33,6 +34,7 @@
FarcasterRawThreadInfo,
} from '../../types/minimally-encoded-thread-permissions-types';
import type { Dispatch } from '../../types/redux-types.js';
+import type { ThreadType } from '../../types/thread-types-enum.js';
import { updateTypes } from '../../types/update-types-enum.js';
import type { ClientUpdateInfo } from '../../types/update-types.js';
import { extractFarcasterIDsFromPayload } from '../../utils/conversion-utils.js';
@@ -51,6 +53,7 @@
farcasterThreadIDFromConversationID,
userIDFromFID,
} from '../id-utils.js';
+import { threadSpecs } from '../threads/thread-specs.js';
const FARCASTER_DATA_BATCH_SIZE = 20;
const MAX_RETRIES = 3;
@@ -375,9 +378,11 @@
function useFetchMessagesForConversation(): (
conversationID: string,
messagesNumberLimit?: number,
+ cursor?: ?string,
) => Promise<{
+messages: Array<RawMessageInfo>,
+userIDs: Array<string>,
+ +newCursor?: ?string,
}> {
const fetchFarcasterMessages = useFetchFarcasterMessages();
const fetchUsersByFIDs = useGetCommFCUsersForFIDs();
@@ -386,14 +391,15 @@
async (
conversationID: string,
messagesNumberLimit: number = 20,
+ cursor?: ?string,
): Promise<{
+messages: Array<RawMessageInfo>,
+userIDs: Array<string>,
+ +newCursor?: ?string,
}> => {
const result: Array<RawMessageInfo> = [];
const userIDs: Array<string> = [];
try {
- let cursor: ?string = null;
let totalMessagesFetched = 0;
do {
@@ -454,6 +460,7 @@
return {
messages: result,
userIDs: Array.from(new Set(userIDs)),
+ newCursor: cursor,
};
},
[fetchFarcasterMessages, fetchUsersByFIDs],
@@ -798,6 +805,42 @@
return { inProgress, progress };
}
+function useFarcasterThreadRefresher(
+ activeChatThreadID: ?string,
+ threadType: ?ThreadType,
+ appFocused: boolean,
+): void {
+ const prevActiveThreadID = React.useRef<?string>(null);
+ const prevAppFocused = React.useRef(appFocused);
+ const farcasterRefreshConversation = useRefreshFarcasterConversation();
+ const farcasterMessageFetching = useFarcasterMessageFetching();
+
+ React.useEffect(() => {
+ if (
+ threadType &&
+ activeChatThreadID &&
+ (prevActiveThreadID.current !== activeChatThreadID ||
+ (appFocused && !prevAppFocused.current))
+ ) {
+ threadSpecs[threadType].protocol().onOpenThread?.(
+ { threadID: activeChatThreadID },
+ {
+ farcasterRefreshConversation,
+ farcasterMessageFetching,
+ },
+ );
+ }
+ prevActiveThreadID.current = activeChatThreadID;
+ prevAppFocused.current = appFocused;
+ }, [
+ activeChatThreadID,
+ appFocused,
+ farcasterMessageFetching,
+ farcasterRefreshConversation,
+ threadType,
+ ]);
+}
+
export {
useFarcasterConversationsSync,
useFetchConversationWithBatching,
@@ -807,4 +850,5 @@
useRefreshFarcasterConversation,
useAddNewFarcasterMessage,
useFarcasterSync,
+ useFarcasterThreadRefresher,
};
diff --git a/lib/shared/farcaster/farcaster-message-fetching-context.js b/lib/shared/farcaster/farcaster-message-fetching-context.js
new file mode 100644
--- /dev/null
+++ b/lib/shared/farcaster/farcaster-message-fetching-context.js
@@ -0,0 +1,217 @@
+// @flow
+
+import invariant from 'invariant';
+import * as React from 'react';
+import { useDispatch } from 'react-redux';
+
+import { processFarcasterOpsActionType } from './farcaster-actions.js';
+import { useFetchMessagesForConversation } from './farcaster-hooks.js';
+import {
+ type MessageTruncationStatus,
+ messageTruncationStatus,
+ type RawMessageInfo,
+} from '../../types/message-types.js';
+import type { ClientUpdateInfo } from '../../types/update-types.js';
+import { conversationIDFromFarcasterThreadID } from '../id-utils.js';
+import { fetchMessagesFromDB } from '../message-utils.js';
+
+type ThreadFetchingState = {
+ +farcasterCursor: ?string,
+ +dbExhausted: boolean,
+ +farcasterExhausted: boolean,
+};
+
+export type FarcasterMessageFetchingContextType = {
+ +fetchMoreMessages: (
+ threadID: string,
+ numMessages: number,
+ currentOffset: number,
+ ) => Promise<void>,
+};
+
+const FarcasterMessageFetchingContext: React.Context<?FarcasterMessageFetchingContextType> =
+ React.createContext<?FarcasterMessageFetchingContextType>(null);
+
+type ProcessResultsInput = {
+ +dbResult: ?{
+ +threadID: string,
+ +rawMessageInfos: $ReadOnlyArray<RawMessageInfo>,
+ +truncationStatus: MessageTruncationStatus,
+ },
+ +farcasterResult: ?{
+ +messages: $ReadOnlyArray<RawMessageInfo>,
+ +newCursor: ?string,
+ +userIDs: $ReadOnlyArray<string>,
+ },
+};
+
+type ProcessResultsOutput = {
+ +combinedMessages: $ReadOnlyArray<RawMessageInfo>,
+ +newCursor: ?string,
+ +bothExhausted: boolean,
+ +userIDs: $ReadOnlyArray<string>,
+};
+
+function processResults(input: ProcessResultsInput): ProcessResultsOutput {
+ const { dbResult, farcasterResult } = input;
+
+ const dbMessages = dbResult?.rawMessageInfos ?? [];
+ const farcasterMessages = farcasterResult?.messages ?? [];
+
+ // Deduplicate messages, keeping Farcaster messages over DB messages
+ const messageMap = new Map<string, RawMessageInfo>();
+
+ // First add DB messages
+ for (const message of dbMessages) {
+ if (message.id) {
+ messageMap.set(message.id, message);
+ }
+ }
+
+ for (const message of farcasterMessages) {
+ if (message.id) {
+ messageMap.set(message.id, message);
+ }
+ }
+
+ const combinedMessages = Array.from(messageMap.values());
+
+ const newCursor = farcasterResult?.newCursor ?? null;
+ const dbExhausted =
+ dbResult?.truncationStatus === messageTruncationStatus.EXHAUSTIVE;
+ const farcasterExhausted = !newCursor;
+
+ return {
+ combinedMessages,
+ newCursor,
+ bothExhausted: dbExhausted && farcasterExhausted,
+ userIDs: farcasterResult?.userIDs ?? [],
+ };
+}
+
+type Props = {
+ +children: React.Node,
+};
+
+function FarcasterMessageFetchingProvider(props: Props): React.Node {
+ const { children } = props;
+
+ const [threadStates, setThreadStates] = React.useState<{
+ [threadID: string]: ThreadFetchingState,
+ }>({});
+
+ const getState = React.useCallback(
+ (threadID: string): ThreadFetchingState => {
+ return (
+ threadStates[threadID] ?? {
+ farcasterCursor: null,
+ dbExhausted: false,
+ farcasterExhausted: false,
+ }
+ );
+ },
+ [threadStates],
+ );
+
+ const updateState = React.useCallback(
+ (threadID: string, updates: Partial<ThreadFetchingState>) => {
+ setThreadStates(prevStates => ({
+ ...prevStates,
+ [threadID]: {
+ ...getState(threadID),
+ ...updates,
+ },
+ }));
+ },
+ [getState],
+ );
+
+ const fetchFarcasterMessagesForConversation =
+ useFetchMessagesForConversation();
+ const dispatch = useDispatch();
+ const fetchMoreMessages = React.useCallback(
+ async (
+ threadID: string,
+ numMessages: number,
+ currentOffset: number,
+ ): Promise<void> => {
+ const state =
+ currentOffset === 0
+ ? {
+ farcasterCursor: null,
+ dbExhausted: false,
+ farcasterExhausted: false,
+ }
+ : getState(threadID);
+ const conversationID = conversationIDFromFarcasterThreadID(threadID);
+
+ const dbPromise = !state.dbExhausted
+ ? fetchMessagesFromDB(threadID, numMessages, currentOffset)
+ : Promise.resolve(null);
+
+ const farcasterPromise = !state.farcasterExhausted
+ ? fetchFarcasterMessagesForConversation(
+ conversationID,
+ numMessages,
+ state.farcasterCursor ?? null,
+ )
+ : Promise.resolve(null);
+
+ const [dbResult, farcasterResult] = await Promise.allSettled([
+ dbPromise,
+ farcasterPromise,
+ ]);
+
+ const processedResults = processResults({
+ dbResult: dbResult.status === 'fulfilled' ? dbResult.value : null,
+ farcasterResult:
+ farcasterResult.status === 'fulfilled' ? farcasterResult.value : null,
+ });
+
+ updateState(threadID, {
+ farcasterCursor: processedResults.newCursor,
+ dbExhausted:
+ dbResult.status === 'fulfilled' &&
+ dbResult.value?.truncationStatus ===
+ messageTruncationStatus.EXHAUSTIVE,
+ farcasterExhausted: !processedResults.newCursor,
+ });
+
+ dispatch({
+ type: processFarcasterOpsActionType,
+ payload: {
+ rawMessageInfos: processedResults.combinedMessages,
+ updateInfos: ([]: Array<ClientUpdateInfo>),
+ userIDs: processedResults.userIDs,
+ truncationStatuses: {
+ [threadID]: processedResults.bothExhausted
+ ? messageTruncationStatus.EXHAUSTIVE
+ : messageTruncationStatus.UNCHANGED,
+ },
+ },
+ });
+ },
+ [getState, fetchFarcasterMessagesForConversation, updateState, dispatch],
+ );
+
+ const contextValue = React.useMemo(
+ () => ({
+ fetchMoreMessages,
+ }),
+ [fetchMoreMessages],
+ );
+
+ return (
+ <FarcasterMessageFetchingContext.Provider value={contextValue}>
+ {children}
+ </FarcasterMessageFetchingContext.Provider>
+ );
+}
+
+function useFarcasterMessageFetching(): FarcasterMessageFetchingContextType {
+ const context = React.useContext(FarcasterMessageFetchingContext);
+ invariant(context, 'FarcasterMessageFetchingContext must be set');
+ return context;
+}
+
+export { FarcasterMessageFetchingProvider, useFarcasterMessageFetching };
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.js';
import * as React from 'react';
+import { useFarcasterMessageFetching } from './farcaster/farcaster-message-fetching-context.js';
import { codeBlockRegex, type ParserRules } from './markdown.js';
import type { CreationSideEffectsFunc } from './messages/message-spec.js';
import { messageSpecs } from './messages/message-specs.js';
@@ -637,6 +638,7 @@
const callFetchMessagesBeforeCursor = useFetchMessagesBeforeCursor();
const callFetchMostRecentMessages = useFetchMostRecentMessages();
const dispatchActionPromise = useDispatchActionPromise();
+ const farcasterMessageFetching = useFarcasterMessageFetching();
React.useEffect(() => {
registerFetchKey(fetchMessagesBeforeCursorActionTypes);
@@ -664,6 +666,7 @@
keyserverFetchMessagesBeforeCursor: callFetchMessagesBeforeCursor,
keyserverFetchMostRecentMessages: callFetchMostRecentMessages,
dispatchActionPromise,
+ farcasterMessageFetching,
},
);
},
@@ -671,6 +674,7 @@
callFetchMessagesBeforeCursor,
callFetchMostRecentMessages,
dispatchActionPromise,
+ farcasterMessageFetching,
messageIDs?.length,
oldestMessageServerID,
threadID,
@@ -705,5 +709,6 @@
isInvalidPinSourceForThread,
isUnableToBeRenderedIndependently,
findNewestMessageTimePerKeyserver,
+ fetchMessagesFromDB,
useFetchMessages,
};
diff --git a/lib/shared/threads/protocols/farcaster-thread-protocol.js b/lib/shared/threads/protocols/farcaster-thread-protocol.js
--- a/lib/shared/threads/protocols/farcaster-thread-protocol.js
+++ b/lib/shared/threads/protocols/farcaster-thread-protocol.js
@@ -2,7 +2,6 @@
import invariant from 'invariant';
-import { fetchMessagesBeforeCursorActionTypes } from '../../../actions/message-actions.js';
import {
changeThreadMemberRolesActionTypes,
changeThreadSettingsActionTypes,
@@ -672,18 +671,11 @@
) => {
const { threadID, numMessagesToFetch, currentNumberOfFetchedMessages } =
input;
- const promise = (async () => {
- return await utils.fetchMessagesFromDB(
- threadID,
- numMessagesToFetch ?? defaultNumberPerThread,
- currentNumberOfFetchedMessages,
- );
- })();
- void utils.dispatchActionPromise(
- fetchMessagesBeforeCursorActionTypes,
- promise,
+ await utils.farcasterMessageFetching.fetchMoreMessages(
+ threadID,
+ numMessagesToFetch ?? defaultNumberPerThread,
+ currentNumberOfFetchedMessages,
);
- await promise;
},
createPendingThread: (
@@ -820,8 +812,11 @@
input: ProtocolOnOpenThreadInput,
utils: OnOpenThreadUtils,
) => {
- const conversationID = conversationIDFromFarcasterThreadID(input.threadID);
- void utils.farcasterRefreshConversation(conversationID);
+ void utils.farcasterMessageFetching.fetchMoreMessages(
+ input.threadID,
+ defaultNumberPerThread,
+ 0,
+ );
},
threadIDMatchesProtocol: (threadID: string): boolean => {
diff --git a/lib/shared/threads/thread-spec.js b/lib/shared/threads/thread-spec.js
--- a/lib/shared/threads/thread-spec.js
+++ b/lib/shared/threads/thread-spec.js
@@ -101,6 +101,7 @@
SendReactionInput,
} from '../farcaster/farcaster-api.js';
import type { FarcasterConversation } from '../farcaster/farcaster-conversation-types.js';
+import type { FarcasterMessageFetchingContextType } from '../farcaster/farcaster-message-fetching-context.js';
import type { FetchMessagesFromDBType } from '../message-utils.js';
import type {
CreationSideEffectsFunc,
@@ -314,6 +315,7 @@
+keyserverFetchMessagesBeforeCursor: FetchMessagesBeforeCursorInput => Promise<FetchMessageInfosPayload>,
+keyserverFetchMostRecentMessages: FetchMostRecentMessagesInput => Promise<FetchMessageInfosPayload>,
+dispatchActionPromise: DispatchActionPromise,
+ +farcasterMessageFetching: FarcasterMessageFetchingContextType,
};
export type ProtocolCreatePendingThreadInput = {
@@ -367,6 +369,7 @@
};
export type OnOpenThreadUtils = {
+farcasterRefreshConversation: (conversationID: string) => Promise<mixed>,
+ +farcasterMessageFetching: FarcasterMessageFetchingContextType,
};
export type ProtocolName = 'Comm DM' | 'Farcaster DC' | 'Keyserver';
diff --git a/native/chat/message-list-container.react.js b/native/chat/message-list-container.react.js
--- a/native/chat/message-list-container.react.js
+++ b/native/chat/message-list-container.react.js
@@ -11,16 +11,14 @@
import genesis from 'lib/facts/genesis.js';
import { threadInfoSelector } from 'lib/selectors/thread-selectors.js';
import { userInfoSelectorForPotentialMembers } from 'lib/selectors/user-selectors.js';
-import { useRefreshFarcasterConversation } from 'lib/shared/farcaster/farcaster-hooks.js';
+import { useFarcasterThreadRefresher } from 'lib/shared/farcaster/farcaster-hooks.js';
+import { useIsAppForegrounded } from 'lib/shared/lifecycle-utils.js';
import {
usePotentialMemberItems,
useSearchUsers,
} from 'lib/shared/search-utils.js';
import { useExistingThreadInfoFinder } from 'lib/shared/thread-utils.js';
-import {
- threadTypeIsPersonal,
- threadSpecs,
-} from 'lib/shared/threads/thread-specs.js';
+import { threadTypeIsPersonal } from 'lib/shared/threads/thread-specs.js';
import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js';
import type { AccountUserInfo, UserListItem } from 'lib/types/user-types.js';
import { pinnedMessageCountText } from 'lib/utils/message-pinning-utils.js';
@@ -434,19 +432,12 @@
colors.panelBackgroundLabel,
]);
- const prevActiveThreadID = React.useRef(threadInfo.id);
- const farcasterRefreshConversation = useRefreshFarcasterConversation();
- React.useEffect(() => {
- if (prevActiveThreadID !== threadInfo.id) {
- threadSpecs[threadInfo.type]
- .protocol()
- .onOpenThread?.(
- { threadID: threadInfo.id },
- { farcasterRefreshConversation },
- );
- prevActiveThreadID.current = threadInfo.id;
- }
- }, [farcasterRefreshConversation, threadInfo.id, threadInfo.type]);
+ const isAppForegrounded = useIsAppForegrounded();
+ useFarcasterThreadRefresher(
+ threadInfo.id,
+ threadInfo.type,
+ isAppForegrounded,
+ );
let relationshipPrompt = null;
if (threadTypeIsPersonal(threadInfo.type)) {
diff --git a/native/root.react.js b/native/root.react.js
--- a/native/root.react.js
+++ b/native/root.react.js
@@ -52,6 +52,7 @@
import { IdentitySearchProvider } from 'lib/identity-search/identity-search-context.js';
import { CallKeyserverEndpointProvider } from 'lib/keyserver-conn/call-keyserver-endpoint-provider.react.js';
import KeyserverConnectionsHandler from 'lib/keyserver-conn/keyserver-connections-handler.js';
+import { FarcasterMessageFetchingProvider } from 'lib/shared/farcaster/farcaster-message-fetching-context.js';
import { TunnelbrokerProvider } from 'lib/tunnelbroker/tunnelbroker-context.js';
import { actionLogger } from 'lib/utils/action-logger.js';
import { useFullBackupSupportEnabled } from 'lib/utils/services-utils.js';
@@ -340,7 +341,9 @@
<GlobalSearchIndexProvider>
<NUXTipsContextProvider>
<BackupHandlerContextProvider>
- <RootNavigator />
+ <FarcasterMessageFetchingProvider>
+ <RootNavigator />
+ </FarcasterMessageFetchingProvider>
</BackupHandlerContextProvider>
</NUXTipsContextProvider>
</GlobalSearchIndexProvider>
diff --git a/web/app.react.js b/web/app.react.js
--- a/web/app.react.js
+++ b/web/app.react.js
@@ -43,6 +43,7 @@
combineLoadingStatuses,
} from 'lib/selectors/loading-selectors.js';
import { isLoggedIn } from 'lib/selectors/user-selectors.js';
+import { FarcasterMessageFetchingProvider } from 'lib/shared/farcaster/farcaster-message-fetching-context.js';
import { extractMajorDesktopVersion } from 'lib/shared/version-utils.js';
import type { SecondaryTunnelbrokerConnection } from 'lib/tunnelbroker/secondary-tunnelbroker-connection.js';
import { TunnelbrokerProvider } from 'lib/tunnelbroker/tunnelbroker-context.js';
@@ -235,8 +236,10 @@
<EditUserAvatarProvider>
<StaffContextProvider>
<MemberListSidebarProvider>
- {this.renderMainContent()}
- {this.props.modals}
+ <FarcasterMessageFetchingProvider>
+ {this.renderMainContent()}
+ {this.props.modals}
+ </FarcasterMessageFetchingProvider>
</MemberListSidebarProvider>
</StaffContextProvider>
</EditUserAvatarProvider>
diff --git a/web/chat/chat-message-list-container.react.js b/web/chat/chat-message-list-container.react.js
--- a/web/chat/chat-message-list-container.react.js
+++ b/web/chat/chat-message-list-container.react.js
@@ -7,9 +7,8 @@
import { NativeTypes } from 'react-dnd-html5-backend';
import { useProtocolSelection } from 'lib/contexts/protocol-selection-context.js';
-import { useRefreshFarcasterConversation } from 'lib/shared/farcaster/farcaster-hooks.js';
+import { useFarcasterThreadRefresher } from 'lib/shared/farcaster/farcaster-hooks.js';
import { threadIsPending } from 'lib/shared/thread-utils.js';
-import { threadSpecs } from 'lib/shared/threads/thread-specs.js';
import { useWatchThread } from 'lib/shared/watch-thread-utils.js';
import { useDispatch } from 'lib/utils/redux-utils.js';
@@ -20,6 +19,7 @@
import ThreadTopBar from './thread-top-bar.react.js';
import { InputStateContext } from '../input/input-state.js';
import { updateNavInfoActionType } from '../redux/action-types.js';
+import { useSelector } from '../redux/redux-utils.js';
import {
useThreadInfoForPossiblyPendingThread,
useInfosForPendingThread,
@@ -150,19 +150,12 @@
);
}, [inputState, isChatCreation, selectedUserInfos, threadInfo]);
- const prevActiveThreadID = React.useRef(activeChatThreadID);
- const farcasterRefreshConversation = useRefreshFarcasterConversation();
- React.useEffect(() => {
- if (prevActiveThreadID !== activeChatThreadID && activeChatThreadID) {
- threadSpecs[threadInfo.type]
- .protocol()
- .onOpenThread?.(
- { threadID: activeChatThreadID },
- { farcasterRefreshConversation },
- );
- prevActiveThreadID.current = activeChatThreadID;
- }
- }, [farcasterRefreshConversation, activeChatThreadID, threadInfo.type]);
+ const windowActive = useSelector(state => state.windowActive);
+ useFarcasterThreadRefresher(
+ activeChatThreadID,
+ threadInfo.type,
+ windowActive,
+ );
return connectDropTarget(
<div className={containerStyle} ref={containerRef}>

File Metadata

Mime Type
text/plain
Expires
Sat, Dec 6, 10:43 PM (23 h, 1 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5841221
Default Alt Text
D15375.1765060982.diff (22 KB)

Event Timeline