Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9bec59b
refresh user presence
Rohit3523 Apr 9, 2026
fff654c
chore: format code and fix lint issues
Rohit3523 Apr 9, 2026
f1fad5b
changes
Rohit3523 Apr 9, 2026
d27b333
Merge branch 'presence-fix' of https://github.com/RocketChat/Rocket.C…
Rohit3523 Apr 9, 2026
0169c4a
chore: format code and fix lint issues
Rohit3523 Apr 9, 2026
9bcee5f
more changes
Rohit3523 Apr 9, 2026
fc95fa4
Merge branch 'presence-fix' of https://github.com/RocketChat/Rocket.C…
Rohit3523 Apr 9, 2026
29ac6da
revert
Rohit3523 Apr 9, 2026
3b0f1a1
coderabbit fix
Rohit3523 Apr 9, 2026
baed9ed
coderabbit fix
Rohit3523 Apr 9, 2026
00b4aa0
changes
Rohit3523 Apr 13, 2026
bfa8d92
Merge branch 'develop' into presence-fix
Rohit3523 Apr 13, 2026
9919a96
Added app root guard back
Rohit3523 Apr 16, 2026
9ff3b85
refresh user presence on room request
Rohit3523 Apr 16, 2026
7fafb7f
more changes...
Rohit3523 Apr 16, 2026
3da17c2
revert change
Rohit3523 Apr 16, 2026
a087432
added meteor success back...
Rohit3523 Apr 16, 2026
5b033b0
remove from room
Rohit3523 Apr 16, 2026
0eb77b2
correct
Rohit3523 Apr 16, 2026
b9b9acf
fix
Rohit3523 Apr 16, 2026
db6e29b
remove unwanted things
Rohit3523 Apr 16, 2026
dc3f970
move to login
Rohit3523 Apr 16, 2026
296e3e9
remove from state
Rohit3523 Apr 16, 2026
2b31108
remove line
Rohit3523 Apr 16, 2026
f208843
Merge branch 'develop' into presence-fix
Rohit3523 Apr 16, 2026
27457c6
rename var
Rohit3523 Apr 16, 2026
1e19282
Merge branch 'presence-fix' of https://github.com/RocketChat/Rocket.C…
Rohit3523 Apr 16, 2026
a1d7cf4
check Presence_broadcast_disabled...
Rohit3523 Apr 22, 2026
bd5dfcb
Merge branch 'develop' into presence-fix
Rohit3523 Apr 22, 2026
fcb7b76
Improve
Rohit3523 Apr 22, 2026
f15b5c2
Merge branch 'presence-fix' of https://github.com/RocketChat/Rocket.C…
Rohit3523 Apr 22, 2026
5c43d2d
using or
Rohit3523 Apr 23, 2026
c52ef4a
Merge branch 'develop' into presence-fix
Rohit3523 Apr 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/containers/Status/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

return <Status size={size} style={style} status={status ?? statusState} {...props} />;
};
Expand Down
30 changes: 28 additions & 2 deletions app/lib/methods/getUsersPresence.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -140,3 +141,28 @@ export const setPresenceCap = async (enabled: boolean) => {
reduxStore.dispatch(setNotificationPresenceCap(false));
}
};

export const getDirectMessageUserIds = async (): Promise<string[]> => {
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)];
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} catch (e) {
return [];
}
};

export const refreshDmUsersPresence = async (): Promise<void> => {
try {
const dmUserIds = await getDirectMessageUserIds();
if (dmUserIds.length > 0) {
await getUsersPresence(dmUserIds);
}
} catch (e) {
// Silently fail
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};
11 changes: 10 additions & 1 deletion app/sagas/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down
54 changes: 43 additions & 11 deletions app/sagas/state.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,54 @@
import { 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 { refreshDmUsersPresence, subscribeUsersPresence } from '../lib/methods/getUsersPresence';

const CONNECTION_RETRY_LIMIT = 10;
const CONNECTION_RETRY_DELAY_MS = 1000;

const isAuthAndConnected = function* isAuthAndConnected() {
const login = yield select(state => state.login);
const meteor = yield select(state => state.meteor);
return login.isAuthenticated && meteor.connected;
};

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;
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() {
try {
const server = yield select(state => state.server.server);
yield localAuthenticate(server);
checkAndReopen();

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();
yield call(refreshDmUsersPresence);

// 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);
});
return yield setUserPresenceOnline();
} catch (e) {
log(e);
}
Expand All @@ -55,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;
Loading