From 7f7bfd7b721afba4f8904a39d9de459a42aa15af Mon Sep 17 00:00:00 2001 From: Anurag Shivarathri Date: Fri, 9 Dec 2022 23:47:34 +0530 Subject: [PATCH] [MM-48926] Fetch new thread posts on websocket reconnect (#6844) --- app/actions/local/thread.ts | 10 +++- app/actions/remote/entry/common.ts | 14 ++--- app/actions/websocket/index.ts | 53 ++++++++++++++++++- app/queries/servers/post.ts | 25 +++++++-- .../global_threads/threads_list/index.ts | 2 +- app/screens/navigation.ts | 11 ++-- 6 files changed, 97 insertions(+), 18 deletions(-) diff --git a/app/actions/local/thread.ts b/app/actions/local/thread.ts index 413341d9a..ed003bcee 100644 --- a/app/actions/local/thread.ts +++ b/app/actions/local/thread.ts @@ -79,11 +79,19 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo const teamId = channel.teamId || currentTeamId; let switchingTeams = false; - if (currentTeamId !== teamId) { + if (currentTeamId === teamId) { + const models = await prepareCommonSystemValues(operator, { + currentChannelId: channel.id, + }); + if (models.length) { + await operator.batchRecords(models); + } + } else { const modelPromises: Array> = []; switchingTeams = true; modelPromises.push(addTeamToTeamHistory(operator, teamId, true)); const commonValues: PrepareCommonSystemValuesArgs = { + currentChannelId: channel.id, currentTeamId: teamId, }; modelPromises.push(prepareCommonSystemValues(operator, commonValues)); diff --git a/app/actions/remote/entry/common.ts b/app/actions/remote/entry/common.ts index ae4d1357b..a6dc2ff58 100644 --- a/app/actions/remote/entry/common.ts +++ b/app/actions/remote/entry/common.ts @@ -534,6 +534,10 @@ export async function handleEntryAfterLoadNavigation( const currentTeamIdAfterLoad = await getCurrentTeamId(database); const currentChannelIdAfterLoad = await getCurrentChannelId(database); + const mountedScreens = NavigationStore.getScreensInStack(); + const isChannelScreenMounted = mountedScreens.includes(Screens.CHANNEL); + const isThreadsMounted = mountedScreens.includes(Screens.THREAD); + const tabletDevice = await isTablet(); if (currentTeamIdAfterLoad !== currentTeamId) { // Switched teams while loading @@ -545,18 +549,14 @@ export async function handleEntryAfterLoadNavigation( } else if (currentChannelIdAfterLoad !== currentChannelId) { // Switched channels while loading if (!channelMembers.find((m) => m.channel_id === currentChannelIdAfterLoad)) { - const tabletDevice = await isTablet(); - const navComponents = NavigationStore.getScreensInStack(); - if (tabletDevice || navComponents.includes(Screens.CHANNEL) || navComponents.includes(Screens.THREAD)) { + if (tabletDevice || isChannelScreenMounted || isThreadsMounted) { await handleKickFromChannel(serverUrl, currentChannelIdAfterLoad); } else { await setCurrentTeamAndChannelId(operator, initialTeamId, initialChannelId); } } - } else if (currentChannelIdAfterLoad !== initialChannelId) { - const tabletDevice = await isTablet(); - const navComponents = NavigationStore.getScreensInStack(); - if (tabletDevice || navComponents.includes(Screens.CHANNEL) || navComponents.includes(Screens.THREAD)) { + } else if (currentChannelIdAfterLoad && currentChannelIdAfterLoad !== initialChannelId) { + if (tabletDevice || isChannelScreenMounted || isThreadsMounted) { await handleKickFromChannel(serverUrl, currentChannelIdAfterLoad); } else { await setCurrentTeamAndChannelId(operator, initialTeamId, initialChannelId); diff --git a/app/actions/websocket/index.ts b/app/actions/websocket/index.ts index 652a2c0ee..23e6f0d74 100644 --- a/app/actions/websocket/index.ts +++ b/app/actions/websocket/index.ts @@ -3,8 +3,11 @@ import {DeviceEventEmitter} from 'react-native'; +import {markChannelAsViewed} from '@actions/local/channel'; +import {markChannelAsRead} from '@actions/remote/channel'; import {handleEntryAfterLoadNavigation} from '@actions/remote/entry/common'; import {deferredAppEntryActions, entry} from '@actions/remote/entry/gql_common'; +import {fetchPostsForChannel, fetchPostThread} from '@actions/remote/post'; import {fetchStatusByIds} from '@actions/remote/user'; import {loadConfigAndCalls} from '@calls/actions/calls'; import { @@ -27,22 +30,28 @@ import { handleCallUserVoiceOn, } from '@calls/connection/websocket_event_handlers'; import {isSupportedServerCalls} from '@calls/utils'; -import {Events, WebsocketEvents} from '@constants'; +import {Events, Screens, WebsocketEvents} from '@constants'; import {SYSTEM_IDENTIFIERS} from '@constants/database'; import DatabaseManager from '@database/manager'; import AppsManager from '@managers/apps_manager'; import {getActiveServerUrl} from '@queries/app/servers'; import {getCurrentChannel} from '@queries/servers/channel'; +import {getLastPostInThread} from '@queries/servers/post'; import { getConfig, + getCurrentChannelId, getCurrentUserId, getLicense, getWebSocketLastDisconnected, resetWebSocketLastDisconnected, } from '@queries/servers/system'; import {getCurrentTeam} from '@queries/servers/team'; +import {getIsCRTEnabled} from '@queries/servers/thread'; import {getCurrentUser} from '@queries/servers/user'; -import {logInfo} from '@utils/log'; +import EphemeralStore from '@store/ephemeral_store'; +import NavigationStore from '@store/navigation_store'; +import {isTablet} from '@utils/helpers'; +import {logDebug, logInfo} from '@utils/log'; import {handleCategoryCreatedEvent, handleCategoryDeletedEvent, handleCategoryOrderUpdatedEvent, handleCategoryUpdatedEvent} from './category'; import {handleChannelConvertedEvent, handleChannelCreatedEvent, @@ -151,6 +160,8 @@ async function doReconnect(serverUrl: string) { await operator.batchRecords(models); logInfo('WEBSOCKET RECONNECT MODELS BATCHING TOOK', `${Date.now() - dt}ms`); + await fetchPostDataIfNeeded(serverUrl); + const {id: currentUserId, locale: currentUserLocale} = (await getCurrentUser(database))!; const license = await getLicense(database); const config = await getConfig(database); @@ -398,3 +409,41 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) { break; } } + +async function fetchPostDataIfNeeded(serverUrl: string) { + try { + const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const currentChannelId = await getCurrentChannelId(database); + const isCRTEnabled = await getIsCRTEnabled(database); + const mountedScreens = NavigationStore.getScreensInStack(); + const isChannelScreenMounted = mountedScreens.includes(Screens.CHANNEL); + const isThreadScreenMounted = mountedScreens.includes(Screens.THREAD); + const tabletDevice = await isTablet(); + + if (isCRTEnabled && isThreadScreenMounted) { + // Fetch new posts in the thread only when CRT is enabled, + // for non-CRT fetchPostsForChannel includes posts in the thread + const rootId = EphemeralStore.getCurrentThreadId(); + if (rootId) { + const lastPost = await getLastPostInThread(database, rootId); + if (lastPost) { + if (lastPost) { + const options: FetchPaginatedThreadOptions = {}; + options.fromCreateAt = lastPost.createAt; + options.fromPost = lastPost.id; + options.direction = 'down'; + await fetchPostThread(serverUrl, rootId, options); + } + } + } + } + + if (currentChannelId && (isChannelScreenMounted || tabletDevice)) { + await fetchPostsForChannel(serverUrl, currentChannelId); + markChannelAsRead(serverUrl, currentChannelId); + markChannelAsViewed(serverUrl, currentChannelId); + } + } catch (error) { + logDebug('could not fetch needed post after WS reconnect', error); + } +} diff --git a/app/queries/servers/post.ts b/app/queries/servers/post.ts index c5a39cc8c..aae4ed41e 100644 --- a/app/queries/servers/post.ts +++ b/app/queries/servers/post.ts @@ -126,7 +126,20 @@ export const getRecentPostsInThread = async (database: Database, rootId: string) return []; }; -export const queryPostsChunk = (database: Database, id: string, earliest: number, latest: number, inThread = false, includeDeleted = false) => { +export const getLastPostInThread = async (database: Database, rootId: string) => { + const chunks = await queryPostsInThread(database, rootId, true, true).fetch(); + if (chunks.length) { + const recent = chunks[0]; + const post = await getPostById(database, rootId); + if (post) { + const posts = await queryPostsChunk(database, rootId, recent.earliest, recent.latest, true, true, 1).fetch(); + return posts[0]; + } + } + return undefined; +}; + +export const queryPostsChunk = (database: Database, id: string, earliest: number, latest: number, inThread = false, includeDeleted = false, limit = 0) => { const conditions: Q.Condition[] = [Q.where('create_at', Q.between(earliest, latest))]; if (inThread) { conditions.push(Q.where('root_id', id)); @@ -138,12 +151,18 @@ export const queryPostsChunk = (database: Database, id: string, earliest: number conditions.push(Q.where('delete_at', Q.eq(0))); } - return database.get(POST).query( + const clauses: Q.Clause[] = [ Q.and( ...conditions, ), Q.sortBy('create_at', Q.desc), - ); + ]; + + if (limit) { + clauses.push(Q.take(limit)); + } + + return database.get(POST).query(...clauses); }; export const getRecentPostsInChannel = async (database: Database, channelId: string, includeDeleted = false) => { diff --git a/app/screens/global_threads/threads_list/index.ts b/app/screens/global_threads/threads_list/index.ts index a0f864292..82df7f229 100644 --- a/app/screens/global_threads/threads_list/index.ts +++ b/app/screens/global_threads/threads_list/index.ts @@ -34,7 +34,7 @@ const enhanced = withObservables(['tab', 'teamId', 'forceQueryAfterAppState'], ( teammateNameDisplay: observeTeammateNameDisplay(database), threads: teamThreadsSyncObserver.pipe( switchMap((teamThreadsSync) => { - const earliest = teamThreadsSync?.[0]?.earliest; + const earliest = tab === 'all' ? teamThreadsSync?.[0]?.earliest : 0; return queryThreadsInTeam(database, teamId, getOnlyUnreads, false, true, true, earliest).observe(); }), ), diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index c5ba53c19..2e9bf30df 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -30,11 +30,11 @@ const alpha = { }; export function registerNavigationListeners() { - Navigation.events().registerScreenPoppedListener(screenPoppedListener); - Navigation.events().registerCommandListener(registerCommandListener); + Navigation.events().registerScreenPoppedListener(onPoppedListener); + Navigation.events().registerCommandListener(onCommandListener); } -function registerCommandListener(name: string, params: any) { +function onCommandListener(name: string, params: any) { switch (name) { case 'setRoot': NavigationStore.clearScreensFromStack(); @@ -63,9 +63,12 @@ function registerCommandListener(name: string, params: any) { } } -function screenPoppedListener({componentId}: ScreenPoppedEvent) { +function onPoppedListener({componentId}: ScreenPoppedEvent) { // screen pop does not trigger registerCommandListener, but does trigger screenPoppedListener NavigationStore.removeScreenFromStack(componentId); + if (NavigationStore.getVisibleScreen() === Screens.HOME) { + DeviceEventEmitter.emit(Events.TAB_BAR_VISIBLE, true); + } } export const loginAnimationOptions = () => {