From 9bec59b083b5440f8b034f7f42db6c0ecae108cb Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 10 Apr 2026 02:10:21 +0530 Subject: [PATCH 1/9] refresh user presence --- app/sagas/state.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/sagas/state.js b/app/sagas/state.js index 20272cfeb73..e2cd664b296 100644 --- a/app/sagas/state.js +++ b/app/sagas/state.js @@ -1,3 +1,4 @@ +import { Q } from '@nozbe/watermelondb'; import { select, takeLatest } from 'redux-saga/effects'; import log from '../lib/methods/helpers/log'; @@ -7,6 +8,8 @@ import { RootEnum } from '../definitions'; import { checkAndReopen } from '../lib/services/connect'; import { setUserPresenceOnline, setUserPresenceAway } from '../lib/services/restApi'; import { checkPendingNotification } from '../lib/notifications'; +import database from '../lib/database'; +import { getUsersPresence } from '../lib/methods/getUsersPresence'; const isAuthAndConnected = function* isAuthAndConnected() { const login = yield select(state => state.login); @@ -14,6 +17,26 @@ const isAuthAndConnected = function* isAuthAndConnected() { return login.isAuthenticated && meteor.connected; }; +const getDirectMessageUserIds = async () => { + try { + const db = database.active; + const subscriptionsCollection = db.get('subscriptions'); + // Query for open direct message subscriptions that are not archived + const subscriptions = await subscriptionsCollection + .query(Q.where('t', 'd'), Q.where('open', true), Q.where('archived', false)) + .fetch(); + // Extract user IDs from uids field (direct messages store the other user's ID in uids) + const userIds = subscriptions + .map(sub => sub.uids?.[0]) + .filter(Boolean); + // Remove duplicates + return [...new Set(userIds)]; + } catch (e) { + log('[state.js] Error getting DM user IDs:', e); + return []; + } +}; + const appHasComeBackToForeground = function* appHasComeBackToForeground() { const appRoot = yield select(state => state.app.root); if (appRoot !== RootEnum.ROOT_INSIDE) { @@ -31,6 +54,11 @@ const appHasComeBackToForeground = function* appHasComeBackToForeground() { checkPendingNotification().catch(e => { log('[state.js] Error checking pending notification:', e); }); + // Refresh presence for DM users to ensure status is up-to-date after background + const dmUserIds = yield getDirectMessageUserIds(); + if (dmUserIds.length > 0) { + yield getUsersPresence(dmUserIds); + } return yield setUserPresenceOnline(); } catch (e) { log(e); From fff654cc597689eea0c00e9d6bdc98f8b935cc83 Mon Sep 17 00:00:00 2001 From: Rohit3523 Date: Thu, 9 Apr 2026 20:43:13 +0000 Subject: [PATCH 2/9] chore: format code and fix lint issues --- app/sagas/state.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/sagas/state.js b/app/sagas/state.js index e2cd664b296..9b413654d28 100644 --- a/app/sagas/state.js +++ b/app/sagas/state.js @@ -26,9 +26,7 @@ const getDirectMessageUserIds = async () => { .query(Q.where('t', 'd'), Q.where('open', true), Q.where('archived', false)) .fetch(); // Extract user IDs from uids field (direct messages store the other user's ID in uids) - const userIds = subscriptions - .map(sub => sub.uids?.[0]) - .filter(Boolean); + const userIds = subscriptions.map(sub => sub.uids?.[0]).filter(Boolean); // Remove duplicates return [...new Set(userIds)]; } catch (e) { From f1fad5bd4f33663d70308365bd17f32256adb959 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 10 Apr 2026 03:38:48 +0530 Subject: [PATCH 3/9] changes --- app/sagas/state.js | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/app/sagas/state.js b/app/sagas/state.js index e2cd664b296..664d3ffb038 100644 --- a/app/sagas/state.js +++ b/app/sagas/state.js @@ -1,5 +1,5 @@ import { Q } from '@nozbe/watermelondb'; -import { select, takeLatest } from 'redux-saga/effects'; +import { delay, select, takeLatest } from 'redux-saga/effects'; import log from '../lib/methods/helpers/log'; import { localAuthenticate, saveLastLocalAuthenticationSession } from '../lib/methods/helpers/localAuthentication'; @@ -11,6 +11,9 @@ import { checkPendingNotification } from '../lib/notifications'; import database from '../lib/database'; import { getUsersPresence } from '../lib/methods/getUsersPresence'; +const CONNECTION_RETRY_LIMIT = 5; +const CONNECTION_RETRY_DELAY_MS = 500; + const isAuthAndConnected = function* isAuthAndConnected() { const login = yield select(state => state.login); const meteor = yield select(state => state.meteor); @@ -37,29 +40,45 @@ const getDirectMessageUserIds = async () => { } }; +const waitForConnection = function* waitForConnection() { + let retries = 0; + let isReady = yield isAuthAndConnected(); + while (!isReady && retries < CONNECTION_RETRY_LIMIT) { + yield delay(CONNECTION_RETRY_DELAY_MS); + isReady = yield isAuthAndConnected(); + retries++; + } + return isReady; +}; + const appHasComeBackToForeground = function* appHasComeBackToForeground() { const appRoot = yield select(state => state.app.root); if (appRoot !== RootEnum.ROOT_INSIDE) { return; } - const isReady = yield isAuthAndConnected(); - if (!isReady) { - return; - } try { const server = yield select(state => state.server.server); yield localAuthenticate(server); checkAndReopen(); - // Check for pending notification when app comes to foreground (Android - notification tap while in background) - checkPendingNotification().catch(e => { - log('[state.js] Error checking pending notification:', e); - }); + + const isReady = yield waitForConnection(); + if (!isReady) { + log('[state.js] Connection not ready after retries, aborting foreground tasks'); + return; + } + // Refresh presence for DM users to ensure status is up-to-date after background + yield setUserPresenceOnline(); + const dmUserIds = yield getDirectMessageUserIds(); if (dmUserIds.length > 0) { yield getUsersPresence(dmUserIds); } - return yield setUserPresenceOnline(); + + // Check for pending notification when app comes to foreground (Android - notification tap while in background) + checkPendingNotification().catch(e => { + log('[state.js] Error checking pending notification:', e); + }); } catch (e) { log(e); } @@ -88,4 +107,4 @@ const root = function* root() { yield takeLatest(APP_STATE.BACKGROUND, appHasComeBackToBackground); }; -export default root; +export default root; \ No newline at end of file From 0169c4ad02a0e0a8cfb2ea0f2bb4c8eb29f4fae4 Mon Sep 17 00:00:00 2001 From: Rohit3523 Date: Thu, 9 Apr 2026 22:10:32 +0000 Subject: [PATCH 4/9] chore: format code and fix lint issues --- app/sagas/state.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/sagas/state.js b/app/sagas/state.js index 8c767806292..4d1bddb5f47 100644 --- a/app/sagas/state.js +++ b/app/sagas/state.js @@ -105,4 +105,4 @@ const root = function* root() { yield takeLatest(APP_STATE.BACKGROUND, appHasComeBackToBackground); }; -export default root; \ No newline at end of file +export default root; From 9bcee5facd6e8796d543fbf6f85c84d58f09cb90 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 10 Apr 2026 04:54:40 +0530 Subject: [PATCH 5/9] more changes --- app/containers/Status/index.tsx | 5 +-- app/lib/methods/getUsersPresence.ts | 30 ++++++++++++++-- app/sagas/login.js | 11 +++++- app/sagas/state.js | 55 +++++++++++------------------ 4 files changed, 62 insertions(+), 39 deletions(-) diff --git a/app/containers/Status/index.tsx b/app/containers/Status/index.tsx index e131f20a943..39d14020bf5 100644 --- a/app/containers/Status/index.tsx +++ b/app/containers/Status/index.tsx @@ -21,10 +21,11 @@ const StatusContainer = ({ id, style, status, size = 32, ...props }: IStatus): R }); useEffect(() => { - if (connected && statusState === 'loading' && !status) { + if (connected && !status) { + // Always fetch fresh presence when connected (especially after reconnection) getUserPresence(id); } - }, [connected, statusState]); + }, [connected, id, status]); return ; }; diff --git a/app/lib/methods/getUsersPresence.ts b/app/lib/methods/getUsersPresence.ts index 798fe37ebec..e9df0e58ba4 100644 --- a/app/lib/methods/getUsersPresence.ts +++ b/app/lib/methods/getUsersPresence.ts @@ -1,5 +1,6 @@ import { InteractionManager } from 'react-native'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; +import { Q } from '@nozbe/watermelondb'; import { type IActiveUsers } from '../../reducers/activeUsers'; import { store as reduxStore } from '../store/auxStore'; @@ -55,8 +56,8 @@ export async function getUsersPresence(usersParams: string[]) { if (!usersParams.length) { return; } - // Request userPresence on demand - params = { ids: usersParams.join(',') }; + // Request userPresence on demand with cache-busting timestamp + params = { ids: usersParams.join(','), _t: Date.now() }; } try { @@ -140,3 +141,28 @@ export const setPresenceCap = async (enabled: boolean) => { reduxStore.dispatch(setNotificationPresenceCap(false)); } }; + +export const getDirectMessageUserIds = async (): Promise => { + try { + const db = database.active; + const subscriptionsCollection = db.get('subscriptions'); + const subscriptions = await subscriptionsCollection + .query(Q.where('t', 'd'), Q.where('open', true), Q.where('archived', false)) + .fetch(); + const userIds = subscriptions.map((sub: any) => sub.uids?.[0]).filter(Boolean); + return [...new Set(userIds)]; + } catch (e) { + return []; + } +}; + +export const refreshDmUsersPresence = async (): Promise => { + try { + const dmUserIds = await getDirectMessageUserIds(); + if (dmUserIds.length > 0) { + await getUsersPresence(dmUserIds); + } + } catch (e) { + // Silently fail + } +}; diff --git a/app/sagas/login.js b/app/sagas/login.js index e88740ed6ff..d5ab793575c 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -28,7 +28,7 @@ import { getEnterpriseModules, isOmnichannelModuleAvailable } from '../lib/metho import { getPermissions } from '../lib/methods/getPermissions'; import { getRoles } from '../lib/methods/getRoles'; import { getSlashCommands } from '../lib/methods/getSlashCommands'; -import { getUserPresence, subscribeUsersPresence } from '../lib/methods/getUsersPresence'; +import { getUserPresence, subscribeUsersPresence, refreshDmUsersPresence } from '../lib/methods/getUsersPresence'; import { logout, removeServerData, removeServerDatabase } from '../lib/methods/logout'; import { subscribeSettings } from '../lib/methods/getSettings'; import { connect, loginWithPassword, login } from '../lib/services/connect'; @@ -200,6 +200,14 @@ const fetchUsersPresenceFork = function* fetchUsersPresenceFork() { } }; +const fetchDmUsersPresenceFork = function* fetchDmUsersPresenceFork() { + try { + yield call(refreshDmUsersPresence); + } catch (e) { + log('[login.js] Error fetching DM users presence:', e); + } +}; + const fetchEnterpriseModulesFork = function* fetchEnterpriseModulesFork({ user }) { try { yield getEnterpriseModules(); @@ -253,6 +261,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) { yield fork(fetchSlashCommandsFork); yield fork(registerPushTokenFork); yield fork(fetchUsersPresenceFork); + yield fork(fetchDmUsersPresenceFork); yield fork(fetchEnterpriseModulesFork, { user }); yield fork(subscribeSettingsFork); yield fork(fetchUsersRoles); diff --git a/app/sagas/state.js b/app/sagas/state.js index 8c767806292..655238108e9 100644 --- a/app/sagas/state.js +++ b/app/sagas/state.js @@ -1,18 +1,16 @@ -import { Q } from '@nozbe/watermelondb'; -import { delay, select, takeLatest } from 'redux-saga/effects'; +import { call, delay, select, takeLatest } from 'redux-saga/effects'; import log from '../lib/methods/helpers/log'; import { localAuthenticate, saveLastLocalAuthenticationSession } from '../lib/methods/helpers/localAuthentication'; -import { APP_STATE } from '../actions/actionsTypes'; +import { APP_STATE, METEOR } from '../actions/actionsTypes'; import { RootEnum } from '../definitions'; import { checkAndReopen } from '../lib/services/connect'; import { setUserPresenceOnline, setUserPresenceAway } from '../lib/services/restApi'; import { checkPendingNotification } from '../lib/notifications'; -import database from '../lib/database'; -import { getUsersPresence } from '../lib/methods/getUsersPresence'; +import { refreshDmUsersPresence, subscribeUsersPresence } from '../lib/methods/getUsersPresence'; -const CONNECTION_RETRY_LIMIT = 5; -const CONNECTION_RETRY_DELAY_MS = 500; +const CONNECTION_RETRY_LIMIT = 10; +const CONNECTION_RETRY_DELAY_MS = 1000; const isAuthAndConnected = function* isAuthAndConnected() { const login = yield select(state => state.login); @@ -20,24 +18,6 @@ const isAuthAndConnected = function* isAuthAndConnected() { return login.isAuthenticated && meteor.connected; }; -const getDirectMessageUserIds = async () => { - try { - const db = database.active; - const subscriptionsCollection = db.get('subscriptions'); - // Query for open direct message subscriptions that are not archived - const subscriptions = await subscriptionsCollection - .query(Q.where('t', 'd'), Q.where('open', true), Q.where('archived', false)) - .fetch(); - // Extract user IDs from uids field (direct messages store the other user's ID in uids) - const userIds = subscriptions.map(sub => sub.uids?.[0]).filter(Boolean); - // Remove duplicates - return [...new Set(userIds)]; - } catch (e) { - log('[state.js] Error getting DM user IDs:', e); - return []; - } -}; - const waitForConnection = function* waitForConnection() { let retries = 0; let isReady = yield isAuthAndConnected(); @@ -50,10 +30,6 @@ const waitForConnection = function* waitForConnection() { }; const appHasComeBackToForeground = function* appHasComeBackToForeground() { - const appRoot = yield select(state => state.app.root); - if (appRoot !== RootEnum.ROOT_INSIDE) { - return; - } try { const server = yield select(state => state.server.server); yield localAuthenticate(server); @@ -67,11 +43,7 @@ const appHasComeBackToForeground = function* appHasComeBackToForeground() { // Refresh presence for DM users to ensure status is up-to-date after background yield setUserPresenceOnline(); - - const dmUserIds = yield getDirectMessageUserIds(); - if (dmUserIds.length > 0) { - yield getUsersPresence(dmUserIds); - } + yield call(refreshDmUsersPresence); // Check for pending notification when app comes to foreground (Android - notification tap while in background) checkPendingNotification().catch(e => { @@ -100,9 +72,24 @@ const appHasComeBackToBackground = function* appHasComeBackToBackground() { } }; +const handleMeteorConnect = function* handleMeteorConnect() { + const appRoot = yield select(state => state.app.root); + if (appRoot !== RootEnum.ROOT_INSIDE) { + return; + } + try { + // Re-subscribe to presence stream and fetch current presence + subscribeUsersPresence(); + yield call(refreshDmUsersPresence); + } catch (e) { + log('[state.js] Error refreshing DM users presence on connect:', e); + } +}; + const root = function* root() { yield takeLatest(APP_STATE.FOREGROUND, appHasComeBackToForeground); yield takeLatest(APP_STATE.BACKGROUND, appHasComeBackToBackground); + yield takeLatest(METEOR.SUCCESS, handleMeteorConnect); }; export default root; \ No newline at end of file From 29ac6da0a79a505c7399560cb1c7c694762fc686 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 10 Apr 2026 05:01:41 +0530 Subject: [PATCH 6/9] revert --- app/containers/Status/index.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/containers/Status/index.tsx b/app/containers/Status/index.tsx index 39d14020bf5..e131f20a943 100644 --- a/app/containers/Status/index.tsx +++ b/app/containers/Status/index.tsx @@ -21,11 +21,10 @@ const StatusContainer = ({ id, style, status, size = 32, ...props }: IStatus): R }); useEffect(() => { - if (connected && !status) { - // Always fetch fresh presence when connected (especially after reconnection) + if (connected && statusState === 'loading' && !status) { getUserPresence(id); } - }, [connected, id, status]); + }, [connected, statusState]); return ; }; From 3b0f1a14a7d57b9e30385d02be394b38b4a10ecd Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 10 Apr 2026 05:04:31 +0530 Subject: [PATCH 7/9] coderabbit fix --- app/lib/methods/getUsersPresence.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/lib/methods/getUsersPresence.ts b/app/lib/methods/getUsersPresence.ts index e9df0e58ba4..6b13aa6e828 100644 --- a/app/lib/methods/getUsersPresence.ts +++ b/app/lib/methods/getUsersPresence.ts @@ -145,11 +145,14 @@ export const setPresenceCap = async (enabled: boolean) => { export const getDirectMessageUserIds = async (): Promise => { try { const db = database.active; + const loggedUserId = reduxStore.getState().login.user?.id; const subscriptionsCollection = db.get('subscriptions'); const subscriptions = await subscriptionsCollection .query(Q.where('t', 'd'), Q.where('open', true), Q.where('archived', false)) .fetch(); - const userIds = subscriptions.map((sub: any) => sub.uids?.[0]).filter(Boolean); + const userIds = subscriptions + .map((sub: { uids?: string[] }) => sub.uids?.find(uid => uid && uid !== loggedUserId)) + .filter((uid): uid is string => Boolean(uid)); return [...new Set(userIds)]; } catch (e) { return []; From baed9edd7dad8105921d8c7393e8cbb5022b63f6 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 10 Apr 2026 05:05:24 +0530 Subject: [PATCH 8/9] coderabbit fix --- app/lib/methods/getUsersPresence.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/lib/methods/getUsersPresence.ts b/app/lib/methods/getUsersPresence.ts index 6b13aa6e828..44988ad7f91 100644 --- a/app/lib/methods/getUsersPresence.ts +++ b/app/lib/methods/getUsersPresence.ts @@ -10,6 +10,7 @@ import database from '../database'; import { type IUser } from '../../definitions'; import sdk from '../services/sdk'; import { compareServerVersion } from './helpers'; +import log from './helpers/log'; import userPreferences from './userPreferences'; import { NOTIFICATION_PRESENCE_CAP } from '../constants/notifications'; import { setNotificationPresenceCap } from '../../actions/app'; @@ -166,6 +167,6 @@ export const refreshDmUsersPresence = async (): Promise => { await getUsersPresence(dmUserIds); } } catch (e) { - // Silently fail + log(e); } }; From 00b4aa0f060803ae50efa057b4ad18fb5f291104 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 13 Apr 2026 17:17:03 +0530 Subject: [PATCH 9/9] changes --- app/lib/methods/getUsersPresence.ts | 4 ++-- app/sagas/state.js | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/lib/methods/getUsersPresence.ts b/app/lib/methods/getUsersPresence.ts index 44988ad7f91..063defadb7e 100644 --- a/app/lib/methods/getUsersPresence.ts +++ b/app/lib/methods/getUsersPresence.ts @@ -57,8 +57,8 @@ export async function getUsersPresence(usersParams: string[]) { if (!usersParams.length) { return; } - // Request userPresence on demand with cache-busting timestamp - params = { ids: usersParams.join(','), _t: Date.now() }; + // Request userPresence on demand + params = { ids: usersParams.join(',') }; } try { diff --git a/app/sagas/state.js b/app/sagas/state.js index 04b2d833828..edf9dc8e50d 100644 --- a/app/sagas/state.js +++ b/app/sagas/state.js @@ -7,7 +7,7 @@ import { RootEnum } from '../definitions'; import { checkAndReopen } from '../lib/services/connect'; import { setUserPresenceOnline, setUserPresenceAway } from '../lib/services/restApi'; import { checkPendingNotification } from '../lib/notifications'; -import { refreshDmUsersPresence, subscribeUsersPresence } from '../lib/methods/getUsersPresence'; +import { refreshDmUsersPresence } from '../lib/methods/getUsersPresence'; const CONNECTION_RETRY_LIMIT = 10; const CONNECTION_RETRY_DELAY_MS = 1000; @@ -78,8 +78,6 @@ const handleMeteorConnect = function* handleMeteorConnect() { return; } try { - // Re-subscribe to presence stream and fetch current presence - subscribeUsersPresence(); yield call(refreshDmUsersPresence); } catch (e) { log('[state.js] Error refreshing DM users presence on connect:', e);