diff --git a/.changeset/quick-emus-march.md b/.changeset/quick-emus-march.md new file mode 100644 index 00000000000..7a6d7b44465 --- /dev/null +++ b/.changeset/quick-emus-march.md @@ -0,0 +1,7 @@ +--- +'@rocket.chat/ddp-client': minor +'@rocket.chat/core-services': minor +'@rocket.chat/meteor': minor +--- + +Add new event to notify users directly about new banners diff --git a/apps/meteor/client/views/banners/hooks/useRemoteBanners.ts b/apps/meteor/client/views/banners/hooks/useRemoteBanners.ts index fdad9038984..ff42d4ae9ac 100644 --- a/apps/meteor/client/views/banners/hooks/useRemoteBanners.ts +++ b/apps/meteor/client/views/banners/hooks/useRemoteBanners.ts @@ -11,6 +11,7 @@ export const useRemoteBanners = () => { const serverContext = useContext(ServerContext); const getBanners = useEndpoint('GET', '/v1/banners'); const subscribeToNotifyLoggedIn = useStream('notify-logged'); + const subscribeToNotifyUser = useStream('notify-user'); useEffect(() => { if (!uid) { @@ -63,11 +64,17 @@ export const useRemoteBanners = () => { }); }); + const unsubscribeBanners = subscribeToNotifyUser(`${uid}/banners`, async (banner) => { + banners.open(banner.view); + }); + return () => { controller.abort(); unsubscribeFromBannerChanged(); + unsubscribeBanners(); + banners.clear(); }; - }, [getBanners, serverContext, subscribeToNotifyLoggedIn, uid]); + }, [getBanners, serverContext, subscribeToNotifyLoggedIn, uid, subscribeToNotifyUser]); }; diff --git a/apps/meteor/server/modules/listeners/listeners.module.ts b/apps/meteor/server/modules/listeners/listeners.module.ts index 96fa50e37ff..973b542bf54 100644 --- a/apps/meteor/server/modules/listeners/listeners.module.ts +++ b/apps/meteor/server/modules/listeners/listeners.module.ts @@ -337,13 +337,19 @@ export class ListenersModule { }); }); + service.onEvent('banner.user', (userId, banner): void => { + notifications.notifyUserInThisInstance(userId, 'banners', banner); + }); + service.onEvent('banner.new', (bannerId): void => { notifications.notifyLoggedInThisInstance('new-banner', { bannerId }); // deprecated notifications.notifyLoggedInThisInstance('banner-changed', { bannerId }); }); + service.onEvent('banner.disabled', (bannerId): void => { notifications.notifyLoggedInThisInstance('banner-changed', { bannerId }); }); + service.onEvent('banner.enabled', (bannerId): void => { notifications.notifyLoggedInThisInstance('banner-changed', { bannerId }); }); diff --git a/apps/meteor/server/services/banner/service.ts b/apps/meteor/server/services/banner/service.ts index 56e5bc5a3ee..4dc0dbbec49 100644 --- a/apps/meteor/server/services/banner/service.ts +++ b/apps/meteor/server/services/banner/service.ts @@ -42,7 +42,7 @@ export class BannerService extends ServiceClassInternal implements IBannerServic throw new Error('error-creating-banner'); } - void api.broadcast('banner.new', banner._id); + void this.sendToUsers(banner); return banner; } @@ -123,9 +123,38 @@ export class BannerService extends ServiceClassInternal implements IBannerServic const { _id, ...banner } = result; - await Banners.updateOne({ _id }, { $set: { ...banner, ...doc, active: true } }); // reenable the banner + const newBanner = { ...banner, ...doc, active: true }; + + await Banners.updateOne({ _id }, { $set: newBanner }); // reenable the banner + + void this.sendToUsers({ _id, ...newBanner }); + + return true; + } + + async sendToUsers(banner: IBanner): Promise { + if (!banner.active) { + return false; + } + + // no roles set, so it should be sent to all users + if (!banner.roles?.length) { + void api.broadcast('banner.enabled', banner._id); + return true; + } + + const total = await Users.countActiveUsersInRoles(banner.roles); + + // if more than 100 users should receive the banner, send it to all users + if (total > 100) { + void api.broadcast('banner.enabled', banner._id); + return true; + } + + await Users.findActiveUsersInRoles(banner.roles, { projection: { _id: 1 } }).forEach((user) => { + void api.broadcast('banner.user', user._id, banner); + }); - void api.broadcast('banner.enabled', bannerId); return true; } } diff --git a/ee/packages/ddp-client/src/types/streams.ts b/ee/packages/ddp-client/src/types/streams.ts index ec307faab23..7b86ad557c5 100644 --- a/ee/packages/ddp-client/src/types/streams.ts +++ b/ee/packages/ddp-client/src/types/streams.ts @@ -21,6 +21,7 @@ import type { ICalendarNotification, IUserStatus, ILivechatInquiryRecord, + IBanner, } from '@rocket.chat/core-typings'; type ClientAction = 'inserted' | 'updated' | 'removed' | 'changed'; @@ -165,6 +166,7 @@ export interface StreamerEvents { ]; }, { key: `${string}/calendar`; args: [ICalendarNotification] }, + { key: `${string}/banners`; args: [IBanner] }, ]; 'importers': [ diff --git a/packages/core-services/src/Events.ts b/packages/core-services/src/Events.ts index 838e60f8471..acefa0b423c 100644 --- a/packages/core-services/src/Events.ts +++ b/packages/core-services/src/Events.ts @@ -30,6 +30,7 @@ import type { ICalendarNotification, AtLeast, ILivechatInquiryRecord, + IBanner, } from '@rocket.chat/core-typings'; import type { AutoUpdateRecord } from './types/IMeteor'; @@ -48,6 +49,7 @@ export type EventSignatures = { 'banner.new'(bannerId: string): void; 'banner.enabled'(bannerId: string): void; 'banner.disabled'(bannerId: string): void; + 'banner.user'(userId: string, banner: IBanner): void; 'emoji.deleteCustom'(emoji: IEmoji): void; 'emoji.updateCustom'(emoji: IEmoji): void; 'license.module'(data: { module: string; valid: boolean }): void;