Skip to content
30 changes: 30 additions & 0 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 All @@ -9,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';
Expand Down Expand Up @@ -140,3 +142,31 @@ export const setPresenceCap = async (enabled: boolean) => {
reduxStore.dispatch(setNotificationPresenceCap(false));
}
};

export const getDirectMessageUserIds = async (): Promise<string[]> => {
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: { uids?: string[] }) => sub.uids?.find(uid => uid && uid !== loggedUserId))
.filter((uid): uid is string => Boolean(uid));
return [...new Set(userIds)];
} catch (e) {
return [];
}
};

export const refreshDmUsersPresence = async (): Promise<void> => {
try {
const dmUserIds = await getDirectMessageUserIds();
if (dmUserIds.length > 0) {
await getUsersPresence(dmUserIds);
}
} catch (e) {
log(e);
}
};
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
52 changes: 41 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 } 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,22 @@ const appHasComeBackToBackground = function* appHasComeBackToBackground() {
}
};

const handleMeteorConnect = function* handleMeteorConnect() {
const appRoot = yield select(state => state.app.root);
if (appRoot !== RootEnum.ROOT_INSIDE) {
return;
}
try {
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