From adbf1711e656102349b17a3bd13ff393ceeff230 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 17 Aug 2021 11:21:37 -0300 Subject: [PATCH 01/13] Add new stream to get individual presence updates --- app/notifications/client/lib/Notifications.js | 5 ++ app/ui-sidenav/client/userPresence.js | 54 +-------------- client/lib/presence.ts | 8 +++ client/startup/index.ts | 1 - client/startup/listenActiveUsers.ts | 69 ------------------- server/modules/listeners/listeners.module.ts | 4 ++ .../notifications/notifications.module.ts | 14 ++++ 7 files changed, 32 insertions(+), 123 deletions(-) delete mode 100644 client/startup/listenActiveUsers.ts diff --git a/app/notifications/client/lib/Notifications.js b/app/notifications/client/lib/Notifications.js index cc9f8f9ebb4b..4e1733a0e190 100644 --- a/app/notifications/client/lib/Notifications.js +++ b/app/notifications/client/lib/Notifications.js @@ -17,6 +17,7 @@ class Notifications { this.streamRoom = new Meteor.Streamer('notify-room'); this.streamRoomUsers = new Meteor.Streamer('notify-room-users'); this.streamUser = new Meteor.Streamer('notify-user'); + this.streamPresence = new Meteor.Streamer('user-presence'); if (this.debug === true) { this.onAll(function() { return console.log('RocketChat.Notifications: onAll', args); @@ -95,6 +96,10 @@ class Notifications { unUser(eventName, callback) { return this.streamUser.removeListener(`${ Meteor.userId() }/${ eventName }`, callback); } + + onUserPresence(uid, callback) { + return this.streamPresence.on(uid, callback); + } } export default new Notifications(); diff --git a/app/ui-sidenav/client/userPresence.js b/app/ui-sidenav/client/userPresence.js index 957b4537268f..56f1b699b4d2 100644 --- a/app/ui-sidenav/client/userPresence.js +++ b/app/ui-sidenav/client/userPresence.js @@ -2,58 +2,12 @@ import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import { Template } from 'meteor/templating'; import { Tracker } from 'meteor/tracker'; -import _ from 'underscore'; -import mem from 'mem'; -import { APIClient } from '../../utils/client'; -import { saveUser, interestedUserIds } from '../../../client/startup/listenActiveUsers'; import { Presence } from '../../../client/lib/presence'; import './userPresence.html'; const data = new Map(); -const promises = new Map(); -const pending = new Map(); - -const getAll = _.debounce(async function getAll() { - const ids = Array.from(pending.keys()); - - if (ids.length === 0) { - return; - } - - const params = { - ids, - }; - - try { - const { - users, - } = await APIClient.v1.get('users.presence', params); - - users.forEach((user) => saveUser(user, true)); - - ids.forEach((id) => { - const { resolve } = promises.get(id); - resolve(); - }); - } catch (e) { - ids.forEach((id) => { - const { reject } = promises.get(id); - reject(); - }); - } -}, 100); - -export const get = mem(function get(id) { - interestedUserIds.add(id); - const promise = pending.get(id) || new Promise((resolve, reject) => { - promises.set(id, { resolve, reject }); - }); - pending.set(id, promise); - return promise; -}); - const options = { threshold: 0.1, }; @@ -63,10 +17,8 @@ const handleEntries = function(entries) { lastEntries = entries.filter(({ isIntersecting }) => isIntersecting); lastEntries.forEach(async (entry) => { const { uid } = data.get(entry.target); - await get(uid); - pending.delete(uid); + Presence.get(uid); }); - getAll(); }; const featureExists = !!window.IntersectionObserver; @@ -80,7 +32,6 @@ Tracker.autorun(() => { Presence.reset(); return Meteor.users.update({ status: { $exists: true } }, { $unset: { status: true } }, { multi: true }); } - mem.clear(get); Presence.restart(); @@ -93,11 +44,8 @@ Tracker.autorun(() => { } - getAll(); - Accounts.onLogout(() => { Presence.reset(); - interestedUserIds.clear(); }); }); diff --git a/client/lib/presence.ts b/client/lib/presence.ts index ad65211ae5ff..66fa2366aa31 100644 --- a/client/lib/presence.ts +++ b/client/lib/presence.ts @@ -1,5 +1,6 @@ import { Emitter, EventHandlerOf } from '@rocket.chat/emitter'; +import { Notifications } from '../../app/notifications/client'; import { APIClient } from '../../app/utils/client'; import { IUser } from '../../definition/IUser'; import { UserStatus } from '../../definition/UserStatus'; @@ -57,6 +58,9 @@ const getPresence = ((): ((uid: UserPresence['_id']) => void) => { const fetch = (delay = 250): void => { timer && clearTimeout(timer); timer = setTimeout(async () => { + if (uids.size === 0) { + return; + } const currentUids = new Set(uids); uids.clear(); try { @@ -90,6 +94,10 @@ const getPresence = ((): ((uid: UserPresence['_id']) => void) => { }; const get = (uid: UserPresence['_id']): void => { + Notifications.onUserPresence(uid, (status: UserPresence['status']) => { + notify({ _id: uid, status }); + }); + uids.add(uid); fetch(); }; diff --git a/client/startup/index.ts b/client/startup/index.ts index 5871e792aed4..7fcee319e860 100644 --- a/client/startup/index.ts +++ b/client/startup/index.ts @@ -4,7 +4,6 @@ import './contextualBar'; import './e2e'; import './emailVerification'; import './i18n'; -import './listenActiveUsers'; import './routes'; import './loginViaQuery'; import './messageTypes'; diff --git a/client/startup/listenActiveUsers.ts b/client/startup/listenActiveUsers.ts deleted file mode 100644 index 12ba5df2b3be..000000000000 --- a/client/startup/listenActiveUsers.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Accounts } from 'meteor/accounts-base'; -import { Meteor } from 'meteor/meteor'; - -import { Notifications } from '../../app/notifications/client'; -import { IUser } from '../../definition/IUser'; -import { UserStatus } from '../../definition/UserStatus'; -import { Presence } from '../lib/presence'; - -const STATUS_MAP = [UserStatus.OFFLINE, UserStatus.ONLINE, UserStatus.AWAY, UserStatus.BUSY]; - -export const interestedUserIds = new Set(); - -export const saveUser = ( - user: Pick, - force = false, -): void => { - // do not update my own user, my user's status will come from a subscription - if (user._id === (Accounts as any).connection?._userId) { - return; - } - - const found = (Meteor.users as any)._collection._docs._map[user._id]; - - if (found && force) { - Meteor.users.update( - { _id: user._id }, - { - $set: { - ...(user.username && { username: user.username }), - // name: user.name, - // utcOffset: user.utcOffset, - status: user.status, - statusText: user.statusText, - ...(user.avatarETag && { avatarETag: user.avatarETag }), - }, - }, - ); - - return; - } - - if (!found) { - Meteor.users.insert(user); - } -}; - -Meteor.startup(() => { - Notifications.onLogged( - 'user-status', - ([_id, username, status, statusText]: [ - IUser['_id'], - IUser['username'], - number, - IUser['statusText'], - ]) => { - Presence.notify({ - _id, - username, - status: STATUS_MAP[status], - statusText, - }); - if (!interestedUserIds.has(_id)) { - return; - } - - saveUser({ _id, username, status: STATUS_MAP[status], statusText }, true); - }, - ); -}); diff --git a/server/modules/listeners/listeners.module.ts b/server/modules/listeners/listeners.module.ts index 81ed742940f5..b32b19f94802 100644 --- a/server/modules/listeners/listeners.module.ts +++ b/server/modules/listeners/listeners.module.ts @@ -87,6 +87,10 @@ export class ListenersModule { } notifications.notifyLoggedInThisInstance('user-status', [_id, username, STATUS_MAP[status], statusText]); + + if (_id) { + notifications.sendPresence(_id, status); + } }); service.onEvent('user.updateCustomStatus', (userStatus) => { diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index e49939ebfe6b..71d67661003c 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -52,6 +52,8 @@ export class NotificationsModule { public readonly streamLocal: IStreamer; + public readonly streamPresence: IStreamer; + constructor( private Streamer: IStreamerConstructor, ) { @@ -70,6 +72,8 @@ export class NotificationsModule { this.streamStdout = new this.Streamer('stdout'); this.streamRoomData = new this.Streamer('room-data'); + this.streamPresence = new this.Streamer('user-presence'); + this.streamRoomMessage = new this.Streamer('room-messages'); this.streamRoomMessage.on('_afterPublish', async (streamer: IStreamer, publication: IPublication, eventName: string): Promise => { @@ -162,6 +166,9 @@ export class NotificationsModule { this.streamLogged.allowWrite('none'); this.streamLogged.allowRead('logged'); + this.streamPresence.allowWrite('none'); + this.streamPresence.allowRead('logged'); + this.streamRoom.allowRead(async function(eventName, extraData): Promise { const [rid] = eventName.split('/'); @@ -445,6 +452,13 @@ export class NotificationsModule { return this.streamUser.emitWithoutBroadcast(`${ userId }/${ eventName }`, ...args); } + sendPresence(userId: string, status: string): void { + // if (this.debug === true) { + // console.log('notifyUserAndBroadcast', [userId, eventName, ...args]); + // } + return this.streamPresence.emitWithoutBroadcast(userId, status); + } + progressUpdated(progress: {rate: number}): void { this.streamImporters.emit('progress', progress); } From 5967576527dae5ef7506d303322169d28231ad3a Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 17 Aug 2021 17:59:27 -0300 Subject: [PATCH 02/13] Send status text on user-presence stream --- server/modules/listeners/listeners.module.ts | 2 +- server/modules/notifications/notifications.module.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/modules/listeners/listeners.module.ts b/server/modules/listeners/listeners.module.ts index b32b19f94802..76c27d7779e6 100644 --- a/server/modules/listeners/listeners.module.ts +++ b/server/modules/listeners/listeners.module.ts @@ -89,7 +89,7 @@ export class ListenersModule { notifications.notifyLoggedInThisInstance('user-status', [_id, username, STATUS_MAP[status], statusText]); if (_id) { - notifications.sendPresence(_id, status); + notifications.sendPresence(_id, [username, STATUS_MAP[status], statusText]); } }); diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 71d67661003c..4818f6010050 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -452,11 +452,11 @@ export class NotificationsModule { return this.streamUser.emitWithoutBroadcast(`${ userId }/${ eventName }`, ...args); } - sendPresence(userId: string, status: string): void { + sendPresence(userId: string, ...args: any[]): void { // if (this.debug === true) { // console.log('notifyUserAndBroadcast', [userId, eventName, ...args]); // } - return this.streamPresence.emitWithoutBroadcast(userId, status); + return this.streamPresence.emitWithoutBroadcast(userId, ...args); } progressUpdated(progress: {rate: number}): void { From 35076e3dff0c5bceb1aa927c2cd086363e11f3cc Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 17 Aug 2021 18:00:44 -0300 Subject: [PATCH 03/13] Remove useUserData hook in favor of usePresence --- app/notifications/client/lib/Notifications.js | 4 +++ client/components/Message/StatusMessage.tsx | 4 +-- .../UserStatus/ReactiveUserStatus.js | 2 +- client/hooks/usePresence.ts | 24 +++++++++++++--- client/hooks/useUserData.ts | 28 ------------------- client/lib/presence.ts | 20 ++++++++++--- client/views/room/Header/DirectRoomHeader.js | 4 +-- .../room/contextualBar/OTR/OTRWithData.js | 2 +- 8 files changed, 46 insertions(+), 42 deletions(-) delete mode 100644 client/hooks/useUserData.ts diff --git a/app/notifications/client/lib/Notifications.js b/app/notifications/client/lib/Notifications.js index 4e1733a0e190..4d80ba38a011 100644 --- a/app/notifications/client/lib/Notifications.js +++ b/app/notifications/client/lib/Notifications.js @@ -100,6 +100,10 @@ class Notifications { onUserPresence(uid, callback) { return this.streamPresence.on(uid, callback); } + + unUserPresence(uid, callback) { + return this.streamPresence.removeListener(uid, callback); + } } export default new Notifications(); diff --git a/client/components/Message/StatusMessage.tsx b/client/components/Message/StatusMessage.tsx index 9be5ac8695b5..9f90bcecc78b 100644 --- a/client/components/Message/StatusMessage.tsx +++ b/client/components/Message/StatusMessage.tsx @@ -1,10 +1,10 @@ import { Box, Icon } from '@rocket.chat/fuselage'; import React, { ReactElement, memo } from 'react'; -import { useUserData } from '../../hooks/useUserData'; +import { usePresence } from '../../hooks/usePresence'; const StatusMessage = ({ uid }: { uid: string }): ReactElement | null => { - const data = useUserData(uid); + const data = usePresence(uid); if (!data || !data.statusText) { return null; diff --git a/client/components/UserStatus/ReactiveUserStatus.js b/client/components/UserStatus/ReactiveUserStatus.js index 185c44a4b5df..30a0b19242b5 100644 --- a/client/components/UserStatus/ReactiveUserStatus.js +++ b/client/components/UserStatus/ReactiveUserStatus.js @@ -4,7 +4,7 @@ import { usePresence } from '../../hooks/usePresence'; import UserStatus from './UserStatus'; const ReactiveUserStatus = ({ uid, ...props }) => { - const status = usePresence(uid); + const status = usePresence(uid)?.status; return ; }; diff --git a/client/hooks/usePresence.ts b/client/hooks/usePresence.ts index 56e1b24446be..c18768edeee5 100644 --- a/client/hooks/usePresence.ts +++ b/client/hooks/usePresence.ts @@ -1,4 +1,7 @@ -import { useUserData } from './useUserData'; +import { useMemo } from 'react'; +import { useSubscription } from 'use-subscription'; + +import { Presence, UserPresence } from '../lib/presence'; type Presence = 'online' | 'offline' | 'busy' | 'away' | 'loading'; @@ -6,9 +9,22 @@ type Presence = 'online' | 'offline' | 'busy' | 'away' | 'loading'; * Hook to fetch and subscribe users presence * * @param uid - User Id - * @returns User Presence - 'online' | 'offline' | 'busy' | 'away' | 'loading' + * @returns UserPresence * @public */ +export const usePresence = (uid: string): UserPresence | undefined => { + const subscription = useMemo( + () => ({ + getCurrentValue: (): UserPresence | undefined => Presence.store.get(uid), + subscribe: (callback: any): any => { + Presence.listen(uid, callback); + return (): void => { + Presence.stop(uid, callback); + }; + }, + }), + [uid], + ); -export const usePresence = (uid: string, presence: Presence): Presence => - useUserData(uid)?.status || presence; + return useSubscription(subscription); +}; diff --git a/client/hooks/useUserData.ts b/client/hooks/useUserData.ts deleted file mode 100644 index 1dc86854437d..000000000000 --- a/client/hooks/useUserData.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; - -import { Presence, UserPresence } from '../lib/presence'; - -/** - * Hook to fetch and subscribe users data - * - * @param uid - User Id - * @returns Users data: status, statusText, username, name - * @public - */ -export const useUserData = (uid: string): UserPresence | undefined => { - const subscription = useMemo( - () => ({ - getCurrentValue: (): UserPresence | undefined => Presence.store.get(uid), - subscribe: (callback: any): any => { - Presence.listen(uid, callback); - return (): void => { - Presence.stop(uid, callback); - }; - }, - }), - [uid], - ); - - return useSubscription(subscription); -}; diff --git a/client/lib/presence.ts b/client/lib/presence.ts index 66fa2366aa31..93dcb8697bfe 100644 --- a/client/lib/presence.ts +++ b/client/lib/presence.ts @@ -5,6 +5,8 @@ import { APIClient } from '../../app/utils/client'; import { IUser } from '../../definition/IUser'; import { UserStatus } from '../../definition/UserStatus'; +const STATUS_MAP = [UserStatus.OFFLINE, UserStatus.ONLINE, UserStatus.AWAY, UserStatus.BUSY]; + type InternalEvents = { remove: IUser['_id']; reset: undefined; @@ -52,6 +54,8 @@ const notify = (presence: UserPresence): void => { } }; +const subStream = new Map(); + const getPresence = ((): ((uid: UserPresence['_id']) => void) => { let timer: ReturnType; @@ -94,12 +98,19 @@ const getPresence = ((): ((uid: UserPresence['_id']) => void) => { }; const get = (uid: UserPresence['_id']): void => { - Notifications.onUserPresence(uid, (status: UserPresence['status']) => { - notify({ _id: uid, status }); - }); - uids.add(uid); fetch(); + + if (!subStream.has(uid)) { + subStream.set( + uid, + ([username, status, statusText]: [IUser['username'], number, IUser['statusText']]) => { + notify({ _id: uid, username, status: STATUS_MAP[status], statusText }); + }, + ); + + Notifications.onUserPresence(uid, subStream.get(uid)); + } }; emitter.on('remove', (uid) => { @@ -108,6 +119,7 @@ const getPresence = ((): ((uid: UserPresence['_id']) => void) => { } store.delete(uid); + Notifications.unUserPresence(uid, subStream.get(uid)); }); emitter.on('reset', () => { diff --git a/client/views/room/Header/DirectRoomHeader.js b/client/views/room/Header/DirectRoomHeader.js index fc7ff71908de..46895cfabb35 100644 --- a/client/views/room/Header/DirectRoomHeader.js +++ b/client/views/room/Header/DirectRoomHeader.js @@ -1,13 +1,13 @@ import React from 'react'; import { useUserId } from '../../../contexts/UserContext'; -import { useUserData } from '../../../hooks/useUserData'; +import { usePresence } from '../../../hooks/usePresence'; import RoomHeader from './RoomHeader'; const DirectRoomHeader = ({ room, slots }) => { const userId = useUserId(); const directUserId = room.uids.filter((uid) => uid !== userId).shift(); - const directUserData = useUserData(directUserId); + const directUserData = usePresence(directUserId); return ; }; diff --git a/client/views/room/contextualBar/OTR/OTRWithData.js b/client/views/room/contextualBar/OTR/OTRWithData.js index 0eddcf569af9..db96bf423c12 100644 --- a/client/views/room/contextualBar/OTR/OTRWithData.js +++ b/client/views/room/contextualBar/OTR/OTRWithData.js @@ -22,7 +22,7 @@ const OTRWithData = ({ rid, tabBar }) => { ), ); - const userStatus = usePresence(otr.peerId); + const userStatus = usePresence(otr.peerId)?.status; const isOnline = !['offline', 'loading'].includes(userStatus); From 008ccb4946fc48f8fc4ab823b66ea231e6904bed Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 2 Sep 2021 10:24:47 -0300 Subject: [PATCH 04/13] WIP --- app/notifications/client/lib/Notifications.js | 2 + app/notifications/client/lib/Presence.ts | 17 ++++ app/notifications/server/lib/Notifications.ts | 1 + app/notifications/server/lib/Presence.ts | 86 +++++++++++++++++++ client/lib/presence.ts | 42 +++++---- .../notifications/notifications.module.ts | 7 +- 6 files changed, 137 insertions(+), 18 deletions(-) create mode 100644 app/notifications/client/lib/Presence.ts create mode 100644 app/notifications/server/lib/Presence.ts diff --git a/app/notifications/client/lib/Notifications.js b/app/notifications/client/lib/Notifications.js index 4d80ba38a011..e5db4a33a4dd 100644 --- a/app/notifications/client/lib/Notifications.js +++ b/app/notifications/client/lib/Notifications.js @@ -1,6 +1,8 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; +import './Presence'; + class Notifications { constructor(...args) { this.logged = Meteor.userId() !== null; diff --git a/app/notifications/client/lib/Presence.ts b/app/notifications/client/lib/Presence.ts new file mode 100644 index 000000000000..fba21b08d99e --- /dev/null +++ b/app/notifications/client/lib/Presence.ts @@ -0,0 +1,17 @@ +import { DDPCommon } from 'meteor/ddp-common'; +import { Meteor } from 'meteor/meteor'; + +import { Presence, STATUS_MAP } from '../../../../client/lib/presence'; + +// Meteor.onConnection((connection) => { +Meteor.connection._stream.on('message', (rawMsg: string) => { + const msg = DDPCommon.parseDDP(rawMsg); + if (msg.msg !== 'changed' || msg.collection !== 'stream-user-presences') { + return; + } + + const { uid, args } = msg.fields; + const [[username, status, statusText]] = args; + Presence.notify({ _id: uid, username, status: STATUS_MAP[status], statusText }); +}); +// }); diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 0bd8eaf7d057..6653cd98c829 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -11,6 +11,7 @@ import { Users as UsersRaw, Settings as SettingsRaw, } from '../../../models/server/raw'; +import './Presence'; // TODO: Replace this in favor of the api.broadcast // StreamerCentral.on('broadcast', (name, eventName, args) => { diff --git a/app/notifications/server/lib/Presence.ts b/app/notifications/server/lib/Presence.ts new file mode 100644 index 000000000000..8e795f8e2725 --- /dev/null +++ b/app/notifications/server/lib/Presence.ts @@ -0,0 +1,86 @@ +import { DDPCommon } from 'meteor/ddp-common'; +import { Emitter } from '@rocket.chat/emitter'; +import { Meteor } from 'meteor/meteor'; + +import { IUser } from '../../../../definition/IUser'; + +type UserPresenseStreamProps = { + added: IUser['_id'][]; + removed: IUser['_id'][]; +} + +type UserPresenseStreamArgs = { + 'uid': string; + args: unknown; +} + +const e = new Emitter<{ + [key: string]: UserPresenseStreamArgs; +}>(); + + +const clients = new WeakMap(); + +class UserPresence { + private readonly publication: Subscription; + + private readonly listeners: Set; + + constructor(publication: Subscription) { + this.listeners = new Set(); + this.publication = publication; + } + + listen(uid: string): void { + if (this.listeners.has(uid)) { + return; + } + e.on(uid, this.run); + this.listeners.add(uid); + } + + off = (uid: string): void => { + e.off(uid, this.run); + this.listeners.delete(uid); + } + + run = (args: UserPresenseStreamArgs): void => { + (this.publication as any)._session.socket.send(DDPCommon.stringifyDDP({ + msg: 'changed', + collection: 'stream-user-presences', + id: args.uid, + fields: args, + })); + } + + stop(): void { + this.listeners.forEach(this.off); + clients.delete(this.publication.connection as Meteor.Connection); + } +} + +Meteor.publish('streamer-user-presences', function({ added, removed }: UserPresenseStreamProps) { + const stored = clients.get(this.connection); + + const client = stored || new UserPresence(this); + + const main = Boolean(!stored); + + clients.set(this.connection, client); + + added?.forEach((uid) => client.listen(uid)); + removed?.forEach((uid) => client.off(uid)); + + + if (!main) { + this.stop(); + return; + } + + this.ready(); + this.connection.onClose(() => client.stop()); + + this.onStop(() => client.stop()); +}); + +export const emit = (uid: string, args: UserPresenseStreamArgs): void => e.emit(uid, { uid, args }); diff --git a/client/lib/presence.ts b/client/lib/presence.ts index 93dcb8697bfe..e12e49cc2bc4 100644 --- a/client/lib/presence.ts +++ b/client/lib/presence.ts @@ -1,11 +1,12 @@ import { Emitter, EventHandlerOf } from '@rocket.chat/emitter'; +import { Meteor } from 'meteor/meteor'; -import { Notifications } from '../../app/notifications/client'; +// import { Notifications } from '../../app/notifications/client'; import { APIClient } from '../../app/utils/client'; import { IUser } from '../../definition/IUser'; import { UserStatus } from '../../definition/UserStatus'; -const STATUS_MAP = [UserStatus.OFFLINE, UserStatus.ONLINE, UserStatus.AWAY, UserStatus.BUSY]; +export const STATUS_MAP = [UserStatus.OFFLINE, UserStatus.ONLINE, UserStatus.AWAY, UserStatus.BUSY]; type InternalEvents = { remove: IUser['_id']; @@ -54,11 +55,13 @@ const notify = (presence: UserPresence): void => { } }; -const subStream = new Map(); +// const subStream = new Map(); const getPresence = ((): ((uid: UserPresence['_id']) => void) => { let timer: ReturnType; + const deletedUids = new Set(); + const fetch = (delay = 250): void => { timer && clearTimeout(timer); timer = setTimeout(async () => { @@ -67,6 +70,21 @@ const getPresence = ((): ((uid: UserPresence['_id']) => void) => { } const currentUids = new Set(uids); uids.clear(); + + const ids = Array.from(currentUids); + const deleted = Array.from(deletedUids); + + Meteor.subscribe('streamer-user-presences', { + ...(ids.length > 0 && { added: Array.from(currentUids) }), + ...(deleted.length && { deleted: Array.from(deletedUids) }), + }); + + deletedUids.clear(); + + if (ids.length === 0) { + return; + } + try { const params = { ids: [...currentUids], @@ -100,26 +118,18 @@ const getPresence = ((): ((uid: UserPresence['_id']) => void) => { const get = (uid: UserPresence['_id']): void => { uids.add(uid); fetch(); - - if (!subStream.has(uid)) { - subStream.set( - uid, - ([username, status, statusText]: [IUser['username'], number, IUser['statusText']]) => { - notify({ _id: uid, username, status: STATUS_MAP[status], statusText }); - }, - ); - - Notifications.onUserPresence(uid, subStream.get(uid)); - } }; - + const stop = (uid: UserPresence['_id']): void => { + deletedUids.add(uid); + fetch(); + }; emitter.on('remove', (uid) => { if (emitter.has(uid)) { return; } store.delete(uid); - Notifications.unUserPresence(uid, subStream.get(uid)); + stop(uid); }); emitter.on('reset', () => { diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 4818f6010050..2d978e3f6eb2 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -3,11 +3,13 @@ import { Authorization } from '../../sdk'; import { RoomsRaw } from '../../../app/models/server/raw/Rooms'; import { SubscriptionsRaw } from '../../../app/models/server/raw/Subscriptions'; import { ISubscription } from '../../../definition/ISubscription'; +import { emit } from '../../../app/notifications/server/lib/Presence'; import { UsersRaw } from '../../../app/models/server/raw/Users'; import { SettingsRaw } from '../../../app/models/server/raw/Settings'; import { IOmnichannelRoom } from '../../../definition/IRoom'; import { IUser } from '../../../definition/IUser'; + interface IModelsParam { Rooms: RoomsRaw; Subscriptions: SubscriptionsRaw; @@ -452,11 +454,12 @@ export class NotificationsModule { return this.streamUser.emitWithoutBroadcast(`${ userId }/${ eventName }`, ...args); } - sendPresence(userId: string, ...args: any[]): void { + sendPresence(uid: string, ...args: any[]): void { // if (this.debug === true) { // console.log('notifyUserAndBroadcast', [userId, eventName, ...args]); // } - return this.streamPresence.emitWithoutBroadcast(userId, ...args); + emit(uid, args as any); + return this.streamPresence.emitWithoutBroadcast(uid, ...args); } progressUpdated(progress: {rate: number}): void { From 52de8b02151f9e1ebd3ed554ea54ea87e99f7484 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 2 Sep 2021 13:30:00 -0300 Subject: [PATCH 05/13] StatusMessage deprecated --- app/ui-message/client/message.html | 1 - client/components/Message/StatusMessage.tsx | 6 +++++- client/templates.ts | 7 ------- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/app/ui-message/client/message.html b/app/ui-message/client/message.html index 5e3d05079d97..ae1c6263279c 100644 --- a/app/ui-message/client/message.html +++ b/app/ui-message/client/message.html @@ -40,7 +40,6 @@ - {{> StatusMessage uid=msg.u._id}} {{# if showRoles }} {{#each role in roleTags}} diff --git a/client/components/Message/StatusMessage.tsx b/client/components/Message/StatusMessage.tsx index 9f90bcecc78b..1704c8b7280f 100644 --- a/client/components/Message/StatusMessage.tsx +++ b/client/components/Message/StatusMessage.tsx @@ -1,10 +1,14 @@ import { Box, Icon } from '@rocket.chat/fuselage'; -import React, { ReactElement, memo } from 'react'; +import React, { ReactElement, memo, useEffect } from 'react'; import { usePresence } from '../../hooks/usePresence'; +// TODO: deprecate this component const StatusMessage = ({ uid }: { uid: string }): ReactElement | null => { const data = usePresence(uid); + useEffect(() => { + process.env.NODE_ENV === 'development' && console.log('StatusMessage component is deprecated'); + }, [data]); if (!data || !data.statusText) { return null; diff --git a/client/templates.ts b/client/templates.ts index c51659ff18a6..2ca7ab03b977 100644 --- a/client/templates.ts +++ b/client/templates.ts @@ -232,11 +232,4 @@ createTemplateForComponent( createTemplateForComponent('UserDropdown', () => import('./sidebar/header/UserDropdown')); -createTemplateForComponent('StatusMessage', () => import('./components/Message/StatusMessage'), { - renderContainerView: () => - HTML.DIV({ - class: 'message-custom-status', - }), -}); - createTemplateForComponent('sidebarFooter', () => import('./sidebar/footer')); From ba81b05033d37a7991e36e5675d4a1f80040710e Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 2 Sep 2021 14:12:04 -0300 Subject: [PATCH 06/13] fix build --- app/notifications/client/lib/Presence.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/notifications/client/lib/Presence.ts b/app/notifications/client/lib/Presence.ts index fba21b08d99e..df5be2192984 100644 --- a/app/notifications/client/lib/Presence.ts +++ b/app/notifications/client/lib/Presence.ts @@ -1,10 +1,13 @@ +import { Stream } from 'stream'; + import { DDPCommon } from 'meteor/ddp-common'; import { Meteor } from 'meteor/meteor'; + import { Presence, STATUS_MAP } from '../../../../client/lib/presence'; // Meteor.onConnection((connection) => { -Meteor.connection._stream.on('message', (rawMsg: string) => { +(Meteor as unknown as { connection: Meteor.Connection & { _stream: Stream } }).connection._stream.on('message', (rawMsg: string) => { const msg = DDPCommon.parseDDP(rawMsg); if (msg.msg !== 'changed' || msg.collection !== 'stream-user-presences') { return; From 1bb4943af54923c916b40f23f076c95ac7ccf13f Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 2 Sep 2021 23:01:05 -0300 Subject: [PATCH 07/13] typo --- client/lib/presence.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/client/lib/presence.ts b/client/lib/presence.ts index e12e49cc2bc4..a680265099ba 100644 --- a/client/lib/presence.ts +++ b/client/lib/presence.ts @@ -62,21 +62,18 @@ const getPresence = ((): ((uid: UserPresence['_id']) => void) => { const deletedUids = new Set(); - const fetch = (delay = 250): void => { + const fetch = (delay = 500): void => { timer && clearTimeout(timer); timer = setTimeout(async () => { - if (uids.size === 0) { - return; - } const currentUids = new Set(uids); uids.clear(); const ids = Array.from(currentUids); - const deleted = Array.from(deletedUids); + const removed = Array.from(deletedUids); Meteor.subscribe('streamer-user-presences', { ...(ids.length > 0 && { added: Array.from(currentUids) }), - ...(deleted.length && { deleted: Array.from(deletedUids) }), + ...(removed.length && { removed: Array.from(deletedUids) }), }); deletedUids.clear(); From f2c1f91907cafee2d785ee05452602d29be355e0 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 28 Sep 2021 15:11:37 -0300 Subject: [PATCH 08/13] fix --- app/notifications/server/lib/Presence.ts | 2 +- typings.d.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/notifications/server/lib/Presence.ts b/app/notifications/server/lib/Presence.ts index 8e795f8e2725..8def18b8d8e0 100644 --- a/app/notifications/server/lib/Presence.ts +++ b/app/notifications/server/lib/Presence.ts @@ -1,6 +1,6 @@ import { DDPCommon } from 'meteor/ddp-common'; import { Emitter } from '@rocket.chat/emitter'; -import { Meteor } from 'meteor/meteor'; +import { Meteor, Subscription } from 'meteor/meteor'; import { IUser } from '../../../../definition/IUser'; diff --git a/typings.d.ts b/typings.d.ts index 2f202dc49b0a..ef72f152f5fc 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -26,3 +26,10 @@ declare module 'meteor/meteorhacks:inject-initial' { function rawBody(key: string, value: string): void; } } + +declare module 'meteor/ddp-common' { + namespace DDPCommon { + function stringifyDDP(msg: EJSON): string; + function parseDDP(msg: string): EJSON; + } +} From 7e95ad53d80794d3fd5a4136b070af8a4c753963 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 29 Sep 2021 00:13:20 -0300 Subject: [PATCH 09/13] Improve code to work with microservices --- app/notifications/client/lib/Notifications.js | 10 +-- app/notifications/client/lib/Presence.ts | 16 +--- app/notifications/server/lib/Presence.ts | 80 ++++++++++++------- client/lib/presence.ts | 2 +- ee/server/services/ddp-streamer/Streamer.ts | 4 + .../notifications/notifications.module.ts | 10 +-- server/modules/streamer/streamer.module.ts | 2 + 7 files changed, 64 insertions(+), 60 deletions(-) diff --git a/app/notifications/client/lib/Notifications.js b/app/notifications/client/lib/Notifications.js index e5db4a33a4dd..b3298765a3fa 100644 --- a/app/notifications/client/lib/Notifications.js +++ b/app/notifications/client/lib/Notifications.js @@ -19,7 +19,7 @@ class Notifications { this.streamRoom = new Meteor.Streamer('notify-room'); this.streamRoomUsers = new Meteor.Streamer('notify-room-users'); this.streamUser = new Meteor.Streamer('notify-user'); - this.streamPresence = new Meteor.Streamer('user-presence'); + // this.streamPresences = new Meteor.Streamer('user-presence'); if (this.debug === true) { this.onAll(function() { return console.log('RocketChat.Notifications: onAll', args); @@ -98,14 +98,6 @@ class Notifications { unUser(eventName, callback) { return this.streamUser.removeListener(`${ Meteor.userId() }/${ eventName }`, callback); } - - onUserPresence(uid, callback) { - return this.streamPresence.on(uid, callback); - } - - unUserPresence(uid, callback) { - return this.streamPresence.removeListener(uid, callback); - } } export default new Notifications(); diff --git a/app/notifications/client/lib/Presence.ts b/app/notifications/client/lib/Presence.ts index df5be2192984..a814b2336ef7 100644 --- a/app/notifications/client/lib/Presence.ts +++ b/app/notifications/client/lib/Presence.ts @@ -1,20 +1,8 @@ -import { Stream } from 'stream'; - -import { DDPCommon } from 'meteor/ddp-common'; import { Meteor } from 'meteor/meteor'; - import { Presence, STATUS_MAP } from '../../../../client/lib/presence'; -// Meteor.onConnection((connection) => { -(Meteor as unknown as { connection: Meteor.Connection & { _stream: Stream } }).connection._stream.on('message', (rawMsg: string) => { - const msg = DDPCommon.parseDDP(rawMsg); - if (msg.msg !== 'changed' || msg.collection !== 'stream-user-presences') { - return; - } - - const { uid, args } = msg.fields; - const [[username, status, statusText]] = args; +(Meteor as any).StreamerCentral.on('stream-user-presence', (uid, args) => { + const [username, status, statusText] = args; Presence.notify({ _id: uid, username, status: STATUS_MAP[status], statusText }); }); -// }); diff --git a/app/notifications/server/lib/Presence.ts b/app/notifications/server/lib/Presence.ts index 8def18b8d8e0..f427be65c4c8 100644 --- a/app/notifications/server/lib/Presence.ts +++ b/app/notifications/server/lib/Presence.ts @@ -1,15 +1,14 @@ -import { DDPCommon } from 'meteor/ddp-common'; import { Emitter } from '@rocket.chat/emitter'; -import { Meteor, Subscription } from 'meteor/meteor'; import { IUser } from '../../../../definition/IUser'; +import { IPublication, IStreamerConstructor, Connection, IStreamer } from '../../../../server/modules/streamer/streamer.module'; -type UserPresenseStreamProps = { +export type UserPresenseStreamProps = { added: IUser['_id'][]; removed: IUser['_id'][]; } -type UserPresenseStreamArgs = { +export type UserPresenseStreamArgs = { 'uid': string; args: unknown; } @@ -19,16 +18,20 @@ const e = new Emitter<{ }>(); -const clients = new WeakMap(); +const clients = new WeakMap(); -class UserPresence { - private readonly publication: Subscription; + +export class UserPresence { + private readonly streamer: IStreamer; + + private readonly publication: IPublication; private readonly listeners: Set; - constructor(publication: Subscription) { + constructor(publication: IPublication, streamer: IStreamer) { this.listeners = new Set(); this.publication = publication; + this.streamer = streamer; } listen(uid: string): void { @@ -45,42 +48,57 @@ class UserPresence { } run = (args: UserPresenseStreamArgs): void => { - (this.publication as any)._session.socket.send(DDPCommon.stringifyDDP({ - msg: 'changed', - collection: 'stream-user-presences', - id: args.uid, - fields: args, - })); + const payload = this.streamer.changedPayload(this.streamer.subscriptionName, args.uid, { ...args, eventName: args.uid }); // there is no good explanation to keep eventName, I just want to save one 'DDPCommon.parseDDP' on the client side, so I'm trying to fit the Meteor Streamer's payload + (this.publication as any)._session.socket.send(payload); } stop(): void { this.listeners.forEach(this.off); - clients.delete(this.publication.connection as Meteor.Connection); + clients.delete(this.publication.connection); + } + + static getClient(publication: IPublication, streamer: IStreamer): [UserPresence, boolean] { + const { connection } = publication; + const stored = clients.get(connection); + + const client = stored || new UserPresence(publication, streamer); + + const main = Boolean(!stored); + + clients.set(connection, client); + + return [client, main]; } } -Meteor.publish('streamer-user-presences', function({ added, removed }: UserPresenseStreamProps) { - const stored = clients.get(this.connection); +export class StreamPresence { + static getInstance(Streamer: IStreamerConstructor, name = 'user-presence'): IStreamer { + return new class StreamPresence extends Streamer { + async _publish(publication: IPublication, _eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + const { added, removed } = (typeof options !== 'boolean' ? options : {}) as unknown as UserPresenseStreamProps; - const client = stored || new UserPresence(this); - const main = Boolean(!stored); + const [client, main] = UserPresence.getClient(publication, this); - clients.set(this.connection, client); + added?.forEach((uid) => client.listen(uid)); + removed?.forEach((uid) => client.off(uid)); - added?.forEach((uid) => client.listen(uid)); - removed?.forEach((uid) => client.off(uid)); + if (!main) { + publication.stop(); + return; + } - if (!main) { - this.stop(); - return; - } + publication.ready(); + publication.connection.onClose(() => client.stop()); - this.ready(); - this.connection.onClose(() => client.stop()); + publication.onStop(() => client.stop()); + } + }(name); + } +} - this.onStop(() => client.stop()); -}); -export const emit = (uid: string, args: UserPresenseStreamArgs): void => e.emit(uid, { uid, args }); +export const emit = (uid: string, args: UserPresenseStreamArgs): void => { + e.emit(uid, { uid, args }); +}; diff --git a/client/lib/presence.ts b/client/lib/presence.ts index a680265099ba..5cf1ca4af297 100644 --- a/client/lib/presence.ts +++ b/client/lib/presence.ts @@ -71,7 +71,7 @@ const getPresence = ((): ((uid: UserPresence['_id']) => void) => { const ids = Array.from(currentUids); const removed = Array.from(deletedUids); - Meteor.subscribe('streamer-user-presences', { + Meteor.subscribe('stream-user-presence', '', { ...(ids.length > 0 && { added: Array.from(currentUids) }), ...(removed.length && { removed: Array.from(deletedUids) }), }); diff --git a/ee/server/services/ddp-streamer/Streamer.ts b/ee/server/services/ddp-streamer/Streamer.ts index 2932b52a0917..88e544e4c324 100644 --- a/ee/server/services/ddp-streamer/Streamer.ts +++ b/ee/server/services/ddp-streamer/Streamer.ts @@ -15,6 +15,10 @@ StreamerCentral.on('broadcast', (name, eventName, args) => { }); export class Stream extends Streamer { + static publish(name: string, fn: (eventName: string, options: boolean | {useCollection?: boolean; args?: any}) => void): void { + server.publish(name, fn); + } + registerPublication(name: string, fn: (eventName: string, options: boolean | {useCollection?: boolean; args?: any}) => void): void { server.publish(name, fn); } diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 448f90592691..64220e776bef 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -3,7 +3,7 @@ import { Authorization } from '../../sdk'; import { RoomsRaw } from '../../../app/models/server/raw/Rooms'; import { SubscriptionsRaw } from '../../../app/models/server/raw/Subscriptions'; import { ISubscription } from '../../../definition/ISubscription'; -import { emit } from '../../../app/notifications/server/lib/Presence'; +import { emit, StreamPresence } from '../../../app/notifications/server/lib/Presence'; import { UsersRaw } from '../../../app/models/server/raw/Users'; import { SettingsRaw } from '../../../app/models/server/raw/Settings'; import { IOmnichannelRoom } from '../../../definition/IRoom'; @@ -55,6 +55,7 @@ export class NotificationsModule { public readonly streamPresence: IStreamer; + constructor( private Streamer: IStreamerConstructor, ) { @@ -72,8 +73,9 @@ export class NotificationsModule { this.streamLivechatQueueData = new this.Streamer('livechat-inquiry-queue-observer'); this.streamStdout = new this.Streamer('stdout'); this.streamRoomData = new this.Streamer('room-data'); - - this.streamPresence = new this.Streamer('user-presence'); + this.streamPresence = StreamPresence.getInstance(Streamer, 'user-presence'); + this.streamPresence.allowRead('logged'); + this.streamPresence.allowWrite('none'); this.streamRoomMessage = new this.Streamer('room-messages'); @@ -167,8 +169,6 @@ export class NotificationsModule { this.streamLogged.allowWrite('none'); this.streamLogged.allowRead('logged'); - this.streamPresence.allowWrite('none'); - this.streamPresence.allowRead('logged'); this.streamRoom.allowRead(async function(eventName, extraData): Promise { const [rid] = eventName.split('/'); diff --git a/server/modules/streamer/streamer.module.ts b/server/modules/streamer/streamer.module.ts index 8498e4db9ace..4c8ae7f7d812 100644 --- a/server/modules/streamer/streamer.module.ts +++ b/server/modules/streamer/streamer.module.ts @@ -76,6 +76,8 @@ export interface IStreamer { emitWithoutBroadcast(event: string, ...data: any[]): void; changedPayload(collection: string, id: string, fields: Record): string | false; + + _publish(publication: IPublication, eventName: string, options: boolean | {useCollection?: boolean; args?: any}): Promise; } export interface IStreamerConstructor { From 08c648d4e00ae5bd4559646e932dd1be156cb7c5 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 29 Sep 2021 00:30:42 -0300 Subject: [PATCH 10/13] . --- app/notifications/client/lib/Presence.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/notifications/client/lib/Presence.ts b/app/notifications/client/lib/Presence.ts index a814b2336ef7..f3bb096d2dd0 100644 --- a/app/notifications/client/lib/Presence.ts +++ b/app/notifications/client/lib/Presence.ts @@ -1,8 +1,12 @@ import { Meteor } from 'meteor/meteor'; import { Presence, STATUS_MAP } from '../../../../client/lib/presence'; +import { UserStatus } from '../../../../definition/UserStatus'; -(Meteor as any).StreamerCentral.on('stream-user-presence', (uid, args) => { - const [username, status, statusText] = args; +(Meteor as any).StreamerCentral.on('stream-user-presence', (uid: string, args: unknown) => { + if (!Array.isArray(args)) { + throw new Error('Presence event must be an array'); + } + const [username, status, statusText] = args as [string, UserStatus, string | undefined]; Presence.notify({ _id: uid, username, status: STATUS_MAP[status], statusText }); }); From 2f46931fa4a7cf54ae476103cd48d815116f95e7 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 29 Sep 2021 00:58:50 -0300 Subject: [PATCH 11/13] .. --- app/notifications/client/lib/Presence.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/notifications/client/lib/Presence.ts b/app/notifications/client/lib/Presence.ts index f3bb096d2dd0..123b9ddb1934 100644 --- a/app/notifications/client/lib/Presence.ts +++ b/app/notifications/client/lib/Presence.ts @@ -1,12 +1,11 @@ import { Meteor } from 'meteor/meteor'; import { Presence, STATUS_MAP } from '../../../../client/lib/presence'; -import { UserStatus } from '../../../../definition/UserStatus'; (Meteor as any).StreamerCentral.on('stream-user-presence', (uid: string, args: unknown) => { if (!Array.isArray(args)) { throw new Error('Presence event must be an array'); } - const [username, status, statusText] = args as [string, UserStatus, string | undefined]; + const [username, status, statusText] = args as [string, number, string | undefined]; Presence.notify({ _id: uid, username, status: STATUS_MAP[status], statusText }); }); From 94335c662f0b0b1fdd51cab310ac52589defd58a Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 13 Oct 2021 16:48:36 -0300 Subject: [PATCH 12/13] Code cleanup --- app/notifications/client/lib/Notifications.js | 2 +- client/components/Message/StatusMessage.tsx | 2 +- client/hooks/usePresence.ts | 6 +++--- client/lib/presence.ts | 8 +++----- client/startup/index.ts | 1 - 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/app/notifications/client/lib/Notifications.js b/app/notifications/client/lib/Notifications.js index b3298765a3fa..15cd4374fe96 100644 --- a/app/notifications/client/lib/Notifications.js +++ b/app/notifications/client/lib/Notifications.js @@ -19,7 +19,7 @@ class Notifications { this.streamRoom = new Meteor.Streamer('notify-room'); this.streamRoomUsers = new Meteor.Streamer('notify-room-users'); this.streamUser = new Meteor.Streamer('notify-user'); - // this.streamPresences = new Meteor.Streamer('user-presence'); + if (this.debug === true) { this.onAll(function() { return console.log('RocketChat.Notifications: onAll', args); diff --git a/client/components/Message/StatusMessage.tsx b/client/components/Message/StatusMessage.tsx index 1704c8b7280f..ee034d94c2ed 100644 --- a/client/components/Message/StatusMessage.tsx +++ b/client/components/Message/StatusMessage.tsx @@ -7,7 +7,7 @@ import { usePresence } from '../../hooks/usePresence'; const StatusMessage = ({ uid }: { uid: string }): ReactElement | null => { const data = usePresence(uid); useEffect(() => { - process.env.NODE_ENV === 'development' && console.log('StatusMessage component is deprecated'); + process.env.NODE_ENV === 'development' && console.warn('StatusMessage component is deprecated'); }, [data]); if (!data || !data.statusText) { diff --git a/client/hooks/usePresence.ts b/client/hooks/usePresence.ts index c18768edeee5..e95a87059940 100644 --- a/client/hooks/usePresence.ts +++ b/client/hooks/usePresence.ts @@ -15,11 +15,11 @@ type Presence = 'online' | 'offline' | 'busy' | 'away' | 'loading'; export const usePresence = (uid: string): UserPresence | undefined => { const subscription = useMemo( () => ({ - getCurrentValue: (): UserPresence | undefined => Presence.store.get(uid), + getCurrentValue: (): UserPresence | undefined => (uid ? Presence.store.get(uid) : undefined), subscribe: (callback: any): any => { - Presence.listen(uid, callback); + uid && Presence.listen(uid, callback); return (): void => { - Presence.stop(uid, callback); + uid && Presence.stop(uid, callback); }; }, }), diff --git a/client/lib/presence.ts b/client/lib/presence.ts index 5cf1ca4af297..120fe7668c9a 100644 --- a/client/lib/presence.ts +++ b/client/lib/presence.ts @@ -1,7 +1,6 @@ import { Emitter, EventHandlerOf } from '@rocket.chat/emitter'; import { Meteor } from 'meteor/meteor'; -// import { Notifications } from '../../app/notifications/client'; import { APIClient } from '../../app/utils/client'; import { IUser } from '../../definition/IUser'; import { UserStatus } from '../../definition/UserStatus'; @@ -55,8 +54,6 @@ const notify = (presence: UserPresence): void => { } }; -// const subStream = new Map(); - const getPresence = ((): ((uid: UserPresence['_id']) => void) => { let timer: ReturnType; @@ -148,7 +145,9 @@ const listen = ( uid: UserPresence['_id'], handler: EventHandlerOf | (() => void), ): void => { - // emitter.on(uid, update); + if (!uid) { + return; + } emitter.on(uid, handler); const user = store.has(uid) && store.get(uid); @@ -181,7 +180,6 @@ const restart = (): void => { const get = async (uid: UserPresence['_id']): Promise => new Promise((resolve) => { const user = store.has(uid) && store.get(uid); - if (user) { return resolve(user); } diff --git a/client/startup/index.ts b/client/startup/index.ts index 87ff29b54f59..335692a28538 100644 --- a/client/startup/index.ts +++ b/client/startup/index.ts @@ -6,7 +6,6 @@ import './customTranslations'; import './e2e'; import './emailVerification'; import './i18n'; -// import './listenActiveUsers'; import './ldap'; import './loginViaQuery'; import './messageTypes'; From 44ea98ac55e8fe9057e93e232e1ee136b6e05276 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 14 Oct 2021 11:17:51 -0300 Subject: [PATCH 13/13] Cleaning up a bit more --- app/notifications/client/lib/Presence.ts | 2 ++ app/notifications/server/lib/Presence.ts | 1 - ee/server/services/ddp-streamer/Streamer.ts | 4 ---- server/modules/notifications/notifications.module.ts | 6 +++--- typings.d.ts | 7 ------- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/app/notifications/client/lib/Presence.ts b/app/notifications/client/lib/Presence.ts index 123b9ddb1934..2c222d28d643 100644 --- a/app/notifications/client/lib/Presence.ts +++ b/app/notifications/client/lib/Presence.ts @@ -2,6 +2,8 @@ import { Meteor } from 'meteor/meteor'; import { Presence, STATUS_MAP } from '../../../../client/lib/presence'; +// TODO implement API on Streamer to be able to listen to all streamed data +// this is a hacky way to listen to all streamed data from user-presense Streamer (Meteor as any).StreamerCentral.on('stream-user-presence', (uid: string, args: unknown) => { if (!Array.isArray(args)) { throw new Error('Presence event must be an array'); diff --git a/app/notifications/server/lib/Presence.ts b/app/notifications/server/lib/Presence.ts index f427be65c4c8..549c06af110e 100644 --- a/app/notifications/server/lib/Presence.ts +++ b/app/notifications/server/lib/Presence.ts @@ -90,7 +90,6 @@ export class StreamPresence { } publication.ready(); - publication.connection.onClose(() => client.stop()); publication.onStop(() => client.stop()); } diff --git a/ee/server/services/ddp-streamer/Streamer.ts b/ee/server/services/ddp-streamer/Streamer.ts index 88e544e4c324..2932b52a0917 100644 --- a/ee/server/services/ddp-streamer/Streamer.ts +++ b/ee/server/services/ddp-streamer/Streamer.ts @@ -15,10 +15,6 @@ StreamerCentral.on('broadcast', (name, eventName, args) => { }); export class Stream extends Streamer { - static publish(name: string, fn: (eventName: string, options: boolean | {useCollection?: boolean; args?: any}) => void): void { - server.publish(name, fn); - } - registerPublication(name: string, fn: (eventName: string, options: boolean | {useCollection?: boolean; args?: any}) => void): void { server.publish(name, fn); } diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 64220e776bef..da674daf8ca5 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -74,9 +74,6 @@ export class NotificationsModule { this.streamStdout = new this.Streamer('stdout'); this.streamRoomData = new this.Streamer('room-data'); this.streamPresence = StreamPresence.getInstance(Streamer, 'user-presence'); - this.streamPresence.allowRead('logged'); - this.streamPresence.allowWrite('none'); - this.streamRoomMessage = new this.Streamer('room-messages'); this.streamRoomMessage.on('_afterPublish', async (streamer: IStreamer, publication: IPublication, eventName: string): Promise => { @@ -415,6 +412,9 @@ export class NotificationsModule { this.streamLocal.allowRead('none'); this.streamLocal.allowEmit('all'); this.streamLocal.allowWrite('none'); + + this.streamPresence.allowRead('logged'); + this.streamPresence.allowWrite('none'); } notifyAll(eventName: string, ...args: any[]): void { diff --git a/typings.d.ts b/typings.d.ts index ef72f152f5fc..2f202dc49b0a 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -26,10 +26,3 @@ declare module 'meteor/meteorhacks:inject-initial' { function rawBody(key: string, value: string): void; } } - -declare module 'meteor/ddp-common' { - namespace DDPCommon { - function stringifyDDP(msg: EJSON): string; - function parseDDP(msg: string): EJSON; - } -}