[FIX] Messages not loading on some edge cases (#4801)

This commit is contained in:
Diego Mello 2023-01-13 16:32:52 -03:00 committed by GitHub
parent a1580811ed
commit 5387d31a68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 198 additions and 122 deletions

View File

@ -28,7 +28,9 @@ export const ROOM = createRequestTypes('ROOM', [
'DELETE', 'DELETE',
'REMOVED', 'REMOVED',
'FORWARD', 'FORWARD',
'USER_TYPING' 'USER_TYPING',
'HISTORY_REQUEST',
'HISTORY_FINISHED'
]); ]);
export const INQUIRY = createRequestTypes('INQUIRY', [ export const INQUIRY = createRequestTypes('INQUIRY', [
...defaultTypes, ...defaultTypes,

View File

@ -1,6 +1,6 @@
import { Action } from 'redux'; import { Action } from 'redux';
import { ERoomType } from '../definitions/ERoomType'; import { ERoomType, RoomType } from '../definitions';
import { ROOM } from './actionsTypes'; import { ROOM } from './actionsTypes';
// TYPE RETURN RELATED // TYPE RETURN RELATED
@ -44,7 +44,24 @@ interface IUserTyping extends Action {
status: boolean; status: boolean;
} }
export type TActionsRoom = TSubscribeRoom & TUnsubscribeRoom & ILeaveRoom & IDeleteRoom & IForwardRoom & IUserTyping; export interface IRoomHistoryRequest extends Action {
rid: string;
t: RoomType;
loaderId: string;
}
export interface IRoomHistoryFinished extends Action {
loaderId: string;
}
export type TActionsRoom = TSubscribeRoom &
TUnsubscribeRoom &
ILeaveRoom &
IDeleteRoom &
IForwardRoom &
IUserTyping &
IRoomHistoryRequest &
IRoomHistoryFinished;
export function subscribeRoom(rid: string): TSubscribeRoom { export function subscribeRoom(rid: string): TSubscribeRoom {
return { return {
@ -99,3 +116,19 @@ export function userTyping(rid: string, status = true): IUserTyping {
status status
}; };
} }
export function roomHistoryRequest({ rid, t, loaderId }: { rid: string; t: RoomType; loaderId: string }): IRoomHistoryRequest {
return {
type: ROOM.HISTORY_REQUEST,
rid,
t,
loaderId
};
}
export function roomHistoryFinished({ loaderId }: { loaderId: string }): IRoomHistoryFinished {
return {
type: ROOM.HISTORY_FINISHED,
loaderId
};
}

View File

@ -407,7 +407,8 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
threadBadgeColor, threadBadgeColor,
toggleFollowThread, toggleFollowThread,
replies replies
}}> }}
>
{/* @ts-ignore*/} {/* @ts-ignore*/}
<Message <Message
id={id} id={id}

View File

@ -29,6 +29,7 @@ export * from './ISearch';
export * from './TUserStatus'; export * from './TUserStatus';
export * from './IProfile'; export * from './IProfile';
export * from './IReaction'; export * from './IReaction';
export * from './ERoomType';
export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> { export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> {
navigation: StackNavigationProp<T, S>; navigation: StackNavigationProp<T, S>;

View File

@ -1,16 +1,14 @@
import { SubscriptionType, TAnyMessageModel } from '../../../definitions'; import { SubscriptionType, TAnyMessageModel } from '../../definitions';
import { loadNextMessages, loadMessagesForRoom } from '../../../lib/methods'; import { loadNextMessages, loadMessagesForRoom } from '.';
import { MessageTypeLoad } from '../../../lib/constants'; import { MessageTypeLoad } from '../constants';
const getMoreMessages = ({ const getMoreMessages = ({
rid, rid,
t, t,
tmid,
loaderItem loaderItem
}: { }: {
rid: string; rid: string;
t: SubscriptionType; t: SubscriptionType;
tmid?: string;
loaderItem: TAnyMessageModel; loaderItem: TAnyMessageModel;
}): Promise<void> => { }): Promise<void> => {
if ([MessageTypeLoad.MORE, MessageTypeLoad.PREVIOUS_CHUNK].includes(loaderItem.t as MessageTypeLoad)) { if ([MessageTypeLoad.MORE, MessageTypeLoad.PREVIOUS_CHUNK].includes(loaderItem.t as MessageTypeLoad)) {
@ -25,7 +23,6 @@ const getMoreMessages = ({
if (loaderItem.t === MessageTypeLoad.NEXT_CHUNK) { if (loaderItem.t === MessageTypeLoad.NEXT_CHUNK) {
return loadNextMessages({ return loadNextMessages({
rid, rid,
tmid,
ts: loaderItem.ts as Date, ts: loaderItem.ts as Date,
loaderItem loaderItem
}); });

View File

@ -16,6 +16,7 @@ export * from './getSingleMessage';
export * from './getSlashCommands'; export * from './getSlashCommands';
export * from './getThreadName'; export * from './getThreadName';
export * from './getUsersPresence'; export * from './getUsersPresence';
export * from './getMoreMessages';
export * from './loadMessagesForRoom'; export * from './loadMessagesForRoom';
export * from './loadMissedMessages'; export * from './loadMissedMessages';
export * from './loadNextMessages'; export * from './loadNextMessages';

View File

@ -15,7 +15,6 @@ const COUNT = 50;
interface ILoadNextMessages { interface ILoadNextMessages {
rid: string; rid: string;
ts: Date; ts: Date;
tmid?: string;
loaderItem: TMessageModel; loaderItem: TMessageModel;
} }
@ -32,7 +31,6 @@ export function loadNextMessages(args: ILoadNextMessages): Promise<void> {
const loadMoreItem = { const loadMoreItem = {
_id: generateLoadMoreId(lastMessage._id), _id: generateLoadMoreId(lastMessage._id),
rid: lastMessage.rid, rid: lastMessage.rid,
tmid: args.tmid,
ts: moment(lastMessage.ts).add(1, 'millisecond'), ts: moment(lastMessage.ts).add(1, 'millisecond'),
t: MessageTypeLoad.NEXT_CHUNK t: MessageTypeLoad.NEXT_CHUNK
}; };

View File

@ -19,9 +19,6 @@ export function loadSurroundingMessages({ messageId, rid }: { messageId: string;
let messages: IMessage[] = EJSON.fromJSONValue(data?.messages); let messages: IMessage[] = EJSON.fromJSONValue(data?.messages);
messages = orderBy(messages, 'ts'); messages = orderBy(messages, 'ts');
const message = messages.find(m => m._id === messageId);
const tmid = message?.tmid;
if (messages?.length) { if (messages?.length) {
if (data?.moreBefore) { if (data?.moreBefore) {
const firstMessage = messages[0]; const firstMessage = messages[0];
@ -30,7 +27,6 @@ export function loadSurroundingMessages({ messageId, rid }: { messageId: string;
const loadMoreItem = { const loadMoreItem = {
_id: generateLoadMoreId(firstMessage._id), _id: generateLoadMoreId(firstMessage._id),
rid: firstMessage.rid, rid: firstMessage.rid,
tmid,
ts: moment(firstMessage.ts).subtract(1, 'millisecond').toDate(), ts: moment(firstMessage.ts).subtract(1, 'millisecond').toDate(),
t: MessageTypeLoad.PREVIOUS_CHUNK, t: MessageTypeLoad.PREVIOUS_CHUNK,
msg: firstMessage.msg msg: firstMessage.msg
@ -46,7 +42,6 @@ export function loadSurroundingMessages({ messageId, rid }: { messageId: string;
const loadMoreItem = { const loadMoreItem = {
_id: generateLoadMoreId(lastMessage._id), _id: generateLoadMoreId(lastMessage._id),
rid: lastMessage.rid, rid: lastMessage.rid,
tmid,
ts: moment(lastMessage.ts).add(1, 'millisecond').toDate(), ts: moment(lastMessage.ts).add(1, 'millisecond').toDate(),
t: MessageTypeLoad.NEXT_CHUNK, t: MessageTypeLoad.NEXT_CHUNK,
msg: lastMessage.msg msg: lastMessage.msg

View File

@ -1,4 +1,13 @@
import { deleteRoom, forwardRoom, leaveRoom, removedRoom, subscribeRoom, unsubscribeRoom } from '../actions/room'; import {
deleteRoom,
forwardRoom,
leaveRoom,
removedRoom,
roomHistoryFinished,
roomHistoryRequest,
subscribeRoom,
unsubscribeRoom
} from '../actions/room';
import { ERoomType } from '../definitions/ERoomType'; import { ERoomType } from '../definitions/ERoomType';
import { mockedStore } from './mockedStore'; import { mockedStore } from './mockedStore';
import { initialState } from './room'; import { initialState } from './room';
@ -48,4 +57,16 @@ describe('test room reducer', () => {
const { isDeleting } = mockedStore.getState().room; const { isDeleting } = mockedStore.getState().room;
expect(isDeleting).toEqual(false); expect(isDeleting).toEqual(false);
}); });
it('should return historyLoaders with one item after call historyRequest', () => {
mockedStore.dispatch(roomHistoryRequest({ rid: 'GENERAL', t: 'c', loaderId: 'loader' }));
const { historyLoaders } = mockedStore.getState().room;
expect(historyLoaders).toEqual(['loader']);
});
it('should return historyLoaders with empty array after call historyFinished', () => {
mockedStore.dispatch(roomHistoryFinished({ loaderId: 'loader' }));
const { historyLoaders } = mockedStore.getState().room;
expect(historyLoaders).toEqual([]);
});
}); });

View File

@ -7,12 +7,14 @@ export interface IRoom {
rid: string; rid: string;
isDeleting: boolean; isDeleting: boolean;
subscribedRoom: string; subscribedRoom: string;
historyLoaders: string[];
} }
export const initialState: IRoom = { export const initialState: IRoom = {
rid: '', rid: '',
isDeleting: false, isDeleting: false,
subscribedRoom: '' subscribedRoom: '',
historyLoaders: []
}; };
export default function (state = initialState, action: TActionsRoom): IRoom { export default function (state = initialState, action: TActionsRoom): IRoom {
@ -56,6 +58,16 @@ export default function (state = initialState, action: TActionsRoom): IRoom {
...state, ...state,
isDeleting: false isDeleting: false
}; };
case ROOM.HISTORY_REQUEST:
return {
...state,
historyLoaders: [...state.historyLoaders, action.loaderId]
};
case ROOM.HISTORY_FINISHED:
return {
...state,
historyLoaders: state.historyLoaders.filter(loaderId => loaderId !== action.loaderId)
};
default: default:
return state; return state;
} }

View File

@ -1,5 +1,5 @@
import { Alert } from 'react-native'; import { Alert } from 'react-native';
import { delay, put, race, select, take, takeLatest } from 'redux-saga/effects'; import { delay, put, race, select, take, takeLatest, actionChannel } from 'redux-saga/effects';
import EventEmitter from '../lib/methods/helpers/events'; import EventEmitter from '../lib/methods/helpers/events';
import Navigation from '../lib/navigation/appNavigation'; import Navigation from '../lib/navigation/appNavigation';
@ -10,6 +10,26 @@ import I18n from '../i18n';
import { showErrorAlert } from '../lib/methods/helpers/info'; import { showErrorAlert } from '../lib/methods/helpers/info';
import { LISTENER } from '../containers/Toast'; import { LISTENER } from '../containers/Toast';
import { Services } from '../lib/services'; import { Services } from '../lib/services';
import getMoreMessages from '../lib/methods/getMoreMessages';
import { getMessageById } from '../lib/database/services/Message';
function* watchHistoryRequests() {
const requestChan = yield actionChannel(types.ROOM.HISTORY_REQUEST);
while (true) {
const { rid, t, tmid, loaderId } = yield take(requestChan);
const loaderItem = yield getMessageById(loaderId);
if (loaderItem) {
try {
yield getMoreMessages({ rid, t, tmid, loaderItem });
} catch (e) {
log(e);
} finally {
yield put({ type: types.ROOM.HISTORY_FINISHED, loaderId });
}
}
}
}
const watchUserTyping = function* watchUserTyping({ rid, status }) { const watchUserTyping = function* watchUserTyping({ rid, status }) {
const auth = yield select(state => state.login.isAuthenticated); const auth = yield select(state => state.login.isAuthenticated);
@ -132,5 +152,6 @@ const root = function* root() {
yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom); yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom);
yield takeLatest(types.ROOM.DELETE, handleDeleteRoom); yield takeLatest(types.ROOM.DELETE, handleDeleteRoom);
yield takeLatest(types.ROOM.FORWARD, handleForwardRoom); yield takeLatest(types.ROOM.FORWARD, handleForwardRoom);
yield watchHistoryRequests();
}; };
export default root; export default root;

View File

@ -66,7 +66,7 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) {
*/ */
.filter(sub => subscribedRoom !== sub.rid) .filter(sub => subscribedRoom !== sub.rid)
.map(sub => sub.lastMessage && buildMessage(sub.lastMessage)) .map(sub => sub.lastMessage && buildMessage(sub.lastMessage))
.filter(lm => lm); .filter(lm => lm && lm._id && lm.rid);
const lastMessagesIds = lastMessages.map(lm => lm._id).filter(lm => lm); const lastMessagesIds = lastMessages.map(lm => lm._id).filter(lm => lm);
const existingMessages = yield messagesCollection.query(Q.where('id', Q.oneOf(lastMessagesIds))).fetch(); const existingMessages = yield messagesCollection.query(Q.where('id', Q.oneOf(lastMessagesIds))).fetch();
const messagesToUpdate = existingMessages.filter(i1 => lastMessages.find(i2 => i1.id === i2._id)); const messagesToUpdate = existingMessages.filter(i1 => lastMessages.find(i2 => i1.id === i2._id));

View File

@ -11,28 +11,28 @@ export default {
title: 'RoomView/LoadMore' title: 'RoomView/LoadMore'
}; };
const load = () => new Promise(res => setTimeout(res, 1000)); const LoadMore = ({ ...props }) => (
<LoadMoreComponent rid='rid' t='c' loaderId='loaderId' type={MessageTypeLoad.MORE} runOnRender={false} {...props} />
const LoadMore = ({ ...props }) => <LoadMoreComponent type={MessageTypeLoad.MORE} load={load} runOnRender={false} {...props} />; );
export const Basic = () => ( export const Basic = () => (
<> <>
<LoadMore /> <LoadMore loaderId='1' />
<LoadMore runOnRender /> <LoadMore loaderId='2' runOnRender />
<LoadMore type={MessageTypeLoad.PREVIOUS_CHUNK} /> <LoadMore loaderId='3' type={MessageTypeLoad.PREVIOUS_CHUNK} />
<LoadMore type={MessageTypeLoad.NEXT_CHUNK} /> <LoadMore loaderId='4' type={MessageTypeLoad.NEXT_CHUNK} />
</> </>
); );
const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => ( const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => (
<ThemeContext.Provider value={{ theme, colors: themes[theme] }}> <ThemeContext.Provider value={{ theme, colors: themes[theme] }}>
<ScrollView style={{ backgroundColor: themes[theme].backgroundColor }}> <ScrollView style={{ backgroundColor: themes[theme].backgroundColor }}>
<LoadMore type={MessageTypeLoad.PREVIOUS_CHUNK} /> <LoadMore loaderId='5' type={MessageTypeLoad.PREVIOUS_CHUNK} />
<Message msg='Hey!' theme={theme} /> <Message msg='Hey!' theme={theme} />
<Message msg={longText} theme={theme} isHeader={false} /> <Message msg={longText} theme={theme} isHeader={false} />
<Message msg='Older message' theme={theme} isHeader={false} /> <Message msg='Older message' theme={theme} isHeader={false} />
<LoadMore type={MessageTypeLoad.NEXT_CHUNK} /> <LoadMore loaderId='6' type={MessageTypeLoad.NEXT_CHUNK} />
<LoadMore type={MessageTypeLoad.MORE} /> <LoadMore loaderId='7' type={MessageTypeLoad.MORE} />
<Message msg={longText} theme={theme} /> <Message msg={longText} theme={theme} />
<Message msg='This is the third message' isHeader={false} theme={theme} /> <Message msg='This is the third message' isHeader={false} theme={theme} />
<Message msg='This is the second message' isHeader={false} theme={theme} /> <Message msg='This is the second message' isHeader={false} theme={theme} />

View File

@ -1,12 +1,15 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useEffect } from 'react';
import { ActivityIndicator, StyleSheet, Text } from 'react-native'; import { ActivityIndicator, StyleSheet, Text } from 'react-native';
import { useDispatch } from 'react-redux';
import { MessageTypeLoad, themes } from '../../../lib/constants'; import { MessageTypeLoad } from '../../../lib/constants';
import { MessageType } from '../../../definitions'; import { MessageType, RoomType } from '../../../definitions';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
import Touch from '../../../containers/Touch'; import Touch from '../../../containers/Touch';
import sharedStyles from '../../Styles'; import sharedStyles from '../../Styles';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
import { roomHistoryRequest } from '../../../actions/room';
import { useAppSelector } from '../../../lib/hooks';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
button: { button: {
@ -20,29 +23,25 @@ const styles = StyleSheet.create({
} }
}); });
const LoadMore = ({ const LoadMore = React.memo(
load, ({
rid,
t,
loaderId,
type, type,
runOnRender runOnRender
}: { }: {
load: Function; rid: string;
t: RoomType;
loaderId: string;
type: MessageType; type: MessageType;
runOnRender: boolean; runOnRender: boolean;
}): React.ReactElement => { }): React.ReactElement => {
const { theme } = useTheme(); const { colors } = useTheme();
const [loading, setLoading] = useState(false); const dispatch = useDispatch();
const loading = useAppSelector(state => state.room.historyLoaders.some(historyLoader => historyLoader === loaderId));
const handleLoad = useCallback(async () => { const handleLoad = () => dispatch(roomHistoryRequest({ rid, t, loaderId }));
try {
if (loading) {
return;
}
setLoading(true);
await load();
} finally {
setLoading(false);
}
}, [loading]);
useEffect(() => { useEffect(() => {
if (runOnRender) { if (runOnRender) {
@ -61,12 +60,13 @@ const LoadMore = ({
return ( return (
<Touch onPress={handleLoad} style={styles.button} enabled={!loading}> <Touch onPress={handleLoad} style={styles.button} enabled={!loading}>
{loading ? ( {loading ? (
<ActivityIndicator color={themes[theme].auxiliaryText} /> <ActivityIndicator color={colors.auxiliaryText} />
) : ( ) : (
<Text style={[styles.text, { color: themes[theme].titleText }]}>{I18n.t(text)}</Text> <Text style={[styles.text, { color: colors.titleText }]}>{I18n.t(text)}</Text>
)} )}
</Touch> </Touch>
); );
}; }
);
export default LoadMore; export default LoadMore;

View File

@ -75,7 +75,8 @@ import {
TThreadModel, TThreadModel,
ICustomEmojis, ICustomEmojis,
IEmoji, IEmoji,
TGetCustomEmoji TGetCustomEmoji,
RoomType
} from '../../definitions'; } from '../../definitions';
import { E2E_MESSAGE_TYPE, E2E_STATUS, MESSAGE_TYPE_ANY_LOAD, MessageTypeLoad, themes } from '../../lib/constants'; import { E2E_MESSAGE_TYPE, E2E_STATUS, MESSAGE_TYPE_ANY_LOAD, MessageTypeLoad, themes } from '../../lib/constants';
import { TListRef } from './List/List'; import { TListRef } from './List/List';
@ -685,7 +686,11 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
await loadThreadMessages({ tmid: this.tmid, rid: this.rid }); await loadThreadMessages({ tmid: this.tmid, rid: this.rid });
} else { } else {
const newLastOpen = new Date(); const newLastOpen = new Date();
await RoomServices.getMessages(room); await RoomServices.getMessages({
rid: room.rid,
lastOpen: 'lastOpen' in room ? room.lastOpen : undefined,
t: room.t as RoomType
});
// if room is joined // if room is joined
if (joined && 'id' in room) { if (joined && 'id' in room) {
@ -1301,16 +1306,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
return false; return false;
}; };
onLoadMoreMessages = (loaderItem: TAnyMessageModel) => {
const { room } = this.state;
return RoomServices.getMoreMessages({
rid: room.rid,
tmid: this.tmid,
t: room.t as any,
loaderItem
});
};
goToCannedResponses = () => { goToCannedResponses = () => {
const { room } = this.state; const { room } = this.state;
Navigation.navigate('CannedResponsesListView', { rid: room.rid }); Navigation.navigate('CannedResponsesListView', { rid: room.rid });
@ -1337,7 +1332,9 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
if (item.t && MESSAGE_TYPE_ANY_LOAD.includes(item.t as MessageTypeLoad)) { if (item.t && MESSAGE_TYPE_ANY_LOAD.includes(item.t as MessageTypeLoad)) {
content = ( content = (
<LoadMore <LoadMore
load={() => this.onLoadMoreMessages(item)} rid={room.rid}
t={room.t as RoomType}
loaderId={item.id}
type={item.t} type={item.t}
runOnRender={item.t === MessageTypeLoad.MORE && !previousItem} runOnRender={item.t === MessageTypeLoad.MORE && !previousItem}
/> />
@ -1502,7 +1499,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
return ( return (
<> <>
<MessageActions <MessageActions
ref={ref => this.messageActions = ref} ref={ref => (this.messageActions = ref)}
tmid={this.tmid} tmid={this.tmid}
room={room} room={room}
user={user} user={user}
@ -1512,7 +1509,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
onReactionPress={this.onReactionPress} onReactionPress={this.onReactionPress}
isReadOnly={readOnly} isReadOnly={readOnly}
/> />
<MessageErrorActions ref={ref => this.messageErrorActions = ref} tmid={this.tmid} /> <MessageErrorActions ref={ref => (this.messageErrorActions = ref)} tmid={this.tmid} />
</> </>
); );
}; };

View File

@ -1,23 +1,22 @@
import { loadMessagesForRoom, loadMissedMessages } from '../../../lib/methods'; import { loadMessagesForRoom, loadMissedMessages, RoomTypes } from '../../../lib/methods';
// TODO: clarify latest vs lastOpen interface IBaseParams {
const getMessages = ({
rid,
t,
latest,
lastOpen,
loaderItem
}: {
rid: string; rid: string;
t?: string;
latest?: Date;
lastOpen?: Date;
loaderItem?: any; // TODO: type this
}): Promise<void> => {
if (lastOpen) {
return loadMissedMessages({ rid, lastOpen });
} }
return loadMessagesForRoom({ rid, t: t as any, latest, loaderItem });
interface ILoadMessagesForRoomParams extends IBaseParams {
t: RoomTypes;
}
interface ILoadMissedMessagesParams extends IBaseParams {
lastOpen: Date;
}
const getMessages = (params: ILoadMissedMessagesParams | ILoadMessagesForRoomParams): Promise<void> => {
if ('lastOpen' in params) {
return loadMissedMessages(params);
}
return loadMessagesForRoom(params);
}; };
export default getMessages; export default getMessages;

View File

@ -1,9 +1,7 @@
import getMessages from './getMessages'; import getMessages from './getMessages';
import getMoreMessages from './getMoreMessages';
import getMessageInfo from './getMessageInfo'; import getMessageInfo from './getMessageInfo';
export default { export default {
getMessages, getMessages,
getMoreMessages,
getMessageInfo getMessageInfo
}; };