[NEW] Stream to get individual presence updates (#3606)

Co-authored-by: Gerzon Z <gerzonzcanario@gmail.com>
This commit is contained in:
Diego Mello 2022-02-09 17:44:53 -03:00 committed by GitHub
parent 7d23385555
commit ab9d568528
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 62 additions and 35 deletions

View File

@ -58,7 +58,7 @@ export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPE
export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']); export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']);
export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']); export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']);
export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS'; 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 USERS_TYPING = createRequestTypes('USERS_TYPING', ['ADD', 'REMOVE', 'CLEAR']);
export const INVITE_LINKS = createRequestTypes('INVITE_LINKS', [ export const INVITE_LINKS = createRequestTypes('INVITE_LINKS', [
'SET_TOKEN', 'SET_TOKEN',

View File

@ -1,7 +1,7 @@
import { Action } from 'redux'; import { Action } from 'redux';
import { IActiveUsers } from '../reducers/activeUsers'; import { IActiveUsers } from '../reducers/activeUsers';
import { SET_ACTIVE_USERS } from './actionsTypes'; import { ACTIVE_USERS } from './actionsTypes';
interface ISetActiveUsers extends Action { interface ISetActiveUsers extends Action {
activeUsers: IActiveUsers; activeUsers: IActiveUsers;
@ -10,6 +10,10 @@ interface ISetActiveUsers extends Action {
export type TActionActiveUsers = ISetActiveUsers; export type TActionActiveUsers = ISetActiveUsers;
export const setActiveUsers = (activeUsers: IActiveUsers): ISetActiveUsers => ({ export const setActiveUsers = (activeUsers: IActiveUsers): ISetActiveUsers => ({
type: SET_ACTIVE_USERS, type: ACTIVE_USERS.SET,
activeUsers activeUsers
}); });
export const clearActiveUsers = (): Action => ({
type: ACTIVE_USERS.CLEAR
});

View File

@ -19,7 +19,7 @@ export function subscribeUsersPresence() {
this.activeUsersSubTimeout = setTimeout(() => { this.activeUsersSubTimeout = setTimeout(() => {
this.sdk.subscribe('activeUsers'); this.sdk.subscribe('activeUsers');
}, 5000); }, 5000);
} else { } else if (compareServerVersion(serverVersion, 'lowerThan', '4.1.0')) {
this.sdk.subscribe('stream-notify-logged', 'user-status'); this.sdk.subscribe('stream-notify-logged', 'user-status');
} }
@ -52,6 +52,11 @@ export default async function getUsersPresence() {
try { try {
// RC 1.1.0 // RC 1.1.0
const result = await this.sdk.get('users.presence', params); 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) { if (result.success) {
const { users } = result; const { users } = result;
@ -100,13 +105,9 @@ export default async function getUsersPresence() {
let usersTimer = null; let usersTimer = null;
export function getUserPresence(uid) { export function getUserPresence(uid) {
const auth = reduxStore.getState().login.isAuthenticated;
if (!usersTimer) { if (!usersTimer) {
usersTimer = setTimeout(() => { usersTimer = setTimeout(() => {
if (auth && ids.length) {
getUsersPresence.call(this); getUsersPresence.call(this);
}
usersTimer = null; usersTimer = null;
}, 2000); }, 2000);
} }

View File

@ -30,7 +30,7 @@ import { compareServerVersion } from './utils';
import reduxStore from './createStore'; import reduxStore from './createStore';
import database from './database'; import database from './database';
import subscribeRooms from './methods/subscriptions/rooms'; 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 protectedFunction from './methods/helpers/protectedFunction';
import readMessages from './methods/readMessages'; import readMessages from './methods/readMessages';
import getSettings, { getLoginSettings, setSettings, subscribeSettings } from './methods/getSettings'; import getSettings, { getLoginSettings, setSettings, subscribeSettings } from './methods/getSettings';
@ -308,10 +308,26 @@ const RocketChat = {
protectedFunction(ddpMessage => onRolesChanged(ddpMessage)) 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( this.notifyLoggedListener = this.sdk.onStreamData(
'stream-notify-logged', 'stream-notify-logged',
protectedFunction(async ddpMessage => { protectedFunction(async ddpMessage => {
const { eventName } = ddpMessage.fields; 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)) { if (/user-status/.test(eventName)) {
this.activeUsers = this.activeUsers || {}; this.activeUsers = this.activeUsers || {};
if (!this._setUserTimer) { if (!this._setUserTimer) {
@ -1633,6 +1649,8 @@ const RocketChat = {
reduxStore.dispatch(setUser({ status: { status: 'offline' } })); reduxStore.dispatch(setUser({ status: { status: 'offline' } }));
} }
const serverVersion = reduxStore.getState().server.version;
if (compareServerVersion(serverVersion, 'lowerThan', '4.1.0')) {
if (!this._setUserTimer) { if (!this._setUserTimer) {
this._setUserTimer = setTimeout(() => { this._setUserTimer = setTimeout(() => {
const activeUsersBatch = this.activeUsers; const activeUsersBatch = this.activeUsers;
@ -1643,6 +1661,7 @@ const RocketChat = {
return (this.activeUsers = {}); return (this.activeUsers = {});
}, 10000); }, 10000);
} }
}
if (!ddpMessage.fields) { if (!ddpMessage.fields) {
this.activeUsers[ddpMessage.id] = { status: 'offline' }; this.activeUsers[ddpMessage.id] = { status: 'offline' };
@ -1650,7 +1669,6 @@ const RocketChat = {
this.activeUsers[ddpMessage.id] = { status: ddpMessage.fields.status }; this.activeUsers[ddpMessage.id] = { status: ddpMessage.fields.status };
} }
}, },
getUsersPresence,
getUserPresence, getUserPresence,
subscribeUsersPresence, subscribeUsersPresence,
getDirectory({ query, count, offset, sort }) { getDirectory({ query, count, offset, sort }) {

View File

@ -1,4 +1,4 @@
import { setActiveUsers } from '../actions/activeUsers'; import { clearActiveUsers, setActiveUsers } from '../actions/activeUsers';
import { IActiveUsers, initialState } from './activeUsers'; import { IActiveUsers, initialState } from './activeUsers';
import { mockedStore } from './mockedStore'; import { mockedStore } from './mockedStore';
@ -13,4 +13,11 @@ describe('test reducer', () => {
const state = mockedStore.getState().activeUsers; const state = mockedStore.getState().activeUsers;
expect(state).toEqual({ ...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);
});
}); });

View File

@ -1,7 +1,7 @@
import { TApplicationActions } from '../definitions'; 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 { export interface IActiveUser {
status: TUserStatus; status: TUserStatus;
statusText: string; statusText: string;
@ -15,11 +15,13 @@ export const initialState: IActiveUsers = {};
export default function activeUsers(state = initialState, action: TApplicationActions): IActiveUsers { export default function activeUsers(state = initialState, action: TApplicationActions): IActiveUsers {
switch (action.type) { switch (action.type) {
case SET_ACTIVE_USERS: case ACTIVE_USERS.SET:
return { return {
...state, ...state,
...action.activeUsers ...action.activeUsers
}; };
case ACTIVE_USERS.CLEAR:
return initialState;
default: default:
return state; return state;
} }

View File

@ -15,7 +15,6 @@ import EventEmitter from '../utils/events';
import { inviteLinksRequest } from '../actions/inviteLinks'; import { inviteLinksRequest } from '../actions/inviteLinks';
import { showErrorAlert } from '../utils/info'; import { showErrorAlert } from '../utils/info';
import { localAuthenticate } from '../utils/localAuthentication'; import { localAuthenticate } from '../utils/localAuthentication';
import { setActiveUsers } from '../actions/activeUsers';
import { encryptionInit, encryptionStop } from '../actions/encryption'; import { encryptionInit, encryptionStop } from '../actions/encryption';
import UserPreferences from '../lib/userPreferences'; import UserPreferences from '../lib/userPreferences';
import { inquiryRequest, inquiryReset } from '../ee/omnichannel/actions/inquiry'; import { inquiryRequest, inquiryReset } from '../ee/omnichannel/actions/inquiry';
@ -100,7 +99,6 @@ const registerPushToken = function* registerPushToken() {
}; };
const fetchUsersPresence = function* fetchUserPresence() { const fetchUsersPresence = function* fetchUserPresence() {
yield RocketChat.getUsersPresence();
RocketChat.subscribeUsersPresence(); RocketChat.subscribeUsersPresence();
}; };
@ -222,11 +220,6 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
const handleSetUser = function* handleSetUser({ user }) { const handleSetUser = function* handleSetUser({ user }) {
setLanguage(user?.language); 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 (user?.statusLivechat && RocketChat.isOmnichannelModuleAvailable()) {
if (isOmnichannelStatusAvailable(user)) { if (isOmnichannelStatusAvailable(user)) {
yield put(inquiryRequest()); yield put(inquiryRequest());

View File

@ -10,6 +10,7 @@ import { SERVER } from '../actions/actionsTypes';
import { selectServerFailure, selectServerRequest, selectServerSuccess, serverFailure } from '../actions/server'; import { selectServerFailure, selectServerRequest, selectServerSuccess, serverFailure } from '../actions/server';
import { clearSettings } from '../actions/settings'; import { clearSettings } from '../actions/settings';
import { setUser } from '../actions/login'; import { setUser } from '../actions/login';
import { clearActiveUsers } from '../actions/activeUsers';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import database from '../lib/database'; import database from '../lib/database';
import log, { logServerVersion } from '../utils/log'; import log, { logServerVersion } from '../utils/log';
@ -73,6 +74,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
yield put(inquiryReset()); yield put(inquiryReset());
yield put(encryptionStop()); yield put(encryptionStop());
yield put(clearActiveUsers());
const serversDB = database.servers; const serversDB = database.servers;
yield UserPreferences.setStringAsync(RocketChat.CURRENT_SERVER, server); yield UserPreferences.setStringAsync(RocketChat.CURRENT_SERVER, server);
const userId = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); const userId = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`);

View File

@ -49,7 +49,7 @@
"@react-navigation/stack": "5.14.5", "@react-navigation/stack": "5.14.5",
"@rocket.chat/message-parser": "0.30.0", "@rocket.chat/message-parser": "0.30.0",
"@rocket.chat/react-native-fast-image": "^8.2.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", "@rocket.chat/ui-kit": "0.13.0",
"bytebuffer": "^5.0.1", "bytebuffer": "^5.0.1",
"color2k": "1.2.4", "color2k": "1.2.4",

View File

@ -3758,9 +3758,9 @@
resolved "https://registry.yarnpkg.com/@rocket.chat/react-native-fast-image/-/react-native-fast-image-8.2.0.tgz#4f48858f95f40afcb10b39cee9b1239c150d6c51" 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== integrity sha512-NF5KlFt642ZucP/KHnYGBNYLD6O7bcrZMKfRQlH5Y3/1xpnPX1g4wuygtiV7XArMU1FopQT+qmCUPPj8IMDTcw==
"@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#mobile": "@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#subscribe-raw":
version "1.1.0-mobile" version "1.2.0-mobile"
resolved "https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/c64e69ea22514ae3bbe24e36ca77868fdae76157" resolved "https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/6eb97d265d2e2054eced23fc78145ab179e3a1f3"
dependencies: dependencies:
js-sha256 "^0.9.0" js-sha256 "^0.9.0"
lru-cache "^4.1.1" lru-cache "^4.1.1"