diff --git a/app/actions/actionsTypes.ts b/app/actions/actionsTypes.ts index f8852f28c..1367b2251 100644 --- a/app/actions/actionsTypes.ts +++ b/app/actions/actionsTypes.ts @@ -58,7 +58,7 @@ export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPE export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']); export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']); export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS'; -export const SET_ACTIVE_USERS = 'SET_ACTIVE_USERS'; +export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'CLEAR']); export const USERS_TYPING = createRequestTypes('USERS_TYPING', ['ADD', 'REMOVE', 'CLEAR']); export const INVITE_LINKS = createRequestTypes('INVITE_LINKS', [ 'SET_TOKEN', diff --git a/app/actions/activeUsers.ts b/app/actions/activeUsers.ts index 1612e3a8c..55d37d839 100644 --- a/app/actions/activeUsers.ts +++ b/app/actions/activeUsers.ts @@ -1,7 +1,7 @@ import { Action } from 'redux'; import { IActiveUsers } from '../reducers/activeUsers'; -import { SET_ACTIVE_USERS } from './actionsTypes'; +import { ACTIVE_USERS } from './actionsTypes'; interface ISetActiveUsers extends Action { activeUsers: IActiveUsers; @@ -10,6 +10,10 @@ interface ISetActiveUsers extends Action { export type TActionActiveUsers = ISetActiveUsers; export const setActiveUsers = (activeUsers: IActiveUsers): ISetActiveUsers => ({ - type: SET_ACTIVE_USERS, + type: ACTIVE_USERS.SET, activeUsers }); + +export const clearActiveUsers = (): Action => ({ + type: ACTIVE_USERS.CLEAR +}); diff --git a/app/lib/methods/getUsersPresence.js b/app/lib/methods/getUsersPresence.js index 0e65a5f32..933025f03 100644 --- a/app/lib/methods/getUsersPresence.js +++ b/app/lib/methods/getUsersPresence.js @@ -19,7 +19,7 @@ export function subscribeUsersPresence() { this.activeUsersSubTimeout = setTimeout(() => { this.sdk.subscribe('activeUsers'); }, 5000); - } else { + } else if (compareServerVersion(serverVersion, 'lowerThan', '4.1.0')) { this.sdk.subscribe('stream-notify-logged', 'user-status'); } @@ -52,6 +52,11 @@ export default async function getUsersPresence() { try { // RC 1.1.0 const result = await this.sdk.get('users.presence', params); + + if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.1.0')) { + this.sdk.subscribeRaw('stream-user-presence', ['', { added: ids }]); + } + if (result.success) { const { users } = result; @@ -100,13 +105,9 @@ export default async function getUsersPresence() { let usersTimer = null; export function getUserPresence(uid) { - const auth = reduxStore.getState().login.isAuthenticated; - if (!usersTimer) { usersTimer = setTimeout(() => { - if (auth && ids.length) { - getUsersPresence.call(this); - } + getUsersPresence.call(this); usersTimer = null; }, 2000); } diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 1361f1832..f4045c3f6 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -30,7 +30,7 @@ import { compareServerVersion } from './utils'; import reduxStore from './createStore'; import database from './database'; import subscribeRooms from './methods/subscriptions/rooms'; -import getUsersPresence, { getUserPresence, subscribeUsersPresence } from './methods/getUsersPresence'; +import { getUserPresence, subscribeUsersPresence } from './methods/getUsersPresence'; import protectedFunction from './methods/helpers/protectedFunction'; import readMessages from './methods/readMessages'; import getSettings, { getLoginSettings, setSettings, subscribeSettings } from './methods/getSettings'; @@ -308,10 +308,26 @@ const RocketChat = { protectedFunction(ddpMessage => onRolesChanged(ddpMessage)) ); + // RC 4.1 + this.sdk.onStreamData('stream-user-presence', ddpMessage => { + const userStatus = ddpMessage.fields.args[0]; + const { uid } = ddpMessage.fields; + const [, status, statusText] = userStatus; + const newStatus = { status: STATUSES[status], statusText }; + reduxStore.dispatch(setActiveUsers({ [uid]: newStatus })); + + const { user: loggedUser } = reduxStore.getState().login; + if (loggedUser && loggedUser.id === uid) { + reduxStore.dispatch(setUser(newStatus)); + } + }); + this.notifyLoggedListener = this.sdk.onStreamData( 'stream-notify-logged', protectedFunction(async ddpMessage => { const { eventName } = ddpMessage.fields; + + // `user-status` event is deprecated after RC 4.1 in favor of `stream-user-presence/${uid}` if (/user-status/.test(eventName)) { this.activeUsers = this.activeUsers || {}; if (!this._setUserTimer) { @@ -1633,15 +1649,18 @@ const RocketChat = { reduxStore.dispatch(setUser({ status: { status: 'offline' } })); } - if (!this._setUserTimer) { - this._setUserTimer = setTimeout(() => { - const activeUsersBatch = this.activeUsers; - InteractionManager.runAfterInteractions(() => { - reduxStore.dispatch(setActiveUsers(activeUsersBatch)); - }); - this._setUserTimer = null; - return (this.activeUsers = {}); - }, 10000); + const serverVersion = reduxStore.getState().server.version; + if (compareServerVersion(serverVersion, 'lowerThan', '4.1.0')) { + if (!this._setUserTimer) { + this._setUserTimer = setTimeout(() => { + const activeUsersBatch = this.activeUsers; + InteractionManager.runAfterInteractions(() => { + reduxStore.dispatch(setActiveUsers(activeUsersBatch)); + }); + this._setUserTimer = null; + return (this.activeUsers = {}); + }, 10000); + } } if (!ddpMessage.fields) { @@ -1650,7 +1669,6 @@ const RocketChat = { this.activeUsers[ddpMessage.id] = { status: ddpMessage.fields.status }; } }, - getUsersPresence, getUserPresence, subscribeUsersPresence, getDirectory({ query, count, offset, sort }) { diff --git a/app/reducers/activeUsers.test.ts b/app/reducers/activeUsers.test.ts index fbe35207a..b78db6db4 100644 --- a/app/reducers/activeUsers.test.ts +++ b/app/reducers/activeUsers.test.ts @@ -1,4 +1,4 @@ -import { setActiveUsers } from '../actions/activeUsers'; +import { clearActiveUsers, setActiveUsers } from '../actions/activeUsers'; import { IActiveUsers, initialState } from './activeUsers'; import { mockedStore } from './mockedStore'; @@ -13,4 +13,11 @@ describe('test reducer', () => { const state = mockedStore.getState().activeUsers; expect(state).toEqual({ ...activeUsers }); }); + it('should return initial state after dispatching clear', () => { + const previousState = mockedStore.getState().activeUsers; + expect(previousState).not.toBe(initialState); + mockedStore.dispatch(clearActiveUsers()); + const state = mockedStore.getState().activeUsers; + expect(state).toEqual(initialState); + }); }); diff --git a/app/reducers/activeUsers.ts b/app/reducers/activeUsers.ts index 1c32a13fb..bfd765654 100644 --- a/app/reducers/activeUsers.ts +++ b/app/reducers/activeUsers.ts @@ -1,7 +1,7 @@ import { TApplicationActions } from '../definitions'; -import { SET_ACTIVE_USERS } from '../actions/actionsTypes'; +import { ACTIVE_USERS } from '../actions/actionsTypes'; -type TUserStatus = 'online' | 'offline'; +type TUserStatus = 'online' | 'offline' | 'away' | 'busy'; export interface IActiveUser { status: TUserStatus; statusText: string; @@ -15,11 +15,13 @@ export const initialState: IActiveUsers = {}; export default function activeUsers(state = initialState, action: TApplicationActions): IActiveUsers { switch (action.type) { - case SET_ACTIVE_USERS: + case ACTIVE_USERS.SET: return { ...state, ...action.activeUsers }; + case ACTIVE_USERS.CLEAR: + return initialState; default: return state; } diff --git a/app/sagas/login.js b/app/sagas/login.js index 26435ad59..346e3be15 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -15,7 +15,6 @@ import EventEmitter from '../utils/events'; import { inviteLinksRequest } from '../actions/inviteLinks'; import { showErrorAlert } from '../utils/info'; import { localAuthenticate } from '../utils/localAuthentication'; -import { setActiveUsers } from '../actions/activeUsers'; import { encryptionInit, encryptionStop } from '../actions/encryption'; import UserPreferences from '../lib/userPreferences'; import { inquiryRequest, inquiryReset } from '../ee/omnichannel/actions/inquiry'; @@ -100,7 +99,6 @@ const registerPushToken = function* registerPushToken() { }; const fetchUsersPresence = function* fetchUserPresence() { - yield RocketChat.getUsersPresence(); RocketChat.subscribeUsersPresence(); }; @@ -222,11 +220,6 @@ const handleLogout = function* handleLogout({ forcedByServer }) { const handleSetUser = function* handleSetUser({ user }) { setLanguage(user?.language); - if (user && user.status) { - const userId = yield select(state => state.login.user.id); - yield put(setActiveUsers({ [userId]: user })); - } - if (user?.statusLivechat && RocketChat.isOmnichannelModuleAvailable()) { if (isOmnichannelStatusAvailable(user)) { yield put(inquiryRequest()); diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index 26131a31c..c67af42d1 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -10,6 +10,7 @@ import { SERVER } from '../actions/actionsTypes'; import { selectServerFailure, selectServerRequest, selectServerSuccess, serverFailure } from '../actions/server'; import { clearSettings } from '../actions/settings'; import { setUser } from '../actions/login'; +import { clearActiveUsers } from '../actions/activeUsers'; import RocketChat from '../lib/rocketchat'; import database from '../lib/database'; import log, { logServerVersion } from '../utils/log'; @@ -73,6 +74,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch yield put(inquiryReset()); yield put(encryptionStop()); + yield put(clearActiveUsers()); const serversDB = database.servers; yield UserPreferences.setStringAsync(RocketChat.CURRENT_SERVER, server); const userId = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); diff --git a/package.json b/package.json index 118bd814b..1c7c744b7 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@react-navigation/stack": "5.14.5", "@rocket.chat/message-parser": "0.30.0", "@rocket.chat/react-native-fast-image": "^8.2.0", - "@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile", + "@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#subscribe-raw", "@rocket.chat/ui-kit": "0.13.0", "bytebuffer": "^5.0.1", "color2k": "1.2.4", diff --git a/yarn.lock b/yarn.lock index a7d56d191..d9fb01365 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3758,9 +3758,9 @@ resolved "https://registry.yarnpkg.com/@rocket.chat/react-native-fast-image/-/react-native-fast-image-8.2.0.tgz#4f48858f95f40afcb10b39cee9b1239c150d6c51" integrity sha512-NF5KlFt642ZucP/KHnYGBNYLD6O7bcrZMKfRQlH5Y3/1xpnPX1g4wuygtiV7XArMU1FopQT+qmCUPPj8IMDTcw== -"@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#mobile": - version "1.1.0-mobile" - resolved "https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/c64e69ea22514ae3bbe24e36ca77868fdae76157" +"@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#subscribe-raw": + version "1.2.0-mobile" + resolved "https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/6eb97d265d2e2054eced23fc78145ab179e3a1f3" dependencies: js-sha256 "^0.9.0" lru-cache "^4.1.1"