Regression: Fix presence status (#19474)

Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
pull/19508/head^2
Tasso Evangelista 5 years ago committed by GitHub
parent 0f09b1ba14
commit 4b68fb9cbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/ui-sidenav/client/userPresence.js
  2. 17
      client/components/basic/UserStatus.js
  3. 80
      client/lib/presence.js
  4. 133
      client/lib/presence.ts
  5. 2
      definition/IUser.ts
  6. 8
      imports/startup/client/listenActiveUsers.js
  7. 36
      package-lock.json

@ -86,7 +86,7 @@ Tracker.autorun(() => {
mem.clear(get);
wasConnected = isConnected;
Presence.emit('restart');
Presence.restart();
if (featureExists) {
for (const node of data.keys()) {

@ -4,18 +4,18 @@ import { StatusBullet } from '@rocket.chat/fuselage';
import { useTranslation } from '../../contexts/TranslationContext';
import { Presence } from '../../lib/presence';
export const UserStatus = React.memo(({ small, ...props }) => {
export const UserStatus = React.memo(({ small, status, ...props }) => {
const size = small ? 'small' : 'large';
const t = useTranslation();
switch (props.status) {
switch (status) {
case 'online':
return <StatusBullet size={size} title={t('Online')} {...props}/>;
return <StatusBullet size={size} status={status} title={t('Online')} {...props}/>;
case 'busy':
return <StatusBullet size={size} title={t('Busy')} {...props}/>;
return <StatusBullet size={size} status={status} title={t('Busy')} {...props}/>;
case 'away':
return <StatusBullet size={size} title={t('Away')} {...props}/>;
case 'Offline':
return <StatusBullet size={size} title={t('Offline')} {...props}/>;
return <StatusBullet size={size} status={status} title={t('Away')} {...props}/>;
case 'offline':
return <StatusBullet size={size} status={status} title={t('Offline')} {...props}/>;
default:
return <StatusBullet size={size} title={t('Loading')} {...props}/>;
}
@ -26,7 +26,6 @@ export const Away = (props) => <UserStatus status='away' {...props}/>;
export const Online = (props) => <UserStatus status='online' {...props}/>;
export const Offline = (props) => <UserStatus status='offline' {...props}/>;
export const Loading = (props) => <UserStatus {...props}/>;
export const colors = {
busy: 'danger-500',
away: 'warning-600',
@ -37,7 +36,7 @@ export const colors = {
export const usePresence = (uid, presence) => {
const [status, setStatus] = useState(presence);
useEffect(() => {
const handle = ({ status = 'offline' }) => {
const handle = ({ status = 'loading' }) => {
setStatus(status);
};
Presence.listen(uid, handle);

@ -1,80 +0,0 @@
import { Emitter } from '@rocket.chat/emitter';
import { APIClient } from '../../app/utils/client';
export const Presence = new Emitter();
const Statuses = new Map();
const getPresence = (() => {
const uids = new Set();
let timer;
const fetch = () => {
timer && clearTimeout(timer);
timer = setTimeout(async () => {
const params = {
ids: [...uids],
};
const {
users,
} = await APIClient.v1.get('users.presence', params);
users.forEach((user) => {
Presence.emit(user._id, user);
uids.delete(user._id);
});
[...uids].forEach((uid) => {
Presence.emit(uid, { uid });
});
uids.clear();
}, 50);
};
const get = async (uid) => {
uids.add(uid);
fetch();
};
Presence.on('remove', (uid) => {
if (Presence.has(uid)) {
return;
}
Statuses.delete(uid);
});
Presence.on('reset', () => {
Presence.once('restart', () => Presence.events().filter((e) => Boolean(e) && !['reset', 'restart', 'remove'].includes(e) && typeof e === 'string').forEach(get));
});
return get;
})();
const update = ({ _id: uid, status }) => {
Statuses.set(uid, status);
};
Presence.listen = async (uid, handle) => {
Presence.on(uid, handle);
Presence.on(uid, update);
Presence.on('reset', handle);
if (Statuses.has(uid)) {
return handle({ status: Statuses.get(uid) });
}
getPresence(uid);
};
Presence.stop = (uid, handle) => {
Presence.off(uid, handle);
Presence.off('reset', handle);
Presence.emit('remove', uid);
};
Presence.reset = () => {
Presence.emit('reset', { status: 'offline' });
Statuses.clear();
};

@ -0,0 +1,133 @@
import { Emitter, EventType, Handler } from '@rocket.chat/emitter';
import { APIClient } from '../../app/utils/client';
import { IUser } from '../../definition/IUser';
const emitter = new Emitter();
const statuses = new Map();
type User = Pick<IUser, '_id' | 'username' | 'name' | 'status' | 'utcOffset' | 'statusText' | 'avatarETag'>;
type UsersPresencePayload = {
users: User[];
full: boolean;
};
const isUid = (eventType: EventType): eventType is User['_id'] =>
Boolean(eventType) && typeof eventType === 'string' && !['reset', 'restart', 'remove'].includes(eventType);
const uids = new Set<User['_id']>();
const getPresence = ((): ((uid: User['_id']) => void) => {
let timer: ReturnType<typeof setTimeout>;
const fetch = (delay = 250): void => {
timer && clearTimeout(timer);
timer = setTimeout(async () => {
const currentUids = new Set(uids);
uids.clear();
try {
const params = {
ids: [...currentUids],
};
const { users } = await APIClient.v1.get('users.presence', params) as UsersPresencePayload;
users.forEach((user) => {
if (!statuses.has(user._id)) {
emitter.emit(user._id, user);
}
currentUids.delete(user._id);
});
currentUids.forEach((uid) => {
emitter.emit(uid, { uid, status: 'offline' });
});
currentUids.clear();
} catch {
fetch(delay + delay);
} finally {
currentUids.forEach((item) => uids.add(item));
}
}, delay);
};
const get = (uid: User['_id']): void => {
uids.add(uid);
fetch();
};
emitter.on('remove', (uid) => {
if (emitter.has(uid)) {
return;
}
statuses.delete(uid);
});
emitter.on('reset', () => {
statuses.clear();
emitter.once('restart', () => {
emitter.events()
.filter(isUid)
.forEach(get);
});
});
return get;
})();
type PresenceUpdate = Partial<Pick<User, '_id' | 'username' | 'status' | 'statusText'>>;
const update: Handler<PresenceUpdate> = (update) => {
if (update?._id) {
statuses.set(update._id, update.status);
uids.delete(update._id);
}
};
const listen = (uid: User['_id'], handler: Handler<PresenceUpdate>): void => {
emitter.on(uid, handler);
emitter.on(uid, update);
emitter.on('reset', handler);
if (statuses.has(uid)) {
return handler({ status: statuses.get(uid) });
}
getPresence(uid);
};
const stop = (uid: User['_id'], handler: Handler<PresenceUpdate>): void => {
emitter.off(uid, handler);
emitter.off(uid, update);
emitter.off('reset', handler);
emitter.emit('remove', uid);
};
const reset = (): void => {
emitter.emit('reset', {});
statuses.clear();
};
const restart = (): void => {
emitter.emit('restart');
};
const notify = (update: PresenceUpdate): void => {
if (update._id) {
emitter.emit(update._id, update);
}
if (update.username) {
emitter.emit(update.username, update);
}
};
export const Presence = {
listen,
stop,
reset,
restart,
notify,
};

@ -95,7 +95,7 @@ export interface IUser {
name?: string;
services?: IUserServices;
emails?: IUserEmail[];
status?: string;
status?: USER_STATUS;
statusConnection?: string;
lastLogin?: Date;
avatarOrigin?: string;

@ -42,8 +42,12 @@ export const saveUser = (user, force = false) => {
Meteor.startup(function() {
Notifications.onLogged('user-status', ([_id, username, status, statusText]) => {
Presence.emit(_id, { _id, status: STATUS_MAP[status], statusText, username });
Presence.emit(username, { _id, status: STATUS_MAP[status], statusText, username });
Presence.notify({
_id,
username,
status: STATUS_MAP[status],
statusText,
});
if (!interestedUserIds.has(_id)) {
return;
}

36
package-lock.json generated

@ -18113,7 +18113,7 @@
},
"chownr": {
"version": "1.1.1",
"resolved": "",
"resolved": false,
"integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==",
"dev": true,
"optional": true
@ -18148,7 +18148,7 @@
},
"debug": {
"version": "4.1.1",
"resolved": "",
"resolved": false,
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true,
"optional": true,
@ -18179,7 +18179,7 @@
},
"fs-minipass": {
"version": "1.2.5",
"resolved": "",
"resolved": false,
"integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
"dev": true,
"optional": true,
@ -18213,7 +18213,7 @@
},
"glob": {
"version": "7.1.3",
"resolved": "",
"resolved": false,
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"dev": true,
"optional": true,
@ -18245,7 +18245,7 @@
},
"ignore-walk": {
"version": "3.0.1",
"resolved": "",
"resolved": false,
"integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
"dev": true,
"optional": true,
@ -18266,7 +18266,7 @@
},
"inherits": {
"version": "2.0.3",
"resolved": "",
"resolved": false,
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true,
"optional": true
@ -18314,7 +18314,7 @@
},
"minipass": {
"version": "2.3.5",
"resolved": "",
"resolved": false,
"integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
"dev": true,
"optional": true,
@ -18325,7 +18325,7 @@
},
"minizlib": {
"version": "1.2.1",
"resolved": "",
"resolved": false,
"integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==",
"dev": true,
"optional": true,
@ -18345,7 +18345,7 @@
},
"ms": {
"version": "2.1.1",
"resolved": "",
"resolved": false,
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true,
"optional": true
@ -18359,7 +18359,7 @@
},
"needle": {
"version": "2.3.0",
"resolved": "",
"resolved": false,
"integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==",
"dev": true,
"optional": true,
@ -18371,7 +18371,7 @@
},
"node-pre-gyp": {
"version": "0.12.0",
"resolved": "",
"resolved": false,
"integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==",
"dev": true,
"optional": true,
@ -18401,14 +18401,14 @@
},
"npm-bundled": {
"version": "1.0.6",
"resolved": "",
"resolved": false,
"integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==",
"dev": true,
"optional": true
},
"npm-packlist": {
"version": "1.4.1",
"resolved": "",
"resolved": false,
"integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==",
"dev": true,
"optional": true,
@ -18488,7 +18488,7 @@
},
"process-nextick-args": {
"version": "2.0.0",
"resolved": "",
"resolved": false,
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
"dev": true,
"optional": true
@ -18533,7 +18533,7 @@
},
"rimraf": {
"version": "2.6.3",
"resolved": "",
"resolved": false,
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"dev": true,
"optional": true,
@ -18564,7 +18564,7 @@
},
"semver": {
"version": "5.7.0",
"resolved": "",
"resolved": false,
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
"dev": true,
"optional": true
@ -18624,7 +18624,7 @@
},
"tar": {
"version": "4.4.8",
"resolved": "",
"resolved": false,
"integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
"dev": true,
"optional": true,
@ -18664,7 +18664,7 @@
},
"yallist": {
"version": "3.0.3",
"resolved": "",
"resolved": false,
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
"dev": true,
"optional": true

Loading…
Cancel
Save