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

This commit is contained in:
Diego Mello 2023-01-13 16:32:52 -03:00
parent bbe524ee55
commit 8ca06bd646
17 changed files with 198 additions and 122 deletions

View File

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

View File

@ -1,6 +1,6 @@
import { Action } from 'redux';
import { ERoomType } from '../definitions/ERoomType';
import { ERoomType, RoomType } from '../definitions';
import { ROOM } from './actionsTypes';
// TYPE RETURN RELATED
@ -44,7 +44,24 @@ interface IUserTyping extends Action {
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 {
return {
@ -99,3 +116,19 @@ export function userTyping(rid: string, status = true): IUserTyping {
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,
toggleFollowThread,
replies
}}>
}}
>
{/* @ts-ignore*/}
<Message
id={id}

View File

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

View File

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

View File

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

View File

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

View File

@ -19,9 +19,6 @@ export function loadSurroundingMessages({ messageId, rid }: { messageId: string;
let messages: IMessage[] = EJSON.fromJSONValue(data?.messages);
messages = orderBy(messages, 'ts');
const message = messages.find(m => m._id === messageId);
const tmid = message?.tmid;
if (messages?.length) {
if (data?.moreBefore) {
const firstMessage = messages[0];
@ -30,7 +27,6 @@ export function loadSurroundingMessages({ messageId, rid }: { messageId: string;
const loadMoreItem = {
_id: generateLoadMoreId(firstMessage._id),
rid: firstMessage.rid,
tmid,
ts: moment(firstMessage.ts).subtract(1, 'millisecond').toDate(),
t: MessageTypeLoad.PREVIOUS_CHUNK,
msg: firstMessage.msg
@ -46,7 +42,6 @@ export function loadSurroundingMessages({ messageId, rid }: { messageId: string;
const loadMoreItem = {
_id: generateLoadMoreId(lastMessage._id),
rid: lastMessage.rid,
tmid,
ts: moment(lastMessage.ts).add(1, 'millisecond').toDate(),
t: MessageTypeLoad.NEXT_CHUNK,
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 { mockedStore } from './mockedStore';
import { initialState } from './room';
@ -48,4 +57,16 @@ describe('test room reducer', () => {
const { isDeleting } = mockedStore.getState().room;
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;
isDeleting: boolean;
subscribedRoom: string;
historyLoaders: string[];
}
export const initialState: IRoom = {
rid: '',
isDeleting: false,
subscribedRoom: ''
subscribedRoom: '',
historyLoaders: []
};
export default function (state = initialState, action: TActionsRoom): IRoom {
@ -56,6 +58,16 @@ export default function (state = initialState, action: TActionsRoom): IRoom {
...state,
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:
return state;
}

View File

@ -1,5 +1,5 @@
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 Navigation from '../lib/navigation/appNavigation';
@ -10,6 +10,26 @@ import I18n from '../i18n';
import { showErrorAlert } from '../lib/methods/helpers/info';
import { LISTENER } from '../containers/Toast';
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 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.DELETE, handleDeleteRoom);
yield takeLatest(types.ROOM.FORWARD, handleForwardRoom);
yield watchHistoryRequests();
};
export default root;

View File

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

View File

@ -11,28 +11,28 @@ export default {
title: 'RoomView/LoadMore'
};
const load = () => new Promise(res => setTimeout(res, 1000));
const LoadMore = ({ ...props }) => <LoadMoreComponent type={MessageTypeLoad.MORE} load={load} runOnRender={false} {...props} />;
const LoadMore = ({ ...props }) => (
<LoadMoreComponent rid='rid' t='c' loaderId='loaderId' type={MessageTypeLoad.MORE} runOnRender={false} {...props} />
);
export const Basic = () => (
<>
<LoadMore />
<LoadMore runOnRender />
<LoadMore type={MessageTypeLoad.PREVIOUS_CHUNK} />
<LoadMore type={MessageTypeLoad.NEXT_CHUNK} />
<LoadMore loaderId='1' />
<LoadMore loaderId='2' runOnRender />
<LoadMore loaderId='3' type={MessageTypeLoad.PREVIOUS_CHUNK} />
<LoadMore loaderId='4' type={MessageTypeLoad.NEXT_CHUNK} />
</>
);
const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => (
<ThemeContext.Provider value={{ theme, colors: themes[theme] }}>
<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={longText} theme={theme} isHeader={false} />
<Message msg='Older message' theme={theme} isHeader={false} />
<LoadMore type={MessageTypeLoad.NEXT_CHUNK} />
<LoadMore type={MessageTypeLoad.MORE} />
<LoadMore loaderId='6' type={MessageTypeLoad.NEXT_CHUNK} />
<LoadMore loaderId='7' type={MessageTypeLoad.MORE} />
<Message msg={longText} theme={theme} />
<Message msg='This is the third 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 { useDispatch } from 'react-redux';
import { MessageTypeLoad, themes } from '../../../lib/constants';
import { MessageType } from '../../../definitions';
import { MessageTypeLoad } from '../../../lib/constants';
import { MessageType, RoomType } from '../../../definitions';
import { useTheme } from '../../../theme';
import Touch from '../../../containers/Touch';
import sharedStyles from '../../Styles';
import I18n from '../../../i18n';
import { roomHistoryRequest } from '../../../actions/room';
import { useAppSelector } from '../../../lib/hooks';
const styles = StyleSheet.create({
button: {
@ -20,29 +23,25 @@ const styles = StyleSheet.create({
}
});
const LoadMore = ({
load,
const LoadMore = React.memo(
({
rid,
t,
loaderId,
type,
runOnRender
}: {
load: Function;
}: {
rid: string;
t: RoomType;
loaderId: string;
type: MessageType;
runOnRender: boolean;
}): React.ReactElement => {
const { theme } = useTheme();
const [loading, setLoading] = useState(false);
}): React.ReactElement => {
const { colors } = useTheme();
const dispatch = useDispatch();
const loading = useAppSelector(state => state.room.historyLoaders.some(historyLoader => historyLoader === loaderId));
const handleLoad = useCallback(async () => {
try {
if (loading) {
return;
}
setLoading(true);
await load();
} finally {
setLoading(false);
}
}, [loading]);
const handleLoad = () => dispatch(roomHistoryRequest({ rid, t, loaderId }));
useEffect(() => {
if (runOnRender) {
@ -61,12 +60,13 @@ const LoadMore = ({
return (
<Touch onPress={handleLoad} style={styles.button} enabled={!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>
);
};
}
);
export default LoadMore;

View File

@ -75,7 +75,8 @@ import {
TThreadModel,
ICustomEmojis,
IEmoji,
TGetCustomEmoji
TGetCustomEmoji,
RoomType
} from '../../definitions';
import { E2E_MESSAGE_TYPE, E2E_STATUS, MESSAGE_TYPE_ANY_LOAD, MessageTypeLoad, themes } from '../../lib/constants';
import { TListRef } from './List/List';
@ -685,7 +686,11 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
await loadThreadMessages({ tmid: this.tmid, rid: this.rid });
} else {
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 (joined && 'id' in room) {
@ -1301,16 +1306,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
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 = () => {
const { room } = this.state;
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)) {
content = (
<LoadMore
load={() => this.onLoadMoreMessages(item)}
rid={room.rid}
t={room.t as RoomType}
loaderId={item.id}
type={item.t}
runOnRender={item.t === MessageTypeLoad.MORE && !previousItem}
/>
@ -1502,7 +1499,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
return (
<>
<MessageActions
ref={ref => this.messageActions = ref}
ref={ref => (this.messageActions = ref)}
tmid={this.tmid}
room={room}
user={user}
@ -1512,7 +1509,7 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
onReactionPress={this.onReactionPress}
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
const getMessages = ({
rid,
t,
latest,
lastOpen,
loaderItem
}: {
interface IBaseParams {
rid: string;
t?: string;
latest?: Date;
lastOpen?: Date;
loaderItem?: any; // TODO: type this
}): Promise<void> => {
if (lastOpen) {
return loadMissedMessages({ rid, lastOpen });
}
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({ rid, t: t as any, latest, loaderItem });
return loadMessagesForRoom(params);
};
export default getMessages;

View File

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