diff --git a/keyserver/src/responders/message-responders.js b/keyserver/src/responders/message-responders.js --- a/keyserver/src/responders/message-responders.js +++ b/keyserver/src/responders/message-responders.js @@ -24,7 +24,7 @@ type FetchPinnedMessagesRequest, type FetchPinnedMessagesResult, type SearchMessagesResponse, - type SearchMessagesRequest, + type SearchMessagesKeyserverRequest, } from 'lib/types/message-types.js'; import type { EditMessageData } from 'lib/types/messages/edit.js'; import type { ReactionMessageData } from 'lib/types/messages/reaction.js'; @@ -396,8 +396,8 @@ return await fetchPinnedMessageInfos(viewer, request); } -export const searchMessagesResponderInputValidator: TInterface = - tShape({ +export const searchMessagesResponderInputValidator: TInterface = + tShape({ query: t.String, threadID: tID, cursor: t.maybe(tID), @@ -405,7 +405,7 @@ async function searchMessagesResponder( viewer: Viewer, - request: SearchMessagesRequest, + request: SearchMessagesKeyserverRequest, ): Promise { return await searchMessagesInSingleChat( request.query, diff --git a/lib/actions/message-actions.js b/lib/actions/message-actions.js --- a/lib/actions/message-actions.js +++ b/lib/actions/message-actions.js @@ -1,9 +1,11 @@ // @flow import invariant from 'invariant'; +import * as React from 'react'; import type { CallSingleKeyserverEndpointResultInfo } from '../keyserver-conn/call-single-keyserver-endpoint.js'; import { + extractKeyserverIDFromIDOptional, extractKeyserverIDFromID, sortThreadIDsPerKeyserver, } from '../keyserver-conn/keyserver-call-utils.js'; @@ -19,16 +21,20 @@ FetchPinnedMessagesRequest, FetchPinnedMessagesResult, SearchMessagesRequest, + SearchMessagesKeyserverRequest, SearchMessagesResponse, FetchMessageInfosRequest, RawMessageInfo, MessageTruncationStatuses, } from '../types/message-types.js'; +import { defaultNumberPerThread } from '../types/message-types.js'; import type { MediaMessageServerDBContent } from '../types/messages/media.js'; import type { ToggleMessagePinRequest, ToggleMessagePinResult, } from '../types/thread-types.js'; +import { getConfig } from '../utils/config.js'; +import { translateClientDBMessageInfoToRawMessageInfo } from '../utils/message-ops-utils.js'; const fetchMessagesBeforeCursorActionTypes = Object.freeze({ started: 'FETCH_MESSAGES_BEFORE_CURSOR_STARTED', @@ -452,7 +458,9 @@ const searchMessages = ( callKeyserverEndpoint: CallKeyserverEndpoint, - ): ((input: SearchMessagesRequest) => Promise) => + ): (( + input: SearchMessagesKeyserverRequest, + ) => Promise) => async input => { const keyserverID = extractKeyserverIDFromID(input.threadID); const requests = { [keyserverID]: input }; @@ -468,7 +476,38 @@ function useSearchMessages(): ( input: SearchMessagesRequest, ) => Promise { - return useKeyserverCall(searchMessages); + const thinThreadCallback = useKeyserverCall(searchMessages); + return React.useCallback( + async (input: SearchMessagesRequest) => { + const isThreadThin = !!extractKeyserverIDFromIDOptional(input.threadID); + + if (isThreadThin) { + return await thinThreadCallback({ + query: input.query, + threadID: input.threadID, + cursor: input.messageIDcursor, + }); + } + + const { sqliteAPI } = getConfig(); + const timestampCursor = input.timestampCursor?.toString(); + const clientDBMessageInfos = await sqliteAPI.searchMessages( + input.query, + input.threadID, + timestampCursor, + input.messageIDcursor, + ); + + const messages = clientDBMessageInfos.map( + translateClientDBMessageInfoToRawMessageInfo, + ); + return { + endReached: messages.length < defaultNumberPerThread, + messages, + }; + }, + [thinThreadCallback], + ); } const toggleMessagePinActionTypes = Object.freeze({ diff --git a/lib/shared/search-utils.js b/lib/shared/search-utils.js --- a/lib/shared/search-utils.js +++ b/lib/shared/search-utils.js @@ -329,13 +329,21 @@ threadID: string, ) => mixed, queryID: number, - cursor?: ?string, + timestampCursor?: ?number, + messageIDCursor?: ?string, ) => void { const callSearchMessages = useSearchMessagesAction(); const dispatchActionPromise = useDispatchActionPromise(); return React.useCallback( - (query, threadID, onResultsReceived, queryID, cursor) => { + ( + query, + threadID, + onResultsReceived, + queryID, + timestampCursor, + messageIDcursor, + ) => { const searchMessagesPromise = (async () => { if (query === '') { onResultsReceived([], true, queryID, threadID); @@ -344,7 +352,8 @@ const { messages, endReached } = await callSearchMessages({ query, threadID, - cursor, + timestampCursor, + messageIDcursor, }); onResultsReceived(messages, endReached, queryID, threadID); })(); diff --git a/lib/types/message-types.js b/lib/types/message-types.js --- a/lib/types/message-types.js +++ b/lib/types/message-types.js @@ -692,6 +692,13 @@ }; export type SearchMessagesRequest = { + +query: string, + +threadID: string, + +timestampCursor?: ?number, + +messageIDcursor?: ?string, +}; + +export type SearchMessagesKeyserverRequest = { +query: string, +threadID: string, +cursor?: ?string, diff --git a/native/search/message-search.react.js b/native/search/message-search.react.js --- a/native/search/message-search.react.js +++ b/native/search/message-search.react.js @@ -47,6 +47,7 @@ }, [props.navigation, clearQuery]); const [lastID, setLastID] = React.useState(); + const [lastTimestamp, setLastTimestamp] = React.useState(); const [searchResults, setSearchResults] = React.useState< $ReadOnlyArray, >([]); @@ -74,6 +75,7 @@ React.useEffect(() => { setSearchResults([]); setLastID(undefined); + setLastTimestamp(undefined); setEndReached(false); }, [query, searchMessages]); @@ -84,9 +86,17 @@ threadInfo.id, appendSearchResults, queryIDRef.current, + lastTimestamp, lastID, ); - }, [appendSearchResults, lastID, query, searchMessages, threadInfo.id]); + }, [ + appendSearchResults, + lastID, + query, + searchMessages, + threadInfo.id, + lastTimestamp, + ]); const userInfos = useSelector(state => state.userStore.userInfos); @@ -190,8 +200,10 @@ if (endReached) { return; } - setLastID(oldestMessageID(measuredMessages)); - }, [endReached, measuredMessages, setLastID]); + const oldest = oldestMessage(measuredMessages); + setLastID(oldest?.id); + setLastTimestamp(oldest?.time); + }, [endReached, measuredMessages]); const styles = useStyles(unboundStyles); @@ -213,10 +225,10 @@ ); } -function oldestMessageID(data: $ReadOnlyArray) { +function oldestMessage(data: $ReadOnlyArray) { for (let i = data.length - 1; i >= 0; i--) { if (data[i].itemType === 'message' && data[i].messageInfo.id) { - return data[i].messageInfo.id; + return data[i].messageInfo; } } return undefined; diff --git a/web/search/message-search-state-provider.react.js b/web/search/message-search-state-provider.react.js --- a/web/search/message-search-state-provider.react.js +++ b/web/search/message-search-state-provider.react.js @@ -42,6 +42,9 @@ const lastIDs = React.useRef<{ [threadID: string]: string, }>({}); + const lastTimestamps = React.useRef<{ + [threadID: string]: number, + }>({}); const setEndReached = React.useCallback((threadID: string) => { endsReached.current.add(threadID); @@ -59,9 +62,10 @@ const appendResult = React.useCallback( (result: $ReadOnlyArray, threadID: string) => { - const lastMessageID = oldestMessageID(result); - if (lastMessageID) { - lastIDs.current[threadID] = lastMessageID; + const lastMessage = oldestMessage(result); + if (lastMessage?.id) { + lastIDs.current[threadID] = lastMessage.id; + lastTimestamps.current[threadID] = lastMessage.time; } setResults(prevResults => { const prevThreadResults = prevResults[threadID] ?? []; @@ -76,6 +80,7 @@ (threadID: string) => { loading.current = false; delete lastIDs.current[threadID]; + delete lastTimestamps.current[threadID]; removeEndReached(threadID); setResults(prevResults => { const { [threadID]: deleted, ...newState } = prevResults; @@ -149,6 +154,7 @@ threadID, appendResults, queryIDRef.current, + lastTimestamps.current[threadID], lastIDs.current[threadID], ); }, @@ -185,13 +191,13 @@ ); } -function oldestMessageID(data: $ReadOnlyArray) { +function oldestMessage(data: $ReadOnlyArray) { if (!data) { return undefined; } for (let i = data.length - 1; i >= 0; i--) { if (data[i].type === messageTypes.TEXT) { - return data[i].id; + return data[i]; } } return undefined;