From 448d44120309077e0933a8f8a1ae16c198942448 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Fri, 27 Jun 2025 19:58:20 -0300 Subject: [PATCH 01/33] chore: adapt Rooms store: `state.get` --- apps/meteor/app/e2e/client/rocketchat.e2e.room.ts | 4 ++-- apps/meteor/app/e2e/client/rocketchat.e2e.ts | 11 ++++++----- apps/meteor/app/lib/client/methods/sendMessage.ts | 2 +- .../app/reactions/client/methods/setReaction.ts | 4 ++-- apps/meteor/app/slashcommands-topic/client/topic.ts | 2 +- apps/meteor/app/webrtc/client/actionLink.tsx | 4 ++-- apps/meteor/client/lib/chats/data.ts | 4 ++-- apps/meteor/client/lib/getPermaLink.ts | 4 +--- apps/meteor/client/lib/rooms/roomCoordinator.tsx | 11 +++-------- apps/meteor/client/lib/rooms/roomTypes/direct.ts | 2 +- apps/meteor/client/lib/rooms/roomTypes/livechat.ts | 6 +++--- apps/meteor/client/lib/rooms/roomTypes/voip.ts | 2 +- apps/meteor/client/lib/utils/getUidDirectMessage.ts | 2 +- apps/meteor/client/lib/utils/legacyJumpToMessage.ts | 2 +- apps/meteor/client/startup/e2e.ts | 2 +- 15 files changed, 28 insertions(+), 34 deletions(-) diff --git a/apps/meteor/app/e2e/client/rocketchat.e2e.room.ts b/apps/meteor/app/e2e/client/rocketchat.e2e.room.ts index 19a0cf3b13473..f6dbebccefd3c 100644 --- a/apps/meteor/app/e2e/client/rocketchat.e2e.room.ts +++ b/apps/meteor/app/e2e/client/rocketchat.e2e.room.ts @@ -336,8 +336,8 @@ export class E2ERoom extends Emitter { try { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const room = Rooms.findOne({ _id: this.roomId })!; - if (!room.e2eKeyId) { + const room = Rooms.state.get(this.roomId); + if (!room?.e2eKeyId) { this.setState(E2ERoomState.CREATING_KEYS); await this.createGroupKey(); this.setState(E2ERoomState.READY); diff --git a/apps/meteor/app/e2e/client/rocketchat.e2e.ts b/apps/meteor/app/e2e/client/rocketchat.e2e.ts index 118e380d880eb..3fc07db91cc7b 100644 --- a/apps/meteor/app/e2e/client/rocketchat.e2e.ts +++ b/apps/meteor/app/e2e/client/rocketchat.e2e.ts @@ -255,7 +255,7 @@ class E2E extends Emitter { } async getInstanceByRoomId(rid: IRoom['_id']): Promise { - const room = await waitUntilFind(() => Rooms.findOne({ _id: rid })); + const room = await waitUntilFind(() => Rooms.state.get(rid)); if (room.t !== 'd' && room.t !== 'p') { return null; @@ -846,11 +846,12 @@ class E2E extends Emitter { return; } + const predicate = (record: IRoom) => + Boolean('usersWaitingForE2EKeys' in record && record.usersWaitingForE2EKeys?.every((user) => user.userId !== Meteor.userId())); + const keyDistribution = async () => { - const roomIds = Rooms.find({ - 'usersWaitingForE2EKeys': { $exists: true }, - 'usersWaitingForE2EKeys.userId': { $ne: Meteor.userId() }, - }).map((room) => room._id); + const roomIds = Rooms.state.filter(predicate).map((room) => room._id); + if (!roomIds.length) { return; } diff --git a/apps/meteor/app/lib/client/methods/sendMessage.ts b/apps/meteor/app/lib/client/methods/sendMessage.ts index 486a0528a5095..237dab28c7b2e 100644 --- a/apps/meteor/app/lib/client/methods/sendMessage.ts +++ b/apps/meteor/app/lib/client/methods/sendMessage.ts @@ -36,7 +36,7 @@ Meteor.methods({ } // If the room is federated, send the message to matrix only - const room = Rooms.findOne({ _id: message.rid }, { fields: { federated: 1, name: 1 } }); + const room = Rooms.state.get(message.rid); if (room?.federated) { return; } diff --git a/apps/meteor/app/reactions/client/methods/setReaction.ts b/apps/meteor/app/reactions/client/methods/setReaction.ts index 10dfce236765f..0af53d91df5c1 100644 --- a/apps/meteor/app/reactions/client/methods/setReaction.ts +++ b/apps/meteor/app/reactions/client/methods/setReaction.ts @@ -1,4 +1,4 @@ -import type { IMessage, IRoom } from '@rocket.chat/core-typings'; +import type { IMessage } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; @@ -23,7 +23,7 @@ Meteor.methods({ return false; } - const room: IRoom | undefined = Rooms.findOne({ _id: message.rid }); + const room = Rooms.state.get(message.rid); if (!room) { return false; } diff --git a/apps/meteor/app/slashcommands-topic/client/topic.ts b/apps/meteor/app/slashcommands-topic/client/topic.ts index bc32cacd65bd8..4f04b565658e3 100644 --- a/apps/meteor/app/slashcommands-topic/client/topic.ts +++ b/apps/meteor/app/slashcommands-topic/client/topic.ts @@ -13,7 +13,7 @@ slashCommands.add({ if (hasPermission('edit-room', message.rid)) { try { await sdk.call('saveRoomSettings', message.rid, 'roomTopic', params); - await callbacks.run('roomTopicChanged', Rooms.findOne(message.rid)); + await callbacks.run('roomTopicChanged', Rooms.state.get(message.rid)); } catch (error: unknown) { dispatchToastMessage({ type: 'error', message: error }); throw error; diff --git a/apps/meteor/app/webrtc/client/actionLink.tsx b/apps/meteor/app/webrtc/client/actionLink.tsx index 90258eeedce8e..21233d0ff95cd 100644 --- a/apps/meteor/app/webrtc/client/actionLink.tsx +++ b/apps/meteor/app/webrtc/client/actionLink.tsx @@ -7,7 +7,7 @@ import { sdk } from '../../utils/client/lib/SDKClient'; import { t } from '../../utils/lib/i18n'; actionLinks.register('joinLivechatWebRTCCall', (message: IMessage) => { - const room = Rooms.findOne({ _id: message.rid }); + const room = Rooms.state.get(message.rid); if (!room) { throw new Error('Room not found'); } @@ -20,7 +20,7 @@ actionLinks.register('joinLivechatWebRTCCall', (message: IMessage) => { }); actionLinks.register('endLivechatWebRTCCall', async (message: IMessage) => { - const room = Rooms.findOne({ _id: message.rid }); + const room = Rooms.state.get(message.rid); if (!room) { throw new Error('Room not found'); } diff --git a/apps/meteor/client/lib/chats/data.ts b/apps/meteor/client/lib/chats/data.ts index f53f956b93b8f..85f4366d50c91 100644 --- a/apps/meteor/client/lib/chats/data.ts +++ b/apps/meteor/client/lib/chats/data.ts @@ -245,7 +245,7 @@ export const createDataAPI = ({ rid, tmid }: { rid: IRoom['_id']; tmid: IMessage drafts.set(mid, draft); }; - const findRoom = async (): Promise => Rooms.findOne({ _id: rid }, { reactive: false }); + const findRoom = async (): Promise => Rooms.state.get(rid); const getRoom = async (): Promise => { const room = await findRoom(); @@ -264,7 +264,7 @@ export const createDataAPI = ({ rid, tmid }: { rid: IRoom['_id']; tmid: IMessage }; const findDiscussionByID = async (drid: IRoom['_id']): Promise => - Rooms.findOne({ _id: drid, prid: { $exists: true } }, { reactive: false }); + Rooms.state.find((record) => Boolean(record._id === drid && record.prid)); const getDiscussionByID = async (drid: IRoom['_id']): Promise => { const discussion = await findDiscussionByID(drid); diff --git a/apps/meteor/client/lib/getPermaLink.ts b/apps/meteor/client/lib/getPermaLink.ts index 3c0def1459137..cece6e9e549fb 100644 --- a/apps/meteor/client/lib/getPermaLink.ts +++ b/apps/meteor/client/lib/getPermaLink.ts @@ -22,9 +22,7 @@ export const getPermaLink = async (msgId: string): Promise => { if (!msg) { throw new Error('message-not-found'); } - const roomData = Rooms.findOne({ - _id: msg.rid, - }); + const roomData = Rooms.state.get(msg.rid); if (!roomData) { throw new Error('room-not-found'); diff --git a/apps/meteor/client/lib/rooms/roomCoordinator.tsx b/apps/meteor/client/lib/rooms/roomCoordinator.tsx index 07fe60b624025..31b421168d624 100644 --- a/apps/meteor/client/lib/rooms/roomCoordinator.tsx +++ b/apps/meteor/client/lib/rooms/roomCoordinator.tsx @@ -112,12 +112,7 @@ class RoomCoordinatorClient extends RoomCoordinator { } public readOnly(rid: string, user: AtLeast): boolean { - const fields = { - ro: 1, - t: 1, - ...(user && { muted: 1, unmuted: 1 }), - }; - const room = Rooms.findOne({ _id: rid }, { fields }); + const room = Rooms.state.get(rid); if (!room) { return false; } @@ -156,12 +151,12 @@ class RoomCoordinatorClient extends RoomCoordinator { // #ToDo: Move this out of the RoomCoordinator public archived(rid: string): boolean { - const room = Rooms.findOne({ _id: rid }, { fields: { archived: 1 } }); + const room = Rooms.state.get(rid); return Boolean(room?.archived); } public verifyCanSendMessage(rid: string): boolean { - const room = Rooms.findOne({ _id: rid }, { fields: { t: 1, federated: 1 } }); + const room = Rooms.state.get(rid); if (!room?.t) { return false; } diff --git a/apps/meteor/client/lib/rooms/roomTypes/direct.ts b/apps/meteor/client/lib/rooms/roomTypes/direct.ts index 6ddff21e73202..d6a2aa1087b1d 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/direct.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/direct.ts @@ -153,7 +153,7 @@ roomCoordinator.add( const subscription = Subscriptions.findOne(query); if (subscription?.rid) { - return Rooms.findOne(subscription.rid); + return Rooms.state.get(subscription.rid); } }, } as AtLeast, diff --git a/apps/meteor/client/lib/rooms/roomTypes/livechat.ts b/apps/meteor/client/lib/rooms/roomTypes/livechat.ts index 4cede49f0065b..056f4b45deafa 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/livechat.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/livechat.ts @@ -54,7 +54,7 @@ roomCoordinator.add( }, findRoom(identifier) { - return Rooms.findOne({ _id: identifier }); + return Rooms.state.get(identifier); }, isLivechatRoom() { @@ -62,12 +62,12 @@ roomCoordinator.add( }, canSendMessage(rid) { - const room = Rooms.findOne({ _id: rid }, { fields: { open: 1 } }); + const room = Rooms.state.get(rid); return Boolean(room?.open); }, readOnly(rid, _user) { - const room = Rooms.findOne({ _id: rid }, { fields: { open: 1, servedBy: 1 } }); + const room = Rooms.state.get(rid); if (!room?.open) { return true; } diff --git a/apps/meteor/client/lib/rooms/roomTypes/voip.ts b/apps/meteor/client/lib/rooms/roomTypes/voip.ts index 4777aa13f210e..cd5252ba01526 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/voip.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/voip.ts @@ -29,7 +29,7 @@ roomCoordinator.add( }, findRoom(identifier) { - return Rooms.findOne({ _id: identifier }); + return Rooms.state.get(identifier); }, canSendMessage(_rid) { diff --git a/apps/meteor/client/lib/utils/getUidDirectMessage.ts b/apps/meteor/client/lib/utils/getUidDirectMessage.ts index 6073de5f53c52..9b2241d66fffb 100644 --- a/apps/meteor/client/lib/utils/getUidDirectMessage.ts +++ b/apps/meteor/client/lib/utils/getUidDirectMessage.ts @@ -4,7 +4,7 @@ import { Meteor } from 'meteor/meteor'; import { Rooms } from '../../../app/models/client'; export const getUidDirectMessage = (rid: IRoom['_id'], uid: IUser['_id'] | null = Meteor.userId()): string | undefined => { - const room = Rooms.findOne({ _id: rid }, { fields: { t: 1, uids: 1 } }); + const room = Rooms.state.get(rid); if (!room || room.t !== 'd' || !room.uids || room.uids.length > 2) { return undefined; diff --git a/apps/meteor/client/lib/utils/legacyJumpToMessage.ts b/apps/meteor/client/lib/utils/legacyJumpToMessage.ts index 8dc2e2fdde319..76a4d044bc8a0 100644 --- a/apps/meteor/client/lib/utils/legacyJumpToMessage.ts +++ b/apps/meteor/client/lib/utils/legacyJumpToMessage.ts @@ -22,7 +22,7 @@ export const legacyJumpToMessage = async (message: IMessage) => { tab: 'thread', context: message.tmid || message._id, rid: message.rid, - name: Rooms.findOne({ _id: message.rid })?.name ?? '', + name: Rooms.state.get(message.rid)?.name ?? '', }, search: { ...router.getSearchParameters(), diff --git a/apps/meteor/client/startup/e2e.ts b/apps/meteor/client/startup/e2e.ts index 3f439d99a6968..1804b2a66c894 100644 --- a/apps/meteor/client/startup/e2e.ts +++ b/apps/meteor/client/startup/e2e.ts @@ -79,7 +79,7 @@ Meteor.startup(() => { return message; } - const subscription = await waitUntilFind(() => Rooms.findOne({ _id: message.rid })); + const subscription = await waitUntilFind(() => Rooms.state.get(message.rid)); subscription.encrypted ? e2eRoom.resume() : e2eRoom.pause(); From 3a925463cbf7e143b1d3cfaec35861ea60e7d90e Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Fri, 27 Jun 2025 20:00:40 -0300 Subject: [PATCH 02/33] chore: adapt Rooms store: `state.find` --- apps/meteor/client/lib/rooms/roomTypes/private.ts | 9 +++------ apps/meteor/client/lib/rooms/roomTypes/public.ts | 12 +++++------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/apps/meteor/client/lib/rooms/roomTypes/private.ts b/apps/meteor/client/lib/rooms/roomTypes/private.ts index 9da3e8916f1a2..0c1eb49079df4 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/private.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/private.ts @@ -1,7 +1,6 @@ import type { AtLeast, IRoom } from '@rocket.chat/core-typings'; import { isRoomFederated } from '@rocket.chat/core-typings'; import { Meteor } from 'meteor/meteor'; -import type { Filter } from 'mongodb'; import { hasPermission } from '../../../../app/authorization/client'; import { Rooms } from '../../../../app/models/client'; @@ -110,12 +109,10 @@ roomCoordinator.add( }, findRoom(identifier) { - const query: Filter = { - t: 'p', - name: identifier, + const predicate = (record: IRoom): boolean => { + return record.t === 'p' && record.name === identifier; }; - - return Rooms.findOne(query); + return Rooms.state.find(predicate); }, } as AtLeast, ); diff --git a/apps/meteor/client/lib/rooms/roomTypes/public.ts b/apps/meteor/client/lib/rooms/roomTypes/public.ts index 07c3ab19e01fd..4b5b6d62e4c7c 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/public.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/public.ts @@ -1,7 +1,6 @@ import type { AtLeast, IRoom } from '@rocket.chat/core-typings'; import { isRoomFederated } from '@rocket.chat/core-typings'; import { Meteor } from 'meteor/meteor'; -import type { Filter } from 'mongodb'; import { hasAtLeastOnePermission } from '../../../../app/authorization/client'; import { Rooms } from '../../../../app/models/client'; @@ -109,16 +108,15 @@ roomCoordinator.add( }, findRoom(identifier) { - const query: Filter = { - t: 'c', - name: identifier, + const predicate = (record: IRoom): boolean => { + return record.t === 'c' && record.name === identifier; }; - - return Rooms.findOne(query); + return Rooms.state.find(predicate); }, showJoinLink(roomId) { - return !!Rooms.findOne({ _id: roomId, t: 'c' }); + const predicate = (record: IRoom): boolean => record.t === 'c' && record._id === roomId; + return !!Rooms.state.find(predicate); }, } as AtLeast, ); From e89cbdf3d5326718118a0aa31ce53d42aa5dfbcf Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Fri, 27 Jun 2025 20:01:05 -0300 Subject: [PATCH 03/33] chore: adapt Rooms store in `useOpenRoom` --- .../client/views/room/hooks/useOpenRoom.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/meteor/client/views/room/hooks/useOpenRoom.ts b/apps/meteor/client/views/room/hooks/useOpenRoom.ts index 872f918786543..48d8dd8dca1e9 100644 --- a/apps/meteor/client/views/room/hooks/useOpenRoom.ts +++ b/apps/meteor/client/views/room/hooks/useOpenRoom.ts @@ -1,4 +1,5 @@ import { isPublicRoom, type IRoom, type RoomType } from '@rocket.chat/core-typings'; +import { getObjectKeys } from '@rocket.chat/tools'; import { useMethod, usePermission, useRoute, useSetting, useUser } from '@rocket.chat/ui-contexts'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useEffect } from 'react'; @@ -57,21 +58,20 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st throw new RoomNotFoundError(undefined, { type, reference }); } - const $set: any = {}; - const $unset: any = {}; - - for (const key of Object.keys(roomFields)) { - if (key in roomData) { - $set[key] = roomData[key as keyof typeof roomData]; - } else { - $unset[key] = ''; - } - } - const { Rooms, Subscriptions } = await import('../../../../app/models/client'); - Rooms.upsert({ _id: roomData._id }, { $set, $unset }); - const room = Rooms.findOne({ _id: roomData._id }); + Rooms.state.update( + (record) => record._id === roomData._id, + (record) => { + const unsetKeys = getObjectKeys(roomFields).filter((key) => key in roomData) as (keyof IRoom)[]; + unsetKeys.forEach((key) => { + delete record[key]; + }); + return { ...record, ...roomData }; + }, + ); + + const room = Rooms.state.get(roomData._id); if (!room) { throw new TypeError('room is undefined'); @@ -118,7 +118,7 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st useEffect(() => { if (error) { if (['l', 'v'].includes(type) && error instanceof RoomNotFoundError) { - Rooms.remove(reference); + Rooms.state.remove((record) => Object.values(record).includes(reference)); queryClient.removeQueries({ queryKey: ['rooms', reference] }); queryClient.removeQueries({ queryKey: roomsQueryKeys.info(reference) }); } From a5131a9d905a6fc9be4b1bd03e4a42bcee15fada Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Fri, 27 Jun 2025 20:01:39 -0300 Subject: [PATCH 04/33] chore: adapt Rooms store in `RealAppsEngineUIHost` --- apps/meteor/client/apps/RealAppsEngineUIHost.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/meteor/client/apps/RealAppsEngineUIHost.ts b/apps/meteor/client/apps/RealAppsEngineUIHost.ts index 7125fd04e8b47..85f024561de95 100644 --- a/apps/meteor/client/apps/RealAppsEngineUIHost.ts +++ b/apps/meteor/client/apps/RealAppsEngineUIHost.ts @@ -30,7 +30,11 @@ export class RealAppsEngineUIHost extends AppsEngineUIHost { } async getClientRoomInfo(): Promise { - const { name: slugifiedName, _id: id } = Rooms.findOne(RoomManager.opened)!; + const room = RoomManager.opened ? Rooms.state.get(RoomManager.opened) : undefined; + if (!room) { + throw new Error('Room not found'); + } + const { name: slugifiedName, _id: id } = room; let cachedMembers: IExternalComponentUserInfo[] = []; try { From 45dac6338138e83b07fef6f98ac0219681990e4b Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Fri, 27 Jun 2025 20:02:32 -0300 Subject: [PATCH 05/33] chore: `createDocumentMapStore` in `Rooms` model --- apps/meteor/app/models/client/models/Rooms.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/models/client/models/Rooms.ts b/apps/meteor/app/models/client/models/Rooms.ts index 91328881c432e..f68cf73f63de7 100644 --- a/apps/meteor/app/models/client/models/Rooms.ts +++ b/apps/meteor/app/models/client/models/Rooms.ts @@ -1,4 +1,13 @@ +import type { IRoom } from '@rocket.chat/core-typings'; +import type { StoreApi, UseBoundStore } from 'zustand'; + import { CachedChatRoom } from './CachedChatRoom'; +import type { IDocumentMapStore } from '../../../../client/lib/cachedCollections/DocumentMapStore'; + +type RoomsStore = { + use: UseBoundStore>>; + readonly state: IDocumentMapStore; +}; /** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ -export const Rooms = CachedChatRoom.collection; +export const Rooms = CachedChatRoom.collection as RoomsStore; From f0f8a4fb8bbc4435614dbf628cf2f12ced4849e0 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Mon, 30 Jun 2025 16:06:32 -0300 Subject: [PATCH 06/33] chore: create `applyQueryOptions` in pipe --- .../client/lib/cachedCollections/pipe.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/apps/meteor/client/lib/cachedCollections/pipe.ts b/apps/meteor/client/lib/cachedCollections/pipe.ts index 130691e46c1dd..3a512dde451b7 100644 --- a/apps/meteor/client/lib/cachedCollections/pipe.ts +++ b/apps/meteor/client/lib/cachedCollections/pipe.ts @@ -1,3 +1,5 @@ +import type { FindOptions } from '@rocket.chat/ui-contexts/dist/UserContext'; + interface IPipeReturn { slice(skip: number, limit: number): IPipeReturn; sortByField(fieldName: keyof D, direction?: 1 | -1): IPipeReturn; @@ -69,3 +71,22 @@ export function pipe( }, }; } + +export const applyQueryOptions = (records: T[], options: FindOptions): T[] => { + let currentPipeline = pipe(records); + if (options.sort && options.sort.length > 0) { + for (let i = options.sort.length - 1; i >= 0; i--) { + const { field, direction } = options.sort[i]; + currentPipeline = currentPipeline.sortByField(field, direction); + } + } + if (options.skip) { + currentPipeline = currentPipeline.slice(options.skip, records.length); + } + if (options.limit !== undefined) { + // If skip was applied, limit will be applied on the already skipped array + // If no skip, it will be applied from the beginning. + currentPipeline = currentPipeline.slice(0, options.limit); + } + return currentPipeline.apply(); +}; From f5f36a4192ac7eb07d429e4a0332d50a4571c01f Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Tue, 1 Jul 2025 08:19:56 -0300 Subject: [PATCH 07/33] chore: adjust Rooms store usage in `useRoomQuery` --- .../views/room/providers/hooks/useRoomQuery.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts b/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts index 09939ae454a15..eec603bd02c8a 100644 --- a/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts +++ b/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts @@ -29,15 +29,11 @@ export function useRoomQuery( const { refetch } = queryResult; useEffect(() => { - const liveQueryHandle = Rooms.find({ _id: rid }).observe({ - added: () => refetch(), - changed: () => refetch(), - removed: () => refetch(), + Rooms.use.subscribe((state, prevState) => { + if (state.records.size !== prevState.records.size) { + refetch(); + } }); - - return () => { - liveQueryHandle.stop(); - }; }, [refetch, rid]); return queryResult; From 05a8ad8f63ef1a93641e41158a0e04274995e679 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Tue, 1 Jul 2025 09:32:19 -0300 Subject: [PATCH 08/33] chore: adjust Rooms store usage in `UserProvider` and create `convertSort` --- .../client/lib/cachedCollections/pipe.ts | 41 +++++++++++++++++-- .../providers/UserProvider/UserProvider.tsx | 23 +++++++++-- packages/ui-contexts/src/UserContext.ts | 2 + 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/apps/meteor/client/lib/cachedCollections/pipe.ts b/apps/meteor/client/lib/cachedCollections/pipe.ts index 3a512dde451b7..d893763017b0d 100644 --- a/apps/meteor/client/lib/cachedCollections/pipe.ts +++ b/apps/meteor/client/lib/cachedCollections/pipe.ts @@ -72,11 +72,44 @@ export function pipe( }; } -export const applyQueryOptions = (records: T[], options: FindOptions): T[] => { +type OriginalStructure = FindOptions['sort']; + +type SortField = 'lm' | 'lowerCaseFName' | 'lowerCaseName'; +type SortDirection = -1 | 1; + +type SortObject = { + field: SortField; + direction: SortDirection; +}[]; + +/** + * Converts a MongoDB-style sort structure to a sort object. + */ +export const convertSort = (original: OriginalStructure): SortObject => { + const convertedSort: SortObject = []; + + if (!original) { + return convertedSort; + } + Object.keys(original as Record).forEach((key) => { + const direction = original[key as keyof OriginalStructure]; + + if (direction === -1 || direction === 1) { + convertedSort.push({ + field: key as SortField, + direction, + }); + } + }); + return convertedSort; +}; + +export const applyQueryOptions = >(records: T[], options: FindOptions): T[] => { let currentPipeline = pipe(records); - if (options.sort && options.sort.length > 0) { - for (let i = options.sort.length - 1; i >= 0; i--) { - const { field, direction } = options.sort[i]; + if (options.sort) { + const sortObj = convertSort(options.sort); + for (let i = sortObj.sort.length - 1; i >= 0; i--) { + const { field, direction } = sortObj[i]; currentPipeline = currentPipeline.sortByField(field, direction); } } diff --git a/apps/meteor/client/providers/UserProvider/UserProvider.tsx b/apps/meteor/client/providers/UserProvider/UserProvider.tsx index fd3463434de78..28f7fd3783ced 100644 --- a/apps/meteor/client/providers/UserProvider/UserProvider.tsx +++ b/apps/meteor/client/providers/UserProvider/UserProvider.tsx @@ -1,6 +1,7 @@ import type { IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; import { Emitter } from '@rocket.chat/emitter'; import { useLocalStorage } from '@rocket.chat/fuselage-hooks'; +import { createPredicateFromFilter } from '@rocket.chat/mongo-adapter'; import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts'; import { UserContext, useEndpoint, useRouteParameter, useSearchParameter } from '@rocket.chat/ui-contexts'; import { useQueryClient } from '@tanstack/react-query'; @@ -20,6 +21,7 @@ import { sdk } from '../../../app/utils/client/lib/SDKClient'; import { afterLogoutCleanUpCallback } from '../../../lib/callbacks/afterLogoutCleanUpCallback'; import { useIdleConnection } from '../../hooks/useIdleConnection'; import { useReactiveValue } from '../../hooks/useReactiveValue'; +import { applyQueryOptions } from '../../lib/cachedCollections/pipe'; import { createReactiveSubscriptionFactory } from '../../lib/createReactiveSubscriptionFactory'; import { useSamlInviteToken } from '../../views/invite/hooks/useSamlInviteToken'; @@ -74,13 +76,28 @@ const UserProvider = ({ children }: UserProviderProps): ReactElement => { querySubscription: createReactiveSubscriptionFactory((query, fields, sort) => Subscriptions.findOne(query, { fields, sort }), ), - queryRoom: createReactiveSubscriptionFactory((query, fields) => Rooms.findOne(query, { fields })), + queryRoom: createReactiveSubscriptionFactory((query) => { + const predicate = createPredicateFromFilter(query); + return Rooms.state.find(predicate); + }), querySubscriptions: createReactiveSubscriptionFactory((query, options) => { if (userId) { - return Subscriptions.find(query, options).fetch(); + const predicate = createPredicateFromFilter(query); + const subs = Subscriptions.state.filter(predicate); + + if (options?.sort || options?.limit || options?.skip) { + return applyQueryOptions(subs, options); + } + return subs; } + // Anonnymous users don't have subscriptions. Fetch Rooms instead. + const predicate = createPredicateFromFilter(query); + const rooms = Rooms.state.filter(predicate) as unknown as SubscriptionWithRoom[]; // FIXME: this is a hack to make the typings work. How was it working before? - return Rooms.find(query, options).fetch(); + if (options?.sort || options?.limit) { + return applyQueryOptions(rooms, options); + } + return rooms; }), logout: async () => Meteor.logout(), onLogout: (cb) => { diff --git a/packages/ui-contexts/src/UserContext.ts b/packages/ui-contexts/src/UserContext.ts index 81a00126de662..82351822330ae 100644 --- a/packages/ui-contexts/src/UserContext.ts +++ b/packages/ui-contexts/src/UserContext.ts @@ -24,6 +24,8 @@ export type Sort = Exclude = { fields?: Fields; sort?: Sort; + skip?: number; + limit?: number; }; export type UserContextValue = { From d690fcc45db296497e3c6fb5d361958f0538c88a Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 2 Jul 2025 10:42:05 -0300 Subject: [PATCH 09/33] chore: revert Subscriptions --- .../meteor/client/providers/UserProvider/UserProvider.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/meteor/client/providers/UserProvider/UserProvider.tsx b/apps/meteor/client/providers/UserProvider/UserProvider.tsx index 28f7fd3783ced..ae5545a8140c2 100644 --- a/apps/meteor/client/providers/UserProvider/UserProvider.tsx +++ b/apps/meteor/client/providers/UserProvider/UserProvider.tsx @@ -82,13 +82,7 @@ const UserProvider = ({ children }: UserProviderProps): ReactElement => { }), querySubscriptions: createReactiveSubscriptionFactory((query, options) => { if (userId) { - const predicate = createPredicateFromFilter(query); - const subs = Subscriptions.state.filter(predicate); - - if (options?.sort || options?.limit || options?.skip) { - return applyQueryOptions(subs, options); - } - return subs; + return Subscriptions.find(query, options).fetch(); } // Anonnymous users don't have subscriptions. Fetch Rooms instead. const predicate = createPredicateFromFilter(query); From 9aa1928fc48706e6acc63962d5761cb2935f6f32 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 2 Jul 2025 11:54:49 -0300 Subject: [PATCH 10/33] chore: subscribe on `useRoomQuery` --- .../client/views/room/providers/hooks/useRoomQuery.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts b/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts index eec603bd02c8a..73de82ab133eb 100644 --- a/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts +++ b/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts @@ -29,11 +29,7 @@ export function useRoomQuery( const { refetch } = queryResult; useEffect(() => { - Rooms.use.subscribe((state, prevState) => { - if (state.records.size !== prevState.records.size) { - refetch(); - } - }); + Rooms.use.subscribe(() => refetch()); }, [refetch, rid]); return queryResult; From ba7dcdc2d0ecd233a58ccee8caaa3907acbfca9c Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 2 Jul 2025 14:59:28 -0300 Subject: [PATCH 11/33] fix: `useOpenRoom` store --- .../client/views/room/hooks/useOpenRoom.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/apps/meteor/client/views/room/hooks/useOpenRoom.ts b/apps/meteor/client/views/room/hooks/useOpenRoom.ts index 48d8dd8dca1e9..b2fd77fcd06cc 100644 --- a/apps/meteor/client/views/room/hooks/useOpenRoom.ts +++ b/apps/meteor/client/views/room/hooks/useOpenRoom.ts @@ -35,7 +35,7 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st throw new RoomNotFoundError(undefined, { type, reference }); } - let roomData; + let roomData: IRoom; try { roomData = await getRoomByTypeAndName(type, reference); } catch (error) { @@ -60,16 +60,11 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st const { Rooms, Subscriptions } = await import('../../../../app/models/client'); - Rooms.state.update( - (record) => record._id === roomData._id, - (record) => { - const unsetKeys = getObjectKeys(roomFields).filter((key) => key in roomData) as (keyof IRoom)[]; - unsetKeys.forEach((key) => { - delete record[key]; - }); - return { ...record, ...roomData }; - }, - ); + const unsetKeys = getObjectKeys(roomData).filter((key) => !(key in roomFields)); + unsetKeys.forEach((key) => { + delete roomData[key]; + }); + Rooms.state.store(roomData); const room = Rooms.state.get(roomData._id); From 65c24c4fb5b803d37d0617b79a7a9b6982bf69fc Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Wed, 2 Jul 2025 15:12:40 -0300 Subject: [PATCH 12/33] chore: reorg --- .../client/lib/cachedCollections/index.ts | 1 + .../client/lib/cachedCollections/pipe.ts | 54 ------------------ .../client/lib/cachedCollections/utils.ts | 55 +++++++++++++++++++ .../providers/UserProvider/UserProvider.tsx | 2 +- packages/ui-contexts/src/index.ts | 2 +- 5 files changed, 58 insertions(+), 56 deletions(-) create mode 100644 apps/meteor/client/lib/cachedCollections/utils.ts diff --git a/apps/meteor/client/lib/cachedCollections/index.ts b/apps/meteor/client/lib/cachedCollections/index.ts index 66ca20c87320a..caafeb022b83a 100644 --- a/apps/meteor/client/lib/cachedCollections/index.ts +++ b/apps/meteor/client/lib/cachedCollections/index.ts @@ -1,3 +1,4 @@ export { PrivateCachedCollection, PublicCachedCollection } from './CachedCollection'; export { CachedCollectionManager } from './CachedCollectionManager'; export { pipe } from './pipe'; +export { applyQueryOptions, convertSort } from './utils'; diff --git a/apps/meteor/client/lib/cachedCollections/pipe.ts b/apps/meteor/client/lib/cachedCollections/pipe.ts index d893763017b0d..130691e46c1dd 100644 --- a/apps/meteor/client/lib/cachedCollections/pipe.ts +++ b/apps/meteor/client/lib/cachedCollections/pipe.ts @@ -1,5 +1,3 @@ -import type { FindOptions } from '@rocket.chat/ui-contexts/dist/UserContext'; - interface IPipeReturn { slice(skip: number, limit: number): IPipeReturn; sortByField(fieldName: keyof D, direction?: 1 | -1): IPipeReturn; @@ -71,55 +69,3 @@ export function pipe( }, }; } - -type OriginalStructure = FindOptions['sort']; - -type SortField = 'lm' | 'lowerCaseFName' | 'lowerCaseName'; -type SortDirection = -1 | 1; - -type SortObject = { - field: SortField; - direction: SortDirection; -}[]; - -/** - * Converts a MongoDB-style sort structure to a sort object. - */ -export const convertSort = (original: OriginalStructure): SortObject => { - const convertedSort: SortObject = []; - - if (!original) { - return convertedSort; - } - Object.keys(original as Record).forEach((key) => { - const direction = original[key as keyof OriginalStructure]; - - if (direction === -1 || direction === 1) { - convertedSort.push({ - field: key as SortField, - direction, - }); - } - }); - return convertedSort; -}; - -export const applyQueryOptions = >(records: T[], options: FindOptions): T[] => { - let currentPipeline = pipe(records); - if (options.sort) { - const sortObj = convertSort(options.sort); - for (let i = sortObj.sort.length - 1; i >= 0; i--) { - const { field, direction } = sortObj[i]; - currentPipeline = currentPipeline.sortByField(field, direction); - } - } - if (options.skip) { - currentPipeline = currentPipeline.slice(options.skip, records.length); - } - if (options.limit !== undefined) { - // If skip was applied, limit will be applied on the already skipped array - // If no skip, it will be applied from the beginning. - currentPipeline = currentPipeline.slice(0, options.limit); - } - return currentPipeline.apply(); -}; diff --git a/apps/meteor/client/lib/cachedCollections/utils.ts b/apps/meteor/client/lib/cachedCollections/utils.ts new file mode 100644 index 0000000000000..a9dbfc357fcf9 --- /dev/null +++ b/apps/meteor/client/lib/cachedCollections/utils.ts @@ -0,0 +1,55 @@ +import type { FindOptions } from '@rocket.chat/ui-contexts'; + +import { pipe } from './pipe'; + +type OriginalStructure = FindOptions['sort']; + +type SortField = 'lm' | 'lowerCaseFName' | 'lowerCaseName'; +type SortDirection = -1 | 1; + +type SortObject = { + field: SortField; + direction: SortDirection; +}[]; + +/** + * Converts a MongoDB-style sort structure to a sort object. + */ +export const convertSort = (original: OriginalStructure): SortObject => { + const convertedSort: SortObject = []; + + if (!original) { + return convertedSort; + } + Object.keys(original as Record).forEach((key) => { + const direction = original[key as keyof OriginalStructure]; + + if (direction === -1 || direction === 1) { + convertedSort.push({ + field: key as SortField, + direction, + }); + } + }); + return convertedSort; +}; + +export const applyQueryOptions = >(records: T[], options: FindOptions): T[] => { + let currentPipeline = pipe(records); + if (options.sort) { + const sortObj = convertSort(options.sort); + for (let i = sortObj.sort.length - 1; i >= 0; i--) { + const { field, direction } = sortObj[i]; + currentPipeline = currentPipeline.sortByField(field, direction); + } + } + if (options.skip) { + currentPipeline = currentPipeline.slice(options.skip, records.length); + } + if (options.limit !== undefined) { + // If skip was applied, limit will be applied on the already skipped array + // If no skip, it will be applied from the beginning. + currentPipeline = currentPipeline.slice(0, options.limit); + } + return currentPipeline.apply(); +}; diff --git a/apps/meteor/client/providers/UserProvider/UserProvider.tsx b/apps/meteor/client/providers/UserProvider/UserProvider.tsx index ae5545a8140c2..29db13f51a966 100644 --- a/apps/meteor/client/providers/UserProvider/UserProvider.tsx +++ b/apps/meteor/client/providers/UserProvider/UserProvider.tsx @@ -21,7 +21,7 @@ import { sdk } from '../../../app/utils/client/lib/SDKClient'; import { afterLogoutCleanUpCallback } from '../../../lib/callbacks/afterLogoutCleanUpCallback'; import { useIdleConnection } from '../../hooks/useIdleConnection'; import { useReactiveValue } from '../../hooks/useReactiveValue'; -import { applyQueryOptions } from '../../lib/cachedCollections/pipe'; +import { applyQueryOptions } from '../../lib/cachedCollections'; import { createReactiveSubscriptionFactory } from '../../lib/createReactiveSubscriptionFactory'; import { useSamlInviteToken } from '../../views/invite/hooks/useSamlInviteToken'; diff --git a/packages/ui-contexts/src/index.ts b/packages/ui-contexts/src/index.ts index cf976d57555ea..0e624f470e80e 100644 --- a/packages/ui-contexts/src/index.ts +++ b/packages/ui-contexts/src/index.ts @@ -99,6 +99,6 @@ export { useOnLogout } from './hooks/useOnLogout'; export { UploadResult } from './ServerContext'; export { TranslationKey, TranslationLanguage } from './TranslationContext'; -export { Fields } from './UserContext'; +export { Fields, FindOptions } from './UserContext'; export { SubscriptionWithRoom } from './types/SubscriptionWithRoom'; From 781191185aaafef47ecf2220bc6bf548c7c1cc86 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Mon, 7 Jul 2025 12:20:53 -0300 Subject: [PATCH 13/33] chore: unsubscribe --- apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts b/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts index 73de82ab133eb..86ffbb9361f1e 100644 --- a/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts +++ b/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts @@ -28,9 +28,7 @@ export function useRoomQuery( const { refetch } = queryResult; - useEffect(() => { - Rooms.use.subscribe(() => refetch()); - }, [refetch, rid]); + useEffect(() => Rooms.use.subscribe(() => refetch()), [refetch, rid]); return queryResult; } From 7d20ba9dc6ff212ee8c9f6bd5128d0564c62510f Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Mon, 7 Jul 2025 12:21:42 -0300 Subject: [PATCH 14/33] chore: review --- apps/meteor/client/providers/UserProvider/UserProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/providers/UserProvider/UserProvider.tsx b/apps/meteor/client/providers/UserProvider/UserProvider.tsx index 29db13f51a966..4bb47773cc5e8 100644 --- a/apps/meteor/client/providers/UserProvider/UserProvider.tsx +++ b/apps/meteor/client/providers/UserProvider/UserProvider.tsx @@ -86,7 +86,7 @@ const UserProvider = ({ children }: UserProviderProps): ReactElement => { } // Anonnymous users don't have subscriptions. Fetch Rooms instead. const predicate = createPredicateFromFilter(query); - const rooms = Rooms.state.filter(predicate) as unknown as SubscriptionWithRoom[]; // FIXME: this is a hack to make the typings work. How was it working before? + const rooms = Rooms.state.filter(predicate); if (options?.sort || options?.limit) { return applyQueryOptions(rooms, options); From ee694f6f14f2925fc497b2dcb9ad5f9749093ba0 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Mon, 7 Jul 2025 13:52:45 -0300 Subject: [PATCH 15/33] chore: remove eslint disable --- apps/meteor/app/e2e/client/rocketchat.e2e.room.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/meteor/app/e2e/client/rocketchat.e2e.room.ts b/apps/meteor/app/e2e/client/rocketchat.e2e.room.ts index f6dbebccefd3c..42ea61f67bc55 100644 --- a/apps/meteor/app/e2e/client/rocketchat.e2e.room.ts +++ b/apps/meteor/app/e2e/client/rocketchat.e2e.room.ts @@ -335,7 +335,6 @@ export class E2ERoom extends Emitter { } try { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const room = Rooms.state.get(this.roomId); if (!room?.e2eKeyId) { this.setState(E2ERoomState.CREATING_KEYS); From 37a5327f1f20a410b7f28f1eb1c5cd1a3c7dca6e Mon Sep 17 00:00:00 2001 From: Tasso Date: Tue, 8 Jul 2025 13:18:45 -0300 Subject: [PATCH 16/33] Replace `waitUntilFind` --- apps/meteor/app/e2e/client/rocketchat.e2e.ts | 19 +++++++++++++++++-- apps/meteor/client/lib/utils/waitUntilFind.ts | 15 --------------- apps/meteor/client/startup/e2e.ts | 19 ++++++++++++++++--- 3 files changed, 33 insertions(+), 20 deletions(-) delete mode 100644 apps/meteor/client/lib/utils/waitUntilFind.ts diff --git a/apps/meteor/app/e2e/client/rocketchat.e2e.ts b/apps/meteor/app/e2e/client/rocketchat.e2e.ts index 3fc07db91cc7b..102d8af58748c 100644 --- a/apps/meteor/app/e2e/client/rocketchat.e2e.ts +++ b/apps/meteor/app/e2e/client/rocketchat.e2e.ts @@ -32,7 +32,6 @@ import * as banners from '../../../client/lib/banners'; import type { LegacyBannerPayload } from '../../../client/lib/banners'; import { dispatchToastMessage } from '../../../client/lib/toast'; import { mapMessageFromApi } from '../../../client/lib/utils/mapMessageFromApi'; -import { waitUntilFind } from '../../../client/lib/utils/waitUntilFind'; import EnterE2EPasswordModal from '../../../client/views/e2e/EnterE2EPasswordModal'; import SaveE2EPasswordModal from '../../../client/views/e2e/SaveE2EPasswordModal'; import { createQuoteAttachment } from '../../../lib/createQuoteAttachment'; @@ -254,8 +253,24 @@ class E2E extends Emitter { ); } + private waitForRoom(rid: IRoom['_id']): Promise { + return new Promise((resolve) => { + const room = Rooms.state.get(rid); + + if (room) resolve(room); + + const unsubscribe = Rooms.use.subscribe((state) => { + const room = state.get(rid); + if (room) { + unsubscribe(); + resolve(room); + } + }); + }); + } + async getInstanceByRoomId(rid: IRoom['_id']): Promise { - const room = await waitUntilFind(() => Rooms.state.get(rid)); + const room = await this.waitForRoom(rid); if (room.t !== 'd' && room.t !== 'p') { return null; diff --git a/apps/meteor/client/lib/utils/waitUntilFind.ts b/apps/meteor/client/lib/utils/waitUntilFind.ts deleted file mode 100644 index 67ace08c31499..0000000000000 --- a/apps/meteor/client/lib/utils/waitUntilFind.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Tracker } from 'meteor/tracker'; - -export const waitUntilFind = (fn: () => T | undefined): Promise => - new Promise((resolve) => { - Tracker.autorun((c) => { - const result = fn(); - - if (result === undefined) { - return; - } - - c.stop(); - resolve(result); - }); - }); diff --git a/apps/meteor/client/startup/e2e.ts b/apps/meteor/client/startup/e2e.ts index 1804b2a66c894..945ba71010777 100644 --- a/apps/meteor/client/startup/e2e.ts +++ b/apps/meteor/client/startup/e2e.ts @@ -1,4 +1,4 @@ -import type { IMessage } from '@rocket.chat/core-typings'; +import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import { isE2EEPinnedMessage } from '@rocket.chat/core-typings'; import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; @@ -11,7 +11,6 @@ import { settings } from '../../app/settings/client'; import { onClientBeforeSendMessage } from '../lib/onClientBeforeSendMessage'; import { onClientMessageReceived } from '../lib/onClientMessageReceived'; import { isLayoutEmbedded } from '../lib/utils/isLayoutEmbedded'; -import { waitUntilFind } from '../lib/utils/waitUntilFind'; import { router } from '../providers/RouterProvider'; Meteor.startup(() => { @@ -79,7 +78,21 @@ Meteor.startup(() => { return message; } - const subscription = await waitUntilFind(() => Rooms.state.get(message.rid)); + // e2e.getInstanceByRoomId already waits for the room to be available which means this logic needs to be + // refactored to avoid waiting for the room again + const subscription = await new Promise((resolve) => { + const room = Rooms.state.get(message.rid); + + if (room) resolve(room); + + const unsubscribe = Rooms.use.subscribe((state) => { + const room = state.get(message.rid); + if (room) { + unsubscribe(); + resolve(room); + } + }); + }); subscription.encrypted ? e2eRoom.resume() : e2eRoom.pause(); From 62b37f1fd29b86c92947dabb431f76f1f9e43daf Mon Sep 17 00:00:00 2001 From: Tasso Date: Tue, 8 Jul 2025 13:23:43 -0300 Subject: [PATCH 17/33] Add missing translation --- packages/i18n/src/locales/en.i18n.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 7849382201ded..9b134dac289aa 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -5008,6 +5008,7 @@ "Teams_removing__username__from_team": "You are removing {{username}} from this Team", "Teams_removing__username__from_team_and_channels": "You are removing {{username}} from this Team and all its Channels.", "Teams_removing_member": "Removing Member", + "Technology_Provider": "Technology Provider", "Technology_Services": "Technology Services", "Temporarily_unavailable": "Temporarily unavailable", "Terms": "Terms", @@ -6877,4 +6878,4 @@ "__usernames__joined": "{{usernames}} joined", "__usersCount__joined": "{{count}} joined", "__usersCount__people_will_be_invited": "{{usersCount}} people will be invited" -} \ No newline at end of file +} From f5533a62c324e77a027a6d187c6c55b9d9007b09 Mon Sep 17 00:00:00 2001 From: Tasso Date: Tue, 8 Jul 2025 16:40:11 -0300 Subject: [PATCH 18/33] Replace `useRoomQuery` --- .../Info/EditRoomInfo/EditRoomInfo.tsx | 8 ++--- .../views/room/providers/RoomProvider.tsx | 17 ++++------ .../room/providers/hooks/useRoomQuery.ts | 34 ------------------- 3 files changed, 11 insertions(+), 48 deletions(-) delete mode 100644 apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts diff --git a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx index 0a950bf26c941..9dc08fc62bb3d 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx +++ b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx @@ -60,10 +60,10 @@ type EditRoomInfoProps = { }; const title = { - team: 'Edit_team' as TranslationKey, - channel: 'Edit_channel' as TranslationKey, - discussion: 'Edit_discussion' as TranslationKey, -}; + team: 'Edit_team', + channel: 'Edit_channel', + discussion: 'Edit_discussion', +} as const; const getRetentionSetting = (roomType: IRoomWithRetentionPolicy['t']): string => { switch (roomType) { diff --git a/apps/meteor/client/views/room/providers/RoomProvider.tsx b/apps/meteor/client/views/room/providers/RoomProvider.tsx index b6fd623ff68ca..b113b74ff8356 100644 --- a/apps/meteor/client/views/room/providers/RoomProvider.tsx +++ b/apps/meteor/client/views/room/providers/RoomProvider.tsx @@ -6,9 +6,8 @@ import ComposerPopupProvider from './ComposerPopupProvider'; import RoomToolboxProvider from './RoomToolboxProvider'; import UserCardProvider from './UserCardProvider'; import { useRedirectOnSettingsChanged } from './hooks/useRedirectOnSettingsChanged'; -import { useRoomQuery } from './hooks/useRoomQuery'; import { useUsersNameChanged } from './hooks/useUsersNameChanged'; -import { Subscriptions } from '../../../../app/models/client'; +import { Rooms, Subscriptions } from '../../../../app/models/client'; import { UserAction } from '../../../../app/ui/client/lib/UserAction'; import { RoomHistoryManager } from '../../../../app/ui-utils/client'; import { omit } from '../../../../lib/utils/omit'; @@ -32,8 +31,7 @@ type RoomProviderProps = { const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { const resultFromServer = useRoomInfoEndpoint(rid); - const resultFromLocal = useRoomQuery(rid); - + const room = Rooms.use((state) => state.get(rid)); const subscritionFromLocal = Subscriptions.use((state) => state.find((record) => record.rid === rid)); useRedirectOnSettingsChanged(subscritionFromLocal); @@ -41,7 +39,6 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { useUsersNameChanged(); const pseudoRoom: IRoomWithFederationOriginalName | null = useMemo(() => { - const room = resultFromLocal.data; if (!room) { return null; } @@ -52,7 +49,7 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { name: roomCoordinator.getRoomName(room.t, room), federationOriginalName: room.name, }; - }, [resultFromLocal.data, subscritionFromLocal]); + }, [room, subscritionFromLocal]); const { hasMorePreviousMessages, hasMoreNextMessages, isLoadingMoreMessages } = useReactiveValue( useCallback(() => { @@ -86,10 +83,10 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { const { mutate: fireRoomOpenedEvent } = useFireGlobalEvent('room-opened', rid); useEffect(() => { - if (resultFromLocal.data) { - fireRoomOpenedEvent(omit(resultFromLocal.data, 'usernames')); + if (room) { + fireRoomOpenedEvent(omit(room, 'usernames')); } - }, [rid, resultFromLocal.data, fireRoomOpenedEvent]); + }, [rid, room, fireRoomOpenedEvent]); useEffect(() => { if (isSidepanelFeatureEnabled) { @@ -164,7 +161,7 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { }, [rid, subscribed]); if (!pseudoRoom) { - return !resultFromLocal.data ? : ; + return !room ? : ; } return ( diff --git a/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts b/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts deleted file mode 100644 index 86ffbb9361f1e..0000000000000 --- a/apps/meteor/client/views/room/providers/hooks/useRoomQuery.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { IRoom } from '@rocket.chat/core-typings'; -import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query'; -import { useQuery } from '@tanstack/react-query'; -import { useEffect } from 'react'; - -import { Rooms } from '../../../../../app/models/client'; -import { RoomNotFoundError } from '../../../../lib/errors/RoomNotFoundError'; -import { roomsQueryKeys } from '../../../../lib/queryKeys'; - -export function useRoomQuery( - rid: IRoom['_id'], - options?: UseQueryOptions, -): UseQueryResult { - const queryResult = useQuery({ - queryKey: roomsQueryKeys.room(rid), - queryFn: async () => { - const room = Rooms.state.get(rid); - - if (!room) { - throw new RoomNotFoundError(undefined, { rid }); - } - - return room; - }, - staleTime: Infinity, - ...options, - }); - - const { refetch } = queryResult; - - useEffect(() => Rooms.use.subscribe(() => refetch()), [refetch, rid]); - - return queryResult; -} From 58fea4aba88c675a55f0e34fc313b14d5e6c6bc5 Mon Sep 17 00:00:00 2001 From: Tasso Date: Wed, 9 Jul 2025 15:10:47 -0300 Subject: [PATCH 19/33] Implement `CachedStore` as alternative for `CachedCollection` --- .../models/client/models/CachedChatRoom.ts | 6 +- .../client/models/CachedChatSubscription.ts | 2 +- apps/meteor/app/models/client/models/Rooms.ts | 16 ++-- .../lib/cachedCollections/CachedCollection.ts | 91 +++++++++++++++---- .../CachedCollectionManager.ts | 6 +- 5 files changed, 88 insertions(+), 33 deletions(-) diff --git a/apps/meteor/app/models/client/models/CachedChatRoom.ts b/apps/meteor/app/models/client/models/CachedChatRoom.ts index a5a27f8656f38..3071781758ef3 100644 --- a/apps/meteor/app/models/client/models/CachedChatRoom.ts +++ b/apps/meteor/app/models/client/models/CachedChatRoom.ts @@ -3,13 +3,15 @@ import { DEFAULT_SLA_CONFIG, LivechatPriorityWeight } from '@rocket.chat/core-ty import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts'; import { CachedChatSubscription } from './CachedChatSubscription'; -import { PrivateCachedCollection } from '../../../../client/lib/cachedCollections/CachedCollection'; +import { PrivateCachedStore } from '../../../../client/lib/cachedCollections/CachedCollection'; +import { createDocumentMapStore } from '../../../../client/lib/cachedCollections/DocumentMapStore'; -class CachedChatRoom extends PrivateCachedCollection { +class CachedChatRoom extends PrivateCachedStore { constructor() { super({ name: 'rooms', eventType: 'notify-user', + store: createDocumentMapStore(), }); } diff --git a/apps/meteor/app/models/client/models/CachedChatSubscription.ts b/apps/meteor/app/models/client/models/CachedChatSubscription.ts index 739abdf6d59b1..3a1440bf873cc 100644 --- a/apps/meteor/app/models/client/models/CachedChatSubscription.ts +++ b/apps/meteor/app/models/client/models/CachedChatSubscription.ts @@ -21,7 +21,7 @@ class CachedChatSubscription extends PrivateCachedCollection r._id === subscription.rid); + const room = CachedChatRoom.store.getState().find((r) => r._id === subscription.rid); const lastRoomUpdate = room?.lm || subscription.ts || room?.ts; diff --git a/apps/meteor/app/models/client/models/Rooms.ts b/apps/meteor/app/models/client/models/Rooms.ts index f68cf73f63de7..de6065e0ea713 100644 --- a/apps/meteor/app/models/client/models/Rooms.ts +++ b/apps/meteor/app/models/client/models/Rooms.ts @@ -1,13 +1,9 @@ -import type { IRoom } from '@rocket.chat/core-typings'; -import type { StoreApi, UseBoundStore } from 'zustand'; - import { CachedChatRoom } from './CachedChatRoom'; -import type { IDocumentMapStore } from '../../../../client/lib/cachedCollections/DocumentMapStore'; - -type RoomsStore = { - use: UseBoundStore>>; - readonly state: IDocumentMapStore; -}; /** @deprecated new code refer to Minimongo collections like this one; prefer fetching data from the REST API, listening to changes via streamer events, and storing the state in a Tanstack Query */ -export const Rooms = CachedChatRoom.collection as RoomsStore; +export const Rooms = { + use: CachedChatRoom.store, + get state() { + return this.use.getState(); + }, +} as const; diff --git a/apps/meteor/client/lib/cachedCollections/CachedCollection.ts b/apps/meteor/client/lib/cachedCollections/CachedCollection.ts index b1c3f37dc6d21..72a811d7aa60f 100644 --- a/apps/meteor/client/lib/cachedCollections/CachedCollection.ts +++ b/apps/meteor/client/lib/cachedCollections/CachedCollection.ts @@ -5,10 +5,12 @@ import { Accounts } from 'meteor/accounts-base'; import { Meteor } from 'meteor/meteor'; import { ReactiveVar } from 'meteor/reactive-var'; import { Tracker } from 'meteor/tracker'; +import type { StoreApi, UseBoundStore } from 'zustand'; import { baseURI } from '../baseURI'; import { onLoggedIn } from '../loggedIn'; import { CachedCollectionManager } from './CachedCollectionManager'; +import type { IDocumentMapStore } from './DocumentMapStore'; import { MinimongoCollection } from './MinimongoCollection'; import { sdk } from '../../../app/utils/client/lib/SDKClient'; import { isTruthy } from '../../../lib/isTruthy'; @@ -36,12 +38,16 @@ const hasUnserializedUpdatedAt = (record: T): record is T & { _updatedAt: Con localforage.config({ name: baseURI }); -export abstract class CachedCollection { +export interface IWithManageableCache { + clearCacheOnLogout(): void; +} + +export abstract class CachedStore implements IWithManageableCache { private static readonly MAX_CACHE_TIME = 60 * 60 * 24 * 30; - public collection = new MinimongoCollection(); + readonly store: UseBoundStore>>; - public ready = new ReactiveVar(false); + readonly ready = new ReactiveVar(false); protected name: Name; @@ -55,9 +61,10 @@ export abstract class CachedCollection { private timer: ReturnType; - constructor({ name, eventType }: { name: Name; eventType: StreamNames }) { + constructor({ name, eventType, store }: { name: Name; eventType: StreamNames; store: UseBoundStore>> }) { this.name = name; this.eventType = eventType; + this.store = store; this.log = [getConfig(`debugCachedCollection-${this.name}`), getConfig('debugCachedCollection'), getConfig('debug')].includes('true') ? console.log.bind(console, `%cCachedCollection ${this.name}`, `color: navy; font-weight: bold;`) @@ -95,7 +102,7 @@ export abstract class CachedCollection { data.updatedAt = new Date(data.updatedAt); } - if (Date.now() - data.updatedAt.getTime() >= 1000 * CachedCollection.MAX_CACHE_TIME) { + if (Date.now() - data.updatedAt.getTime() >= 1000 * CachedStore.MAX_CACHE_TIME) { return false; } @@ -109,7 +116,7 @@ export abstract class CachedCollection { this.updatedAt = new Date(updatedAt); } - this.collection.state.replaceAll(deserializedRecords.filter(hasId)); + this.store.getState().replaceAll(deserializedRecords.filter(hasId)); this.updatedAt = data.updatedAt || this.updatedAt; @@ -157,7 +164,7 @@ export abstract class CachedCollection { return mapped; }); - this.collection.state.storeMany(newRecords); + this.store.getState().storeMany(newRecords); this.handleLoadedFromServer(newRecords); this.updatedAt = this.updatedAt === lastTime ? startTime : this.updatedAt; @@ -186,7 +193,7 @@ export abstract class CachedCollection { updatedAt: this.updatedAt, version: this.version, token: this.getToken(), - records: Array.from(this.collection.state.records.values()), + records: Array.from(this.store.getState().records.values()), }); this.log('saving cache (done)'); }); @@ -196,7 +203,7 @@ export abstract class CachedCollection { protected async clearCache() { this.log('clearing cache'); await localforage.removeItem(this.name); - this.collection.state.replaceAll([]); + this.store.getState().replaceAll([]); } protected setupListener() { @@ -210,9 +217,9 @@ export abstract class CachedCollection { const newRecord = this.mapRecord(record); if (action === 'removed') { - this.collection.state.delete(newRecord._id); + this.store.getState().delete(newRecord._id); } else { - this.collection.state.store(newRecord); + this.store.getState().store(newRecord); } await this.save(); @@ -250,7 +257,7 @@ export abstract class CachedCollection { const actionTime = hasUpdatedAt(newRecord) ? newRecord._updatedAt : startTime; changes.push({ action: () => { - this.collection.state.store(newRecord); + this.store.getState().store(newRecord); if (actionTime > this.updatedAt) { this.updatedAt = actionTime; } @@ -273,7 +280,7 @@ export abstract class CachedCollection { const actionTime = newRecord._deletedAt; changes.push({ action: () => { - this.collection.state.delete(newRecord._id); + this.store.getState().delete(newRecord._id); if (actionTime > this.updatedAt) { this.updatedAt = actionTime; } @@ -353,22 +360,72 @@ export abstract class CachedCollection { private reconnectionComputation: Tracker.Computation | undefined; } +export class PublicCachedStore extends CachedStore { + protected override getToken() { + return undefined; + } + + override clearCacheOnLogout() { + // do nothing + } +} + +export class PrivateCachedStore extends CachedStore { + protected override getToken() { + return Accounts._storedLoginToken(); + } + + override clearCacheOnLogout() { + void this.clearCache(); + } + + listen() { + if (process.env.NODE_ENV === 'test') { + return; + } + + onLoggedIn(() => { + void this.init(); + }); + + Accounts.onLogout(() => { + this.release(); + }); + } +} + +export abstract class CachedCollection extends CachedStore { + readonly collection; + + constructor({ name, eventType }: { name: Name; eventType: StreamNames }) { + const collection = new MinimongoCollection(); + + super({ + name, + eventType, + store: collection.use, + }); + + this.collection = collection; + } +} + export class PublicCachedCollection extends CachedCollection { - protected getToken() { + protected override getToken() { return undefined; } - clearCacheOnLogout() { + override clearCacheOnLogout() { // do nothing } } export class PrivateCachedCollection extends CachedCollection { - protected getToken() { + protected override getToken() { return Accounts._storedLoginToken(); } - clearCacheOnLogout() { + override clearCacheOnLogout() { void this.clearCache(); } diff --git a/apps/meteor/client/lib/cachedCollections/CachedCollectionManager.ts b/apps/meteor/client/lib/cachedCollections/CachedCollectionManager.ts index ca96af4179308..31ce60f58f7a0 100644 --- a/apps/meteor/client/lib/cachedCollections/CachedCollectionManager.ts +++ b/apps/meteor/client/lib/cachedCollections/CachedCollectionManager.ts @@ -1,9 +1,9 @@ -import type { CachedCollection } from './CachedCollection'; +import type { IWithManageableCache } from './CachedCollection'; class CachedCollectionManager { - private items = new Set>(); + private items = new Set(); - register(cachedCollection: CachedCollection) { + register(cachedCollection: IWithManageableCache) { this.items.add(cachedCollection); } From 3aae73c35b8ce9dc1d0309b388590ea712e711f6 Mon Sep 17 00:00:00 2001 From: Tasso Date: Wed, 9 Jul 2025 21:15:16 -0300 Subject: [PATCH 20/33] Redesign `roomCoordinator.readOnly` to depend on room reactivity --- .../reactions/client/methods/setReaction.ts | 2 +- .../UserAndRoomAutoCompleteMultiple.tsx | 32 ++++++++++++------- .../items/actions/ReactionMessageAction.tsx | 22 ++++++++++--- .../client/lib/rooms/roomCoordinator.tsx | 5 ++- .../client/lib/rooms/roomTypes/livechat.ts | 5 ++- .../meteor/client/lib/rooms/roomTypes/voip.ts | 2 +- .../body/hooks/useFileUploadDropTarget.ts | 2 +- .../views/room/composer/ComposerContainer.tsx | 2 +- .../hooks/useMessageComposerIsReadOnly.ts | 15 ++++----- apps/meteor/definition/IRoomTypeConfig.ts | 2 +- 10 files changed, 54 insertions(+), 35 deletions(-) diff --git a/apps/meteor/app/reactions/client/methods/setReaction.ts b/apps/meteor/app/reactions/client/methods/setReaction.ts index 0af53d91df5c1..d1cab04412310 100644 --- a/apps/meteor/app/reactions/client/methods/setReaction.ts +++ b/apps/meteor/app/reactions/client/methods/setReaction.ts @@ -36,7 +36,7 @@ Meteor.methods({ return false; } - if (roomCoordinator.readOnly(room._id, user)) { + if (roomCoordinator.readOnly(room, user)) { return false; } diff --git a/apps/meteor/client/components/UserAndRoomAutoCompleteMultiple/UserAndRoomAutoCompleteMultiple.tsx b/apps/meteor/client/components/UserAndRoomAutoCompleteMultiple/UserAndRoomAutoCompleteMultiple.tsx index ed7e7b93d8047..747cd1b7700af 100644 --- a/apps/meteor/client/components/UserAndRoomAutoCompleteMultiple/UserAndRoomAutoCompleteMultiple.tsx +++ b/apps/meteor/client/components/UserAndRoomAutoCompleteMultiple/UserAndRoomAutoCompleteMultiple.tsx @@ -5,8 +5,10 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; import { RoomAvatar, UserAvatar } from '@rocket.chat/ui-avatar'; import { useUser, useUserSubscriptions } from '@rocket.chat/ui-contexts'; import type { ComponentProps, ReactElement } from 'react'; -import { memo, useMemo, useState } from 'react'; +import { memo, useCallback, useMemo, useState } from 'react'; +import { Rooms } from '../../../app/models/client'; +import { useReactiveValue } from '../../hooks/useReactiveValue'; import { roomCoordinator } from '../../lib/rooms/roomCoordinator'; type UserAndRoomAutoCompleteMultipleProps = Omit, 'filter'>; @@ -16,7 +18,7 @@ const UserAndRoomAutoCompleteMultiple = ({ value, onChange, ...props }: UserAndR const [filter, setFilter] = useState(''); const debouncedFilter = useDebouncedValue(filter, 1000); - const rooms = useUserSubscriptions( + const subscriptions = useUserSubscriptions( useMemo( () => ({ open: { $ne: false }, @@ -27,17 +29,25 @@ const UserAndRoomAutoCompleteMultiple = ({ value, onChange, ...props }: UserAndR }), [debouncedFilter], ), - ).filter((room) => { - if (!user) { - return; - } + ); + + const rooms = useReactiveValue( + useCallback( + () => + subscriptions.filter((subscription) => { + if (!user) { + return; + } - if (isDirectMessageRoom(room) && (room.blocked || room.blocker)) { - return; - } + if (isDirectMessageRoom(subscription) && (subscription.blocked || subscription.blocker)) { + return; + } - return !roomCoordinator.readOnly(room.rid, user); - }); + return !roomCoordinator.readOnly(Rooms.state.get(subscription.rid), user); + }), + [subscriptions, user], + ), + ); const options = useMemo( () => diff --git a/apps/meteor/client/components/message/toolbar/items/actions/ReactionMessageAction.tsx b/apps/meteor/client/components/message/toolbar/items/actions/ReactionMessageAction.tsx index 81dd7dde6f1a6..f3e1d00062f05 100644 --- a/apps/meteor/client/components/message/toolbar/items/actions/ReactionMessageAction.tsx +++ b/apps/meteor/client/components/message/toolbar/items/actions/ReactionMessageAction.tsx @@ -1,9 +1,11 @@ import { isOmnichannelRoom, type IMessage, type IRoom, type ISubscription } from '@rocket.chat/core-typings'; import { useFeaturePreview } from '@rocket.chat/ui-client'; import { useUser, useMethod } from '@rocket.chat/ui-contexts'; +import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useEmojiPickerData } from '../../../../../contexts/EmojiPickerContext'; +import { useReactiveValue } from '../../../../../hooks/useReactiveValue'; import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator'; import EmojiElement from '../../../../../views/composer/EmojiPicker/EmojiElement'; import { useChat } from '../../../../../views/room/contexts/ChatContext'; @@ -23,11 +25,21 @@ const ReactionMessageAction = ({ message, room, subscription }: ReactionMessageA const { quickReactions, addRecentEmoji } = useEmojiPickerData(); const { t } = useTranslation(); - if (!chat || !room || isOmnichannelRoom(room) || !subscription || message.private || !user) { - return null; - } + const enabled = useReactiveValue( + useCallback(() => { + if (!chat || isOmnichannelRoom(room) || !subscription || message.private || !user) { + return false; + } + + if (roomCoordinator.readOnly(room, user) && !room.reactWhenReadOnly) { + return false; + } + + return true; + }, [chat, room, subscription, message.private, user]), + ); - if (roomCoordinator.readOnly(room._id, user) && !room.reactWhenReadOnly) { + if (!enabled) { return null; } @@ -49,7 +61,7 @@ const ReactionMessageAction = ({ message, room, subscription }: ReactionMessageA qa='Add_Reaction' onClick={(event) => { event.stopPropagation(); - chat.emojiPicker.open(event.currentTarget, (emoji) => { + chat?.emojiPicker.open(event.currentTarget, (emoji) => { toggleReaction(emoji); }); }} diff --git a/apps/meteor/client/lib/rooms/roomCoordinator.tsx b/apps/meteor/client/lib/rooms/roomCoordinator.tsx index 31b421168d624..ea246583b975a 100644 --- a/apps/meteor/client/lib/rooms/roomCoordinator.tsx +++ b/apps/meteor/client/lib/rooms/roomCoordinator.tsx @@ -111,15 +111,14 @@ class RoomCoordinatorClient extends RoomCoordinator { return this.getRoomDirectives(roomType).roomName(roomData) ?? ''; } - public readOnly(rid: string, user: AtLeast): boolean { - const room = Rooms.state.get(rid); + readOnly(room?: IRoom, user?: AtLeast | null): boolean { if (!room) { return false; } const directives = this.getRoomDirectives(room.t); if (directives?.readOnly) { - return directives.readOnly(rid, user); + return directives.readOnly(room, user); } if (!user?.username) { diff --git a/apps/meteor/client/lib/rooms/roomTypes/livechat.ts b/apps/meteor/client/lib/rooms/roomTypes/livechat.ts index 056f4b45deafa..88b349e3dc514 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/livechat.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/livechat.ts @@ -66,13 +66,12 @@ roomCoordinator.add( return Boolean(room?.open); }, - readOnly(rid, _user) { - const room = Rooms.state.get(rid); + readOnly(room) { if (!room?.open) { return true; } - const subscription = Subscriptions.findOne({ rid }); + const subscription = Subscriptions.findOne({ rid: room._id }); return !subscription; }, diff --git a/apps/meteor/client/lib/rooms/roomTypes/voip.ts b/apps/meteor/client/lib/rooms/roomTypes/voip.ts index cd5252ba01526..dcfb23eef116c 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/voip.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/voip.ts @@ -36,7 +36,7 @@ roomCoordinator.add( return false; }, - readOnly(_rid, _user) { + readOnly() { return true; }, diff --git a/apps/meteor/client/views/room/body/hooks/useFileUploadDropTarget.ts b/apps/meteor/client/views/room/body/hooks/useFileUploadDropTarget.ts index c247822f160e1..7cfcd4496dc07 100644 --- a/apps/meteor/client/views/room/body/hooks/useFileUploadDropTarget.ts +++ b/apps/meteor/client/views/room/body/hooks/useFileUploadDropTarget.ts @@ -31,7 +31,7 @@ export const useFileUploadDropTarget = (): readonly [ const fileUploadEnabled = useSetting('FileUpload_Enabled', true); const user = useUser(); const fileUploadAllowedForUser = useReactiveValue( - useCallback(() => !roomCoordinator.readOnly(room._id, { username: user?.username }), [room._id, user?.username]), + useCallback(() => !roomCoordinator.readOnly(room, { username: user?.username }), [room, user?.username]), ); const chat = useChat(); diff --git a/apps/meteor/client/views/room/composer/ComposerContainer.tsx b/apps/meteor/client/views/room/composer/ComposerContainer.tsx index bc598cc0d458d..8dd8048e1ff84 100644 --- a/apps/meteor/client/views/room/composer/ComposerContainer.tsx +++ b/apps/meteor/client/views/room/composer/ComposerContainer.tsx @@ -33,7 +33,7 @@ const ComposerContainer = ({ children, ...props }: ComposerMessageProps): ReactE const isSelectingMessages = useIsSelecting(); const isBlockedOrBlocker = useMessageComposerIsBlocked({ subscription: props.subscription }); const isArchived = useMessageComposerIsArchived(room._id, props.subscription); - const isReadOnly = useMessageComposerIsReadOnly(room._id); + const isReadOnly = useMessageComposerIsReadOnly(room); const isOmnichannel = isOmnichannelRoom(room); const isFederation = isRoomFederated(room); diff --git a/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsReadOnly.ts b/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsReadOnly.ts index 38f0e3eb1c1c8..974b5b16f539a 100644 --- a/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsReadOnly.ts +++ b/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsReadOnly.ts @@ -1,12 +1,11 @@ -import { useUserId } from '@rocket.chat/ui-contexts'; +import type { IRoom } from '@rocket.chat/core-typings'; +import { useUser } from '@rocket.chat/ui-contexts'; +import { useCallback } from 'react'; -import { Users } from '../../../../../app/models/client'; +import { useReactiveValue } from '../../../../hooks/useReactiveValue'; import { roomCoordinator } from '../../../../lib/rooms/roomCoordinator'; -export const useMessageComposerIsReadOnly = (rid: string): boolean => { - const uid = useUserId(); - - const isReadOnly = Users.use((state) => Boolean(roomCoordinator.readOnly(rid, (uid ? state.get(uid) : undefined)!))); - - return isReadOnly; +export const useMessageComposerIsReadOnly = (room: IRoom): boolean => { + const user = useUser(); + return useReactiveValue(useCallback(() => roomCoordinator.readOnly(room, user), [room, user])); }; diff --git a/apps/meteor/definition/IRoomTypeConfig.ts b/apps/meteor/definition/IRoomTypeConfig.ts index 25451f1d45849..5ddfd1208cb7f 100644 --- a/apps/meteor/definition/IRoomTypeConfig.ts +++ b/apps/meteor/definition/IRoomTypeConfig.ts @@ -86,7 +86,7 @@ export interface IRoomTypeClientDirectives { showJoinLink: (roomId: string) => boolean; isLivechatRoom: () => boolean; canSendMessage: (rid: string) => boolean; - readOnly?: (rid: string, user: AtLeast) => boolean; + readOnly?: (room?: IRoom, user?: AtLeast | null) => boolean; } export interface IRoomTypeServerDirectives { From 6b6efb0cc08f1be70eca07b19a54564eae4a54aa Mon Sep 17 00:00:00 2001 From: Tasso Date: Wed, 9 Jul 2025 21:27:57 -0300 Subject: [PATCH 21/33] Remove `roomCoordinator.archived` --- .../client/lib/rooms/roomCoordinator.tsx | 6 ------ .../views/room/composer/ComposerContainer.tsx | 2 +- .../hooks/useMessageComposerIsArchived.ts | 18 +++--------------- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/apps/meteor/client/lib/rooms/roomCoordinator.tsx b/apps/meteor/client/lib/rooms/roomCoordinator.tsx index ea246583b975a..4216b04717322 100644 --- a/apps/meteor/client/lib/rooms/roomCoordinator.tsx +++ b/apps/meteor/client/lib/rooms/roomCoordinator.tsx @@ -148,12 +148,6 @@ class RoomCoordinatorClient extends RoomCoordinator { return false; } - // #ToDo: Move this out of the RoomCoordinator - public archived(rid: string): boolean { - const room = Rooms.state.get(rid); - return Boolean(room?.archived); - } - public verifyCanSendMessage(rid: string): boolean { const room = Rooms.state.get(rid); if (!room?.t) { diff --git a/apps/meteor/client/views/room/composer/ComposerContainer.tsx b/apps/meteor/client/views/room/composer/ComposerContainer.tsx index 8dd8048e1ff84..e0a66f0352f31 100644 --- a/apps/meteor/client/views/room/composer/ComposerContainer.tsx +++ b/apps/meteor/client/views/room/composer/ComposerContainer.tsx @@ -32,7 +32,7 @@ const ComposerContainer = ({ children, ...props }: ComposerMessageProps): ReactE const isAnonymous = useMessageComposerIsAnonymous(); const isSelectingMessages = useIsSelecting(); const isBlockedOrBlocker = useMessageComposerIsBlocked({ subscription: props.subscription }); - const isArchived = useMessageComposerIsArchived(room._id, props.subscription); + const isArchived = useMessageComposerIsArchived(room, props.subscription); const isReadOnly = useMessageComposerIsReadOnly(room); const isOmnichannel = isOmnichannelRoom(room); diff --git a/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsArchived.ts b/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsArchived.ts index d0214f37d960e..4417288d78f8c 100644 --- a/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsArchived.ts +++ b/apps/meteor/client/views/room/composer/hooks/useMessageComposerIsArchived.ts @@ -1,16 +1,4 @@ -import type { ISubscription } from '@rocket.chat/core-typings'; -import { useCallback } from 'react'; +import type { IRoom, ISubscription } from '@rocket.chat/core-typings'; -import { useReactiveValue } from '../../../../hooks/useReactiveValue'; -import { roomCoordinator } from '../../../../lib/rooms/roomCoordinator'; - -export const useMessageComposerIsArchived = (rid: string, subscription?: ISubscription): boolean => { - const isArchived = useReactiveValue( - useCallback( - () => roomCoordinator.archived(rid) || Boolean(subscription && subscription.t === 'd' && subscription.archived), - [rid, subscription], - ), - ); - - return Boolean(isArchived); -}; +export const useMessageComposerIsArchived = (room: IRoom, subscription?: ISubscription): boolean => + !!room?.archived || Boolean(subscription && subscription.t === 'd' && subscription.archived); From 42b64ded6e5b1934bc74293aa0848dfc739217d7 Mon Sep 17 00:00:00 2001 From: Tasso Date: Wed, 9 Jul 2025 22:03:33 -0300 Subject: [PATCH 22/33] Remove `roomCoordinator.verifyCanSendMessage` --- .../client/lib/rooms/roomCoordinator.tsx | 22 +++---------------- .../client/lib/rooms/roomTypes/livechat.ts | 3 +-- .../meteor/client/lib/rooms/roomTypes/voip.ts | 2 +- .../room/composer/messageBox/MessageBox.tsx | 20 +++++++++++++++-- apps/meteor/definition/IRoomTypeConfig.ts | 2 +- 5 files changed, 24 insertions(+), 25 deletions(-) diff --git a/apps/meteor/client/lib/rooms/roomCoordinator.tsx b/apps/meteor/client/lib/rooms/roomCoordinator.tsx index 4216b04717322..a5bc4429d84d6 100644 --- a/apps/meteor/client/lib/rooms/roomCoordinator.tsx +++ b/apps/meteor/client/lib/rooms/roomCoordinator.tsx @@ -1,11 +1,9 @@ import type { IRoom, RoomType, IUser, AtLeast, ValueOf, ISubscription } from '@rocket.chat/core-typings'; -import { isRoomFederated } from '@rocket.chat/core-typings'; import type { RouteName } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermission } from '../../../app/authorization/client'; -import { Rooms, Subscriptions } from '../../../app/models/client'; -import { settings } from '../../../app/settings/client'; +import { Subscriptions } from '../../../app/models/client'; import type { RoomSettingsEnum, RoomMemberActions, @@ -59,8 +57,8 @@ class RoomCoordinatorClient extends RoomCoordinator { isLivechatRoom(): boolean { return false; }, - canSendMessage(rid: string): boolean { - return Subscriptions.find({ rid }).count() > 0; + canSendMessage(room: IRoom): boolean { + return Subscriptions.find({ rid: room._id }).count() > 0; }, ...directives, config: roomConfig, @@ -148,20 +146,6 @@ class RoomCoordinatorClient extends RoomCoordinator { return false; } - public verifyCanSendMessage(rid: string): boolean { - const room = Rooms.state.get(rid); - if (!room?.t) { - return false; - } - if (!this.getRoomDirectives(room.t).canSendMessage(rid)) { - return false; - } - if (isRoomFederated(room)) { - return settings.get('Federation_Matrix_enabled'); - } - return true; - } - private validateRoute(route: IRoomTypeRouteConfig): void { const { name, path, link } = route; diff --git a/apps/meteor/client/lib/rooms/roomTypes/livechat.ts b/apps/meteor/client/lib/rooms/roomTypes/livechat.ts index 88b349e3dc514..39058f48d6129 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/livechat.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/livechat.ts @@ -61,8 +61,7 @@ roomCoordinator.add( return true; }, - canSendMessage(rid) { - const room = Rooms.state.get(rid); + canSendMessage(room) { return Boolean(room?.open); }, diff --git a/apps/meteor/client/lib/rooms/roomTypes/voip.ts b/apps/meteor/client/lib/rooms/roomTypes/voip.ts index dcfb23eef116c..b7b99b4cecba0 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/voip.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/voip.ts @@ -32,7 +32,7 @@ roomCoordinator.add( return Rooms.state.get(identifier); }, - canSendMessage(_rid) { + canSendMessage() { return false; }, diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx index 227ceb92416a4..d633e45134bec 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx @@ -1,5 +1,5 @@ /* eslint-disable complexity */ -import type { IMessage, ISubscription } from '@rocket.chat/core-typings'; +import { isRoomFederated, type IMessage, type ISubscription } from '@rocket.chat/core-typings'; import { useContentBoxSize, useEffectEvent } from '@rocket.chat/fuselage-hooks'; import { useSafeRefCallback } from '@rocket.chat/ui-client'; import { @@ -277,7 +277,23 @@ const MessageBox = ({ const { autoGrowRef, textAreaStyle } = useAutoGrow(textareaRef, isRecordingAudio); - const canSend = useReactiveValue(useCallback(() => roomCoordinator.verifyCanSendMessage(room._id), [room._id])); + const federationMatrixEnabled = useSetting('Federation_Matrix_enabled', false); + const canSend = useReactiveValue( + useCallback(() => { + if (!room.t) { + return false; + } + + if (!roomCoordinator.getRoomDirectives(room.t).canSendMessage(room)) { + return false; + } + + if (isRoomFederated(room)) { + return federationMatrixEnabled; + } + return true; + }, [federationMatrixEnabled, room]), + ); const sizes = useContentBoxSize(textareaRef); diff --git a/apps/meteor/definition/IRoomTypeConfig.ts b/apps/meteor/definition/IRoomTypeConfig.ts index 5ddfd1208cb7f..17a7d25e080d4 100644 --- a/apps/meteor/definition/IRoomTypeConfig.ts +++ b/apps/meteor/definition/IRoomTypeConfig.ts @@ -85,7 +85,7 @@ export interface IRoomTypeClientDirectives { findRoom: (identifier: string) => IRoom | undefined; showJoinLink: (roomId: string) => boolean; isLivechatRoom: () => boolean; - canSendMessage: (rid: string) => boolean; + canSendMessage: (room: IRoom) => boolean; readOnly?: (room?: IRoom, user?: AtLeast | null) => boolean; } From e83f29fb959d3512dc8ea84383b8e98aa27844fb Mon Sep 17 00:00:00 2001 From: Tasso Date: Thu, 10 Jul 2025 01:16:17 -0300 Subject: [PATCH 23/33] Fix reactivity in `UserProvider` --- .../providers/UserProvider/UserProvider.tsx | 75 ++++++++++++++----- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/apps/meteor/client/providers/UserProvider/UserProvider.tsx b/apps/meteor/client/providers/UserProvider/UserProvider.tsx index 4bb47773cc5e8..810d1ce5e318f 100644 --- a/apps/meteor/client/providers/UserProvider/UserProvider.tsx +++ b/apps/meteor/client/providers/UserProvider/UserProvider.tsx @@ -1,14 +1,17 @@ import type { IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; import { Emitter } from '@rocket.chat/emitter'; import { useLocalStorage } from '@rocket.chat/fuselage-hooks'; +import type { Filter } from '@rocket.chat/mongo-adapter'; import { createPredicateFromFilter } from '@rocket.chat/mongo-adapter'; -import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts'; +import type { FindOptions, SubscriptionWithRoom } from '@rocket.chat/ui-contexts'; import { UserContext, useEndpoint, useRouteParameter, useSearchParameter } from '@rocket.chat/ui-contexts'; import { useQueryClient } from '@tanstack/react-query'; import { Accounts } from 'meteor/accounts-base'; import { Meteor } from 'meteor/meteor'; +import type { Filter as MongoFilter } from 'mongodb'; import type { ContextType, ReactElement, ReactNode } from 'react'; import { useEffect, useMemo, useRef } from 'react'; +import type { StoreApi, UseBoundStore } from 'zustand'; import { useClearRemovedRoomsHistory } from './hooks/useClearRemovedRoomsHistory'; import { useDeleteUser } from './hooks/useDeleteUser'; @@ -22,6 +25,7 @@ import { afterLogoutCleanUpCallback } from '../../../lib/callbacks/afterLogoutCl import { useIdleConnection } from '../../hooks/useIdleConnection'; import { useReactiveValue } from '../../hooks/useReactiveValue'; import { applyQueryOptions } from '../../lib/cachedCollections'; +import type { IDocumentMapStore } from '../../lib/cachedCollections/DocumentMapStore'; import { createReactiveSubscriptionFactory } from '../../lib/createReactiveSubscriptionFactory'; import { useSamlInviteToken } from '../../views/invite/hooks/useSamlInviteToken'; @@ -46,6 +50,26 @@ ee.on('logout', async () => { await sdk.call('logoutCleanUp', user); }); +const queryRoom = ( + // TODO: this type is a mismatch that might be fixed by adopting only types from `mongodb` instead of `@rocket.chat/mongo-adapter` + query: MongoFilter>, +): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => IRoom | undefined] => { + const predicate = createPredicateFromFilter(query as Filter>); + let snapshot = Rooms.state.find(predicate); + + const subscribe = (onStoreChange: () => void) => + Rooms.use.subscribe(() => { + const newSnapshot = Rooms.state.find(predicate); + if (newSnapshot === snapshot) return; + snapshot = newSnapshot; + onStoreChange(); + }); + + const getSnapshot = () => snapshot; + + return [subscribe, getSnapshot]; +}; + const UserProvider = ({ children }: UserProviderProps): ReactElement => { const user = useReactiveValue(getUser); const userId = useReactiveValue(getUserId); @@ -66,6 +90,34 @@ const UserProvider = ({ children }: UserProviderProps): ReactElement => { useIdleConnection(userId); useReloadAfterLogin(user); + const querySubscriptions = useMemo(() => { + const createSubscriptionFactory = + (store: UseBoundStore>>) => + ( + query: object, + options: FindOptions = {}, + ): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => SubscriptionWithRoom[]] => { + const predicate = createPredicateFromFilter(query); + let snapshot = applyQueryOptions(store.getState().filter(predicate), options); + + const subscribe = (onStoreChange: () => void) => + store.subscribe(() => { + const newSnapshot = applyQueryOptions(store.getState().filter(predicate), options); + if (newSnapshot === snapshot) return; + snapshot = newSnapshot; + onStoreChange(); + }); + + // TODO: this type assertion is completely wrong; however, the `useUserSubscriptions` hook might be deleted in + // the future, so we can live with it for now + const getSnapshot = () => snapshot as SubscriptionWithRoom[]; + + return [subscribe, getSnapshot]; + }; + + return userId ? createSubscriptionFactory(Subscriptions.use) : createSubscriptionFactory(Rooms.use); + }, [userId]); + const contextValue = useMemo( (): ContextType => ({ userId, @@ -76,29 +128,14 @@ const UserProvider = ({ children }: UserProviderProps): ReactElement => { querySubscription: createReactiveSubscriptionFactory((query, fields, sort) => Subscriptions.findOne(query, { fields, sort }), ), - queryRoom: createReactiveSubscriptionFactory((query) => { - const predicate = createPredicateFromFilter(query); - return Rooms.state.find(predicate); - }), - querySubscriptions: createReactiveSubscriptionFactory((query, options) => { - if (userId) { - return Subscriptions.find(query, options).fetch(); - } - // Anonnymous users don't have subscriptions. Fetch Rooms instead. - const predicate = createPredicateFromFilter(query); - const rooms = Rooms.state.filter(predicate); - - if (options?.sort || options?.limit) { - return applyQueryOptions(rooms, options); - } - return rooms; - }), + queryRoom, + querySubscriptions, logout: async () => Meteor.logout(), onLogout: (cb) => { return ee.on('logout', cb); }, }), - [userId, user], + [userId, user, querySubscriptions], ); useEffect(() => { From f3ce7081b26c322baa08f6c58e7b082444cf5543 Mon Sep 17 00:00:00 2001 From: Tasso Date: Thu, 10 Jul 2025 12:02:23 -0300 Subject: [PATCH 24/33] Adjust test locator --- apps/meteor/tests/e2e/message-actions.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/meteor/tests/e2e/message-actions.spec.ts b/apps/meteor/tests/e2e/message-actions.spec.ts index 1dbaf04b3b9be..8c3f6079b6dd0 100644 --- a/apps/meteor/tests/e2e/message-actions.spec.ts +++ b/apps/meteor/tests/e2e/message-actions.spec.ts @@ -1,6 +1,7 @@ import { ADMIN_CREDENTIALS } from './config/constants'; import { Users } from './fixtures/userStates'; import { HomeChannel, HomeDiscussion } from './page-objects'; +import { HomeFlextab } from './page-objects/fragments'; import { createTargetChannel, createTargetTeam } from './utils'; import { setUserPreferences } from './utils/setUserPreferences'; import { expect, test } from './utils/test'; @@ -156,11 +157,12 @@ test.describe.serial('message-actions', () => { }); test('expect star the message', async ({ page }) => { + const flextab = new HomeFlextab(page); await poHomeChannel.content.sendMessage('Message to star'); await poHomeChannel.content.openLastMessageMenu(); await page.locator('role=menuitem[name="Star"]').click(); await poHomeChannel.dismissToast(); - await page.locator('role=button[name="Options"]').click(); + await flextab.kebab.click(); await page.locator('[data-key="starred-messages"]').click(); await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('Message to star'); }); From 9738e88ea1fe950197a8a12342bc9137dbcf700c Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Thu, 10 Jul 2025 16:10:02 -0300 Subject: [PATCH 25/33] test: improve locator specificity --- .../page-objects/fragments/home-content.ts | 38 +++++++++++++++---- apps/meteor/tests/e2e/threads.spec.ts | 9 +++-- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts index 4512386f729cb..5980445b97e09 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts @@ -256,20 +256,24 @@ export class HomeContent { return this.page.locator('div.thread-list ul.thread [data-qa-type="message"]').last().locator('[data-qa-type="attachment-title-link"]'); } + get menuMore(): Locator { + return this.page.getByRole('menu', { name: 'More', exact: true }); + } + get btnOptionEditMessage(): Locator { - return this.page.locator('role=menu[name="More"] >> role=menuitem[name="Edit"]'); + return this.menuMore.getByRole('menuitem', { name: 'Edit', exact: true }); } get btnOptionDeleteMessage(): Locator { - return this.page.getByRole('menuitem', { name: 'Delete' }); + return this.menuMore.getByRole('menuitem', { name: 'Delete', exact: true }); } get btnOptionPinMessage(): Locator { - return this.page.locator('[data-qa-id="pin-message"]'); + return this.menuMore.getByRole('menuitem', { name: 'Pin', exact: true }); } get btnOptionStarMessage(): Locator { - return this.page.locator('[data-qa-id="star-message"]'); + return this.menuMore.getByRole('menuitem', { name: 'Star', exact: true }); } get btnOptionFileUpload(): Locator { @@ -424,10 +428,14 @@ export class HomeContent { await this.page.locator('[data-qa-type="message"]').last().locator('role=button[name="More"]').click(); } + get threadMessageList(): Locator { + return this.page.getByRole('list', { name: 'Thread message list' }); + } + async openLastThreadMessageMenu(): Promise { - await this.page.getByRole('dialog').locator('[data-qa-type="message"]').last().hover(); - await this.page.getByRole('dialog').locator('[data-qa-type="message"]').last().locator('role=button[name="More"]').waitFor(); - await this.page.getByRole('dialog').locator('[data-qa-type="message"]').last().locator('role=button[name="More"]').click(); + await this.threadMessageList.last().hover(); + await this.threadMessageList.last().getByRole('button', { name: 'More', exact: true }).waitFor(); + await this.threadMessageList.last().getByRole('button', { name: 'More', exact: true }).click(); } async toggleAlsoSendThreadToChannel(isChecked: boolean): Promise { @@ -449,10 +457,26 @@ export class HomeContent { return this.page.locator('[data-qa-id="ToolBoxAction-pause-unfilled"]'); } + get primaryRoomActionsToolbar(): Locator { + return this.page.getByRole('toolbar', { name: 'Primary Room actions' }); + } + get btnVideoCall(): Locator { return this.page.locator('[role=toolbar][aria-label="Primary Room actions"]').getByRole('button', { name: 'Video call' }); } + get btnToolbarOptions(): Locator { + return this.primaryRoomActionsToolbar.getByRole('button', { name: 'Options', exact: true }); + } + + get optionsMenu(): Locator { + return this.page.getByRole('menu', { name: 'Options', exact: true }); + } + + get starredMessagesMenuOption(): Locator { + return this.optionsMenu.getByRole('menuitem', { name: 'Starred Messages', exact: true }); + } + getVideoConfPopup(name?: string): Locator { return this.page.getByRole('dialog', { name }); } diff --git a/apps/meteor/tests/e2e/threads.spec.ts b/apps/meteor/tests/e2e/threads.spec.ts index 62a46ea8f74f7..fbbfcb07e87eb 100644 --- a/apps/meteor/tests/e2e/threads.spec.ts +++ b/apps/meteor/tests/e2e/threads.spec.ts @@ -141,11 +141,12 @@ test.describe.serial('Threads', () => { await expect(poHomeChannel.content.lastThreadMessageTextAttachmentEqualsText).toContainText('this is a message for reply'); }); - test('expect star the thread message', async ({ page }) => { + test('expect star the thread message', async () => { await poHomeChannel.content.openLastThreadMessageMenu(); - await page.locator('role=menuitem[name="Star"]').click(); - await page.getByRole('button').and(page.getByTitle('Options')).click(); - await page.locator('[data-key="starred-messages"]').click(); + await poHomeChannel.content.btnOptionStarMessage.click(); + await poHomeChannel.content.btnToolbarOptions.click(); + await poHomeChannel.content.starredMessagesMenuOption.click(); + await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('this is a message for reply'); }); From 8caec038ff56a2162fd9a2f680647eb3718991ab Mon Sep 17 00:00:00 2001 From: Tasso Date: Fri, 11 Jul 2025 14:07:00 -0300 Subject: [PATCH 26/33] Adjust types --- .../meteor/client/providers/UserProvider/UserProvider.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/meteor/client/providers/UserProvider/UserProvider.tsx b/apps/meteor/client/providers/UserProvider/UserProvider.tsx index 810d1ce5e318f..b887a3775442c 100644 --- a/apps/meteor/client/providers/UserProvider/UserProvider.tsx +++ b/apps/meteor/client/providers/UserProvider/UserProvider.tsx @@ -1,14 +1,13 @@ import type { IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; import { Emitter } from '@rocket.chat/emitter'; import { useLocalStorage } from '@rocket.chat/fuselage-hooks'; -import type { Filter } from '@rocket.chat/mongo-adapter'; import { createPredicateFromFilter } from '@rocket.chat/mongo-adapter'; import type { FindOptions, SubscriptionWithRoom } from '@rocket.chat/ui-contexts'; import { UserContext, useEndpoint, useRouteParameter, useSearchParameter } from '@rocket.chat/ui-contexts'; import { useQueryClient } from '@tanstack/react-query'; import { Accounts } from 'meteor/accounts-base'; import { Meteor } from 'meteor/meteor'; -import type { Filter as MongoFilter } from 'mongodb'; +import type { Filter } from 'mongodb'; import type { ContextType, ReactElement, ReactNode } from 'react'; import { useEffect, useMemo, useRef } from 'react'; import type { StoreApi, UseBoundStore } from 'zustand'; @@ -51,10 +50,9 @@ ee.on('logout', async () => { }); const queryRoom = ( - // TODO: this type is a mismatch that might be fixed by adopting only types from `mongodb` instead of `@rocket.chat/mongo-adapter` - query: MongoFilter>, + query: Filter>, ): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => IRoom | undefined] => { - const predicate = createPredicateFromFilter(query as Filter>); + const predicate = createPredicateFromFilter(query); let snapshot = Rooms.state.find(predicate); const subscribe = (onStoreChange: () => void) => From 47704cbe726ceaed8d4bb32b7769e516b250aed9 Mon Sep 17 00:00:00 2001 From: Tasso Date: Fri, 11 Jul 2025 17:24:14 -0300 Subject: [PATCH 27/33] Eliminate some auxiliar contexts from E2E test suite --- .../omnichannel/omnichannel-livechat.spec.ts | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts index 6c466a019c7e7..706d9729304a7 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts @@ -1,3 +1,5 @@ +import type { Page } from 'playwright-core'; + import { createFakeVisitor } from '../../mocks/data'; import { createAuxContext } from '../fixtures/createAuxContext'; import { Users } from '../fixtures/userStates'; @@ -15,6 +17,7 @@ test.use({ storageState: Users.user1.state }); test.describe.serial('OC - Livechat', () => { let poLiveChat: OmnichannelLiveChat; let poHomeOmnichannel: HomeOmnichannel; + let livechatPage: Page; test.beforeAll(async ({ api }) => { const statusCode = (await api.post('/livechat/users/agent', { username: 'user1' })).status(); @@ -22,7 +25,7 @@ test.describe.serial('OC - Livechat', () => { }); test.beforeAll(async ({ browser, api }) => { - const { page: livechatPage } = await createAuxContext(browser, Users.user1, '/livechat', false); + ({ page: livechatPage } = await createAuxContext(browser, Users.user1, '/livechat', false)); poLiveChat = new OmnichannelLiveChat(livechatPage, api); }); @@ -35,7 +38,7 @@ test.describe.serial('OC - Livechat', () => { }); test.afterAll(async ({ api }) => { - await Promise.all([api.delete('/livechat/users/agent/user1'), poLiveChat.page.close()]); + await Promise.all([api.delete('/livechat/users/agent/user1'), livechatPage.close()]); }); test('OC - Livechat - Send message to online agent', async () => { @@ -110,27 +113,32 @@ test.describe.serial('OC - Livechat', () => { test.describe.serial('OC - Livechat - Visitors closing the room is disabled', () => { let poLiveChat: OmnichannelLiveChat; let poHomeOmnichannel: HomeOmnichannel; + let livechatPage: Page; + let omniPage: Page; test.beforeAll(async ({ api }) => { await api.post('/livechat/users/agent', { username: 'user1' }); }); test.beforeAll(async ({ browser, api }) => { - const { page: livechatPage } = await createAuxContext(browser, Users.user1, '/livechat', false); + ({ page: livechatPage } = await createAuxContext(browser, Users.user1, '/livechat', false)); poLiveChat = new OmnichannelLiveChat(livechatPage, api); }); test.beforeAll(async ({ browser, api }) => { await setSettingValueById(api, 'Livechat_allow_visitor_closing_chat', false); - const { page: omniPage } = await createAuxContext(browser, Users.user1, '/', true); + ({ page: omniPage } = await createAuxContext(browser, Users.user1, '/', true)); poHomeOmnichannel = new HomeOmnichannel(omniPage); }); test.afterAll(async ({ api }) => { - await setSettingValueById(api, 'Livechat_allow_visitor_closing_chat', true); - await api.delete('/livechat/users/agent/user1'); - await poLiveChat.page.close(); + await Promise.all([ + setSettingValueById(api, 'Livechat_allow_visitor_closing_chat', true), + api.delete('/livechat/users/agent/user1'), + livechatPage.close(), + omniPage.close(), + ]); }); test('OC - Livechat - Close Chat disabled', async () => { @@ -161,6 +169,8 @@ test.describe.serial('OC - Livechat - Visitors closing the room is disabled', () test.describe.serial('OC - Livechat - Resub after close room', () => { let poLiveChat: OmnichannelLiveChat; let poHomeOmnichannel: HomeOmnichannel; + let livechatPage: Page; + let omniPage: Page; test.beforeAll(async ({ api }) => { const statusCode = (await api.post('/livechat/users/agent', { username: 'user1' })).status(); @@ -169,20 +179,22 @@ test.describe.serial('OC - Livechat - Resub after close room', () => { test.beforeAll(async ({ browser, api }) => { await api.post('/settings/Livechat_clear_local_storage_when_chat_ended', { value: true }); - const { page: omniPage } = await createAuxContext(browser, Users.user1, '/', true); + ({ page: omniPage } = await createAuxContext(browser, Users.user1, '/', true)); poHomeOmnichannel = new HomeOmnichannel(omniPage); - const { page: livechatPage } = await createAuxContext(browser, Users.user1, '/livechat', false); + ({ page: livechatPage } = await createAuxContext(browser, Users.user1, '/livechat', false)); poLiveChat = new OmnichannelLiveChat(livechatPage, api); await poLiveChat.sendMessageAndCloseChat(firstVisitor); }); test.afterAll(async ({ api }) => { - await api.post('/settings/Livechat_clear_local_storage_when_chat_ended', { value: false }); - await api.delete('/livechat/users/agent/user1'); - await poLiveChat.page.close(); - await poHomeOmnichannel.page.close(); + await Promise.all([ + api.post('/settings/Livechat_clear_local_storage_when_chat_ended', { value: false }), + api.delete('/livechat/users/agent/user1'), + livechatPage.close(), + omniPage.close(), + ]); }); test('OC - Livechat - Resub after close room', async () => { @@ -210,6 +222,8 @@ test.describe.serial('OC - Livechat - Resub after close room', () => { test.describe('OC - Livechat - Resume chat after closing', () => { let poLiveChat: OmnichannelLiveChat; let poHomeOmnichannel: HomeOmnichannel; + let livechatPage: Page; + let omniPage: Page; test.beforeAll(async ({ api }) => { const statusCode = (await api.post('/livechat/users/agent', { username: 'user1' })).status(); @@ -217,19 +231,17 @@ test.describe('OC - Livechat - Resume chat after closing', () => { }); test.beforeAll(async ({ browser, api }) => { - const { page: omniPage } = await createAuxContext(browser, Users.user1, '/', true); + ({ page: omniPage } = await createAuxContext(browser, Users.user1, '/', true)); poHomeOmnichannel = new HomeOmnichannel(omniPage); - const { page: livechatPage } = await createAuxContext(browser, Users.user1, '/livechat', false); + ({ page: livechatPage } = await createAuxContext(browser, Users.user1, '/livechat', false)); poLiveChat = new OmnichannelLiveChat(livechatPage, api); await poLiveChat.sendMessageAndCloseChat(firstVisitor); }); test.afterAll(async ({ api }) => { - await api.delete('/livechat/users/agent/user1'); - await poLiveChat.page.close(); - await poHomeOmnichannel.page.close(); + await Promise.all([api.delete('/livechat/users/agent/user1'), livechatPage.close(), omniPage.close()]); }); test('OC - Livechat - Resume chat after closing', async () => { @@ -256,14 +268,10 @@ test.describe('OC - Livechat - Resume chat after closing', () => { test.describe('OC - Livechat - Close chat using widget', () => { let poLiveChat: OmnichannelLiveChat; - let poHomeOmnichannel: HomeOmnichannel; let agent: Awaited>; - test.beforeAll(async ({ browser, api }) => { + test.beforeAll(async ({ api }) => { agent = await createAgent(api, 'user1'); - - const { page } = await createAuxContext(browser, Users.user1, '/'); - poHomeOmnichannel = new HomeOmnichannel(page); }); test.beforeEach(async ({ page, api }) => { @@ -273,7 +281,6 @@ test.describe('OC - Livechat - Close chat using widget', () => { }); test.afterAll(async () => { - await poHomeOmnichannel.page.close(); await agent.delete(); }); From a6afe6053cacad4a29eabaa1602d8dfe085f8d31 Mon Sep 17 00:00:00 2001 From: Tasso Date: Sat, 12 Jul 2025 02:19:04 -0300 Subject: [PATCH 28/33] Adjust types --- .../priorities/PriorityEditForm.tsx | 6 +- .../directory/hooks/usePriorityInfo.tsx | 8 +- packages/rest-typings/src/v1/omnichannel.ts | 2 +- yarn.lock | 514 +++++++++++++++--- 4 files changed, 447 insertions(+), 83 deletions(-) diff --git a/apps/meteor/client/omnichannel/priorities/PriorityEditForm.tsx b/apps/meteor/client/omnichannel/priorities/PriorityEditForm.tsx index 288cc5ae63519..0a3210552c78f 100644 --- a/apps/meteor/client/omnichannel/priorities/PriorityEditForm.tsx +++ b/apps/meteor/client/omnichannel/priorities/PriorityEditForm.tsx @@ -12,12 +12,8 @@ import StringSettingInput from '../../views/admin/settings/Setting/inputs/String export type PriorityFormData = { name: string; reset: boolean }; -type ILivechatClientPriority = Serialized & { - i18n: TranslationKey; -}; - export type PriorityEditFormProps = { - data: ILivechatClientPriority; + data: Serialized; onCancel: () => void; onSave: (values: PriorityFormData) => Promise; }; diff --git a/apps/meteor/client/views/omnichannel/directory/hooks/usePriorityInfo.tsx b/apps/meteor/client/views/omnichannel/directory/hooks/usePriorityInfo.tsx index 957110b6a58ea..d88eda9d29038 100644 --- a/apps/meteor/client/views/omnichannel/directory/hooks/usePriorityInfo.tsx +++ b/apps/meteor/client/views/omnichannel/directory/hooks/usePriorityInfo.tsx @@ -1,20 +1,14 @@ -import type { ILivechatPriority, Serialized } from '@rocket.chat/core-typings'; -import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; import { useOmnichannelPriorities } from '../../../../omnichannel/hooks/useOmnichannelPriorities'; -type ILivechatClientPriority = Serialized & { - i18n: TranslationKey; -}; - export const usePriorityInfo = (priorityId: string) => { const { enabled } = useOmnichannelPriorities(); const getPriority = useEndpoint('GET', `/v1/livechat/priorities/:priorityId`, { priorityId }); return useQuery({ queryKey: ['/v1/livechat/priorities', priorityId], - queryFn: () => getPriority() as Promise, + queryFn: () => getPriority(), gcTime: 0, enabled, }); diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index 11c6eebe7f34c..c51c7f6134c04 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -4082,7 +4082,7 @@ export type OmnichannelEndpoints = { }; '/v1/livechat/priorities/:priorityId': { - GET: () => ILivechatPriority | void; + GET: () => ILivechatPriority; PUT: (params: PUTLivechatPriority) => void; }; diff --git a/yarn.lock b/yarn.lock index e9f7a8c3873ff..c1361a8e7c6ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -209,6 +209,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/generator@npm:7.28.0" + dependencies: + "@babel/parser": "npm:^7.28.0" + "@babel/types": "npm:^7.28.0" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10/064c5ba4c07ecd7600377bd0022d5f6bdb3b35e9ff78d9378f6bd1e656467ca902c091647222ab2f0d2967f6d6c0ca33157d37dd9b1c51926c9b0e1527ab9b92 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.25.9, @babel/helper-annotate-as-pure@npm:^7.27.1": version: 7.27.3 resolution: "@babel/helper-annotate-as-pure@npm:7.27.3" @@ -276,6 +289,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-globals@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/helper-globals@npm:7.28.0" + checksum: 10/91445f7edfde9b65dcac47f4f858f68dc1661bf73332060ab67ad7cc7b313421099a2bfc4bda30c3db3842cfa1e86fffbb0d7b2c5205a177d91b22c8d7d9cb47 + languageName: node + linkType: hard + "@babel/helper-member-expression-to-functions@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-member-expression-to-functions@npm:7.25.9" @@ -414,6 +434,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.23.0, @babel/parser@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/parser@npm:7.28.0" + dependencies: + "@babel/types": "npm:^7.28.0" + bin: + parser: ./bin/babel-parser.js + checksum: 10/2c14a0d2600bae9ab81924df0a85bbd34e427caa099c260743f7c6c12b2042e743e776043a0d1a2573229ae648f7e66a80cfb26fc27e2a9eb59b55932d44c817 + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.9": version: 7.25.9 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.9" @@ -1554,6 +1585,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.23.2": + version: 7.28.0 + resolution: "@babel/traverse@npm:7.28.0" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.0" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.28.0" + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.0" + debug: "npm:^4.3.1" + checksum: 10/c1c24b12b6cb46241ec5d11ddbd2989d6955c282715cbd8ee91a09fe156b3bdb0b88353ac33329c2992113e3dfb5198f616c834f8805bb3fa85da1f864bec5f3 + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.5, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.27.6, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": version: 7.27.6 resolution: "@babel/types@npm:7.27.6" @@ -1564,6 +1610,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/types@npm:7.28.0" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + checksum: 10/2f28b84efb5005d1e85fc3944219c284400c42aeefc1f6e10500a74fed43b3dfb4f9e349a5d6e0e3fc24f5d241c513b30ef00ede2885535ce7a0a4e111c2098e + languageName: node + linkType: hard + "@bcoe/v8-coverage@npm:^0.2.3": version: 0.2.3 resolution: "@bcoe/v8-coverage@npm:0.2.3" @@ -3384,6 +3440,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/gen-mapping@npm:^0.3.12": + version: 0.3.12 + resolution: "@jridgewell/gen-mapping@npm:0.3.12" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10/151667531566417a940d4dd0a319724979f7a90b9deb9f1617344e1183887d78c835bc1a9209c1ee10fc8a669cdd7ac8120a43a2b6bc8d0d5dd18a173059ff4b + languageName: node + linkType: hard + "@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0": version: 3.1.2 resolution: "@jridgewell/resolve-uri@npm:3.1.2" @@ -3435,6 +3501,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:^0.3.28": + version: 0.3.29 + resolution: "@jridgewell/trace-mapping@npm:0.3.29" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10/64e1ce0dc3a9e56b0118eaf1b2f50746fd59a36de37516cc6855b5370d5f367aa8229e1237536d738262e252c70ee229619cb04e3f3b822146ee3eb1b7ab297f + languageName: node + linkType: hard + "@js-sdsl/ordered-map@npm:^4.4.2": version: 4.4.2 resolution: "@js-sdsl/ordered-map@npm:4.4.2" @@ -3576,7 +3652,7 @@ __metadata: languageName: node linkType: hard -"@mapbox/node-pre-gyp@npm:^1.0.11": +"@mapbox/node-pre-gyp@npm:^1.0.10, @mapbox/node-pre-gyp@npm:^1.0.11": version: 1.0.11 resolution: "@mapbox/node-pre-gyp@npm:1.0.11" dependencies: @@ -6895,21 +6971,21 @@ __metadata: "@rocket.chat/tracing": "workspace:^" "@rocket.chat/tsconfig": "workspace:*" "@types/bcrypt": "npm:^5.0.2" - "@types/gc-stats": "npm:^1.4.3" "@types/node": "npm:~22.16.1" "@types/polka": "npm:^0.5.7" + "@types/prometheus-gc-stats": "npm:^0.6.4" bcrypt: "npm:^5.1.1" ejson: "npm:^2.2.3" eslint: "npm:~8.45.0" event-loop-stats: "npm:^1.4.1" eventemitter3: "npm:^5.0.1" - gc-stats: "npm:^1.4.1" mem: "npm:^8.1.1" moleculer: "npm:^0.14.35" mongodb: "npm:6.10.0" nats: "npm:^2.28.2" pino: "npm:^8.21.0" polka: "npm:^0.5.2" + prometheus-gc-stats: "npm:^1.1.0" ts-node: "npm:^10.9.2" typescript: "npm:~5.8.3" uuid: "npm:^11.0.3" @@ -7031,20 +7107,20 @@ __metadata: "@rocket.chat/string-helpers": "npm:~0.31.25" "@rocket.chat/tracing": "workspace:^" "@rocket.chat/tsconfig": "workspace:*" - "@types/gc-stats": "npm:^1.4.3" "@types/node": "npm:~22.16.1" "@types/polka": "npm:^0.5.7" + "@types/prometheus-gc-stats": "npm:^0.6.4" ejson: "npm:^2.2.3" eslint: "npm:~8.45.0" event-loop-stats: "npm:^1.4.1" eventemitter3: "npm:^5.0.1" - gc-stats: "npm:^1.4.1" mem: "npm:^8.1.1" moleculer: "npm:^0.14.35" mongodb: "npm:6.10.0" nats: "npm:^2.28.2" pino: "npm:^8.21.0" polka: "npm:^0.5.2" + prometheus-gc-stats: "npm:^1.1.0" ts-node: "npm:^10.9.2" typescript: "npm:~5.8.3" languageName: unknown @@ -7204,9 +7280,9 @@ __metadata: "@rocket.chat/string-helpers": "npm:~0.31.25" "@rocket.chat/tracing": "workspace:^" "@types/ejson": "npm:^2.2.2" - "@types/gc-stats": "npm:^1.4.3" "@types/node": "npm:~22.16.1" "@types/polka": "npm:^0.5.7" + "@types/prometheus-gc-stats": "npm:^0.6.4" "@types/underscore": "npm:^1.13.0" "@types/uuid": "npm:^10.0.0" "@types/ws": "npm:^8.5.13" @@ -7215,7 +7291,6 @@ __metadata: eslint: "npm:~8.45.0" event-loop-stats: "npm:^1.4.1" eventemitter3: "npm:^5.0.1" - gc-stats: "npm:^1.4.1" jaeger-client: "npm:^3.19.0" mem: "npm:^8.1.1" moleculer: "npm:^0.14.35" @@ -7224,6 +7299,7 @@ __metadata: pino: "npm:^8.21.0" pino-pretty: "npm:^7.6.1" polka: "npm:^0.5.2" + prometheus-gc-stats: "npm:^1.1.0" sharp: "npm:^0.33.5" ts-node: "npm:^10.9.2" typescript: "npm:~5.8.3" @@ -8147,7 +8223,7 @@ __metadata: postis: "npm:^2.2.0" prettier: "npm:~3.3.3" prom-client: "npm:^14.2.0" - prometheus-gc-stats: "npm:^0.6.5" + prometheus-gc-stats: "npm:^1.1.0" proxy-from-env: "npm:^1.1.0" proxyquire: "npm:^2.1.3" psl: "npm:^1.10.0" @@ -8375,17 +8451,16 @@ __metadata: "@rocket.chat/tools": "workspace:^" "@rocket.chat/tracing": "workspace:^" "@rocket.chat/tsconfig": "workspace:*" - "@types/gc-stats": "npm:^1.4.3" "@types/i18next-sprintf-postprocessor": "npm:^0.2.3" "@types/node": "npm:~22.16.1" "@types/polka": "npm:^0.5.7" + "@types/prometheus-gc-stats": "npm:^0.6.4" "@types/react": "npm:~18.3.23" ejson: "npm:^2.2.3" emoji-toolkit: "npm:^7.0.1" eslint: "npm:~8.45.0" event-loop-stats: "npm:^1.4.1" eventemitter3: "npm:^5.0.1" - gc-stats: "npm:^1.4.1" i18next: "npm:~23.4.9" i18next-sprintf-postprocessor: "npm:^0.2.2" mem: "npm:^8.1.1" @@ -8396,6 +8471,7 @@ __metadata: nats: "npm:^2.28.2" pino: "npm:^8.21.0" polka: "npm:^0.5.2" + prometheus-gc-stats: "npm:^1.1.0" react: "npm:~18.3.1" ts-node: "npm:^10.9.2" typescript: "npm:~5.8.3" @@ -8527,20 +8603,20 @@ __metadata: "@rocket.chat/string-helpers": "npm:~0.31.25" "@rocket.chat/tracing": "workspace:^" "@rocket.chat/tsconfig": "workspace:*" - "@types/gc-stats": "npm:^1.4.3" "@types/node": "npm:~22.16.1" "@types/polka": "npm:^0.5.7" + "@types/prometheus-gc-stats": "npm:^0.6.4" ejson: "npm:^2.2.3" eslint: "npm:~8.45.0" event-loop-stats: "npm:^1.4.1" eventemitter3: "npm:^5.0.1" - gc-stats: "npm:^1.4.1" mem: "npm:^8.1.1" moleculer: "npm:^0.14.35" mongodb: "npm:6.10.0" nats: "npm:^2.28.2" pino: "npm:^8.21.0" polka: "npm:^0.5.2" + prometheus-gc-stats: "npm:^1.1.0" ts-node: "npm:^10.9.2" typescript: "npm:~5.8.3" languageName: unknown @@ -8592,15 +8668,14 @@ __metadata: "@rocket.chat/omnichannel-services": "workspace:^" "@rocket.chat/tracing": "workspace:^" "@rocket.chat/tsconfig": "workspace:*" - "@types/gc-stats": "npm:^1.4.3" "@types/node": "npm:~22.16.1" "@types/polka": "npm:^0.5.7" + "@types/prometheus-gc-stats": "npm:^0.6.4" ejson: "npm:^2.2.3" emoji-toolkit: "npm:^7.0.1" eslint: "npm:~8.45.0" event-loop-stats: "npm:^1.4.1" eventemitter3: "npm:^5.0.1" - gc-stats: "npm:^1.4.1" mem: "npm:^8.1.1" moleculer: "npm:^0.14.35" moment-timezone: "npm:^0.5.48" @@ -8609,6 +8684,7 @@ __metadata: nats: "npm:^2.28.2" pino: "npm:^8.21.0" polka: "npm:^0.5.2" + prometheus-gc-stats: "npm:^1.1.0" ts-node: "npm:^10.9.2" typescript: "npm:~5.8.3" languageName: unknown @@ -8781,20 +8857,20 @@ __metadata: "@rocket.chat/tracing": "workspace:^" "@rocket.chat/tsconfig": "workspace:*" "@types/bcrypt": "npm:^5.0.2" - "@types/gc-stats": "npm:^1.4.3" "@types/node": "npm:~22.16.1" "@types/polka": "npm:^0.5.7" + "@types/prometheus-gc-stats": "npm:^0.6.4" ejson: "npm:^2.2.3" eslint: "npm:~8.45.0" event-loop-stats: "npm:^1.4.1" eventemitter3: "npm:^5.0.1" - gc-stats: "npm:^1.4.1" mem: "npm:^8.1.1" moleculer: "npm:^0.14.35" mongodb: "npm:6.10.0" nats: "npm:^2.28.2" pino: "npm:^8.21.0" polka: "npm:^0.5.2" + prometheus-gc-stats: "npm:^1.1.0" ts-node: "npm:^10.9.2" typescript: "npm:~5.8.3" languageName: unknown @@ -9508,6 +9584,17 @@ __metadata: languageName: node linkType: hard +"@sematext/gc-stats@npm:^1.5.9": + version: 1.5.9 + resolution: "@sematext/gc-stats@npm:1.5.9" + dependencies: + "@mapbox/node-pre-gyp": "npm:^1.0.10" + depcheck: "npm:^1.4.3" + nan: "npm:^2.17.0" + checksum: 10/da403862a9c4c5177f95831305d1bf90dc39a915fc010d12f35e3b5f33fa43bfe1ebb65b1f459c7b250761ca2d4037ea29c66e00e75ce085346d714b973494c2 + languageName: node + linkType: hard + "@shikijs/engine-oniguruma@npm:^3.6.0": version: 3.6.0 resolution: "@shikijs/engine-oniguruma@npm:3.6.0" @@ -11374,15 +11461,6 @@ __metadata: languageName: node linkType: hard -"@types/gc-stats@npm:^1.4.3": - version: 1.4.3 - resolution: "@types/gc-stats@npm:1.4.3" - dependencies: - "@types/node": "npm:*" - checksum: 10/983ad3841a1f62a1014e4c0becf81d258f07dc44849598079168df6f4ea293eb8abef7896b6847b9dd68e61b4628525279950b3bb4a75c045b8d461cbaaef3e9 - languageName: node - linkType: hard - "@types/geojson@npm:*": version: 7946.0.10 resolution: "@types/geojson@npm:7946.0.10" @@ -11810,7 +11888,7 @@ __metadata: languageName: node linkType: hard -"@types/minimatch@npm:*": +"@types/minimatch@npm:*, @types/minimatch@npm:^3.0.3": version: 3.0.5 resolution: "@types/minimatch@npm:3.0.5" checksum: 10/c41d136f67231c3131cf1d4ca0b06687f4a322918a3a5adddc87ce90ed9dbd175a3610adee36b106ae68c0b92c637c35e02b58c8a56c424f71d30993ea220b92 @@ -13057,6 +13135,63 @@ __metadata: languageName: node linkType: hard +"@vue/compiler-core@npm:3.5.17": + version: 3.5.17 + resolution: "@vue/compiler-core@npm:3.5.17" + dependencies: + "@babel/parser": "npm:^7.27.5" + "@vue/shared": "npm:3.5.17" + entities: "npm:^4.5.0" + estree-walker: "npm:^2.0.2" + source-map-js: "npm:^1.2.1" + checksum: 10/b7900f0498cc1f0496e003e0e783ae09ad3fde25258ac635372b704533a14105a04d690b97d196ded5f2cf04ced078a59a53533242acfdaa4508432296372f54 + languageName: node + linkType: hard + +"@vue/compiler-dom@npm:3.5.17": + version: 3.5.17 + resolution: "@vue/compiler-dom@npm:3.5.17" + dependencies: + "@vue/compiler-core": "npm:3.5.17" + "@vue/shared": "npm:3.5.17" + checksum: 10/4370ce4c31c578ec864e25eddbeac23b9498036912a82ba26545346dde4cfc378624bda2865db5104fd6a57321de6f527525af77cd5f83f7d1cd67b00bf1c6de + languageName: node + linkType: hard + +"@vue/compiler-sfc@npm:^3.3.4": + version: 3.5.17 + resolution: "@vue/compiler-sfc@npm:3.5.17" + dependencies: + "@babel/parser": "npm:^7.27.5" + "@vue/compiler-core": "npm:3.5.17" + "@vue/compiler-dom": "npm:3.5.17" + "@vue/compiler-ssr": "npm:3.5.17" + "@vue/shared": "npm:3.5.17" + estree-walker: "npm:^2.0.2" + magic-string: "npm:^0.30.17" + postcss: "npm:^8.5.6" + source-map-js: "npm:^1.2.1" + checksum: 10/32a0e611615e1a9f553a3744084d15b388a48ec4218d1159ab5bf57897e38779fe408fc4e63ed43c134134e301d6a32a812f4e3f58a9c7e0fddba9d96f86a537 + languageName: node + linkType: hard + +"@vue/compiler-ssr@npm:3.5.17": + version: 3.5.17 + resolution: "@vue/compiler-ssr@npm:3.5.17" + dependencies: + "@vue/compiler-dom": "npm:3.5.17" + "@vue/shared": "npm:3.5.17" + checksum: 10/ed92e187d7fd29350c6a267a0ca8989f626b1b90e46ab2dbf9492a7164c4b32a7fd7fb5001dd43de0200c5f4cd3b3152d5cd4f55e39fe6f9837a30ce944876b1 + languageName: node + linkType: hard + +"@vue/shared@npm:3.5.17": + version: 3.5.17 + resolution: "@vue/shared@npm:3.5.17" + checksum: 10/d33b7435df4af16586679e713a0c15abb8c79a49067f558cbea6645d69f64ee2fd5b669a834abce2a084cb24a0d35a4a20164ba54625fbab70b3cc06b6913f72 + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.14.1": version: 1.14.1 resolution: "@webassemblyjs/ast@npm:1.14.1" @@ -13802,6 +13937,13 @@ __metadata: languageName: node linkType: hard +"array-differ@npm:^3.0.0": + version: 3.0.0 + resolution: "array-differ@npm:3.0.0" + checksum: 10/117edd9df5c1530bd116c6e8eea891d4bd02850fd89b1b36e532b6540e47ca620a373b81feca1c62d1395d9ae601516ba538abe5e8172d41091da2c546b05fb7 + languageName: node + linkType: hard + "array-flatten@npm:1.1.1": version: 1.1.1 resolution: "array-flatten@npm:1.1.1" @@ -13962,7 +14104,7 @@ __metadata: languageName: node linkType: hard -"arrify@npm:^2.0.0": +"arrify@npm:^2.0.0, arrify@npm:^2.0.1": version: 2.0.1 resolution: "arrify@npm:2.0.1" checksum: 10/067c4c1afd182806a82e4c1cb8acee16ab8b5284fbca1ce29408e6e91281c36bb5b612f6ddfbd40a0f7a7e0c75bf2696eb94c027f6e328d6e9c52465c98e4209 @@ -15455,6 +15597,13 @@ __metadata: languageName: node linkType: hard +"callsite@npm:^1.0.0": + version: 1.0.0 + resolution: "callsite@npm:1.0.0" + checksum: 10/39fc89ef9dbee7d5491bc69034fc16fbb8876a73456f831cc27060b5828e94357bb6705e0127a6d0182d79b03dbdb0ef88223d0b599c26667c871c89b30eb681 + languageName: node + linkType: hard + "callsites@npm:^3.0.0, callsites@npm:^3.1.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" @@ -16581,6 +16730,19 @@ __metadata: languageName: node linkType: hard +"cosmiconfig@npm:^7.1.0": + version: 7.1.0 + resolution: "cosmiconfig@npm:7.1.0" + dependencies: + "@types/parse-json": "npm:^4.0.0" + import-fresh: "npm:^3.2.1" + parse-json: "npm:^5.0.0" + path-type: "npm:^4.0.0" + yaml: "npm:^1.10.0" + checksum: 10/03600bb3870c80ed151b7b706b99a1f6d78df8f4bdad9c95485072ea13358ef294b13dd99f9e7bf4cc0b43bcd3599d40df7e648750d21c2f6817ca2cd687e071 + languageName: node + linkType: hard + "cosmiconfig@npm:^9.0.0": version: 9.0.0 resolution: "cosmiconfig@npm:9.0.0" @@ -17730,6 +17892,39 @@ __metadata: languageName: node linkType: hard +"depcheck@npm:^1.4.3": + version: 1.4.7 + resolution: "depcheck@npm:1.4.7" + dependencies: + "@babel/parser": "npm:^7.23.0" + "@babel/traverse": "npm:^7.23.2" + "@vue/compiler-sfc": "npm:^3.3.4" + callsite: "npm:^1.0.0" + camelcase: "npm:^6.3.0" + cosmiconfig: "npm:^7.1.0" + debug: "npm:^4.3.4" + deps-regex: "npm:^0.2.0" + findup-sync: "npm:^5.0.0" + ignore: "npm:^5.2.4" + is-core-module: "npm:^2.12.0" + js-yaml: "npm:^3.14.1" + json5: "npm:^2.2.3" + lodash: "npm:^4.17.21" + minimatch: "npm:^7.4.6" + multimatch: "npm:^5.0.0" + please-upgrade-node: "npm:^3.2.0" + readdirp: "npm:^3.6.0" + require-package-name: "npm:^2.0.1" + resolve: "npm:^1.22.3" + resolve-from: "npm:^5.0.0" + semver: "npm:^7.5.4" + yargs: "npm:^16.2.0" + bin: + depcheck: bin/depcheck.js + checksum: 10/e35e87517348a3fd678f9ed7324cb96aff350c65cd0ede7da5be303f03144ad66a18d03ff2b52531cd7900b2ca83f8c2a4fac3295e00db8f701f92fb33744b78 + languageName: node + linkType: hard + "depd@npm:2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" @@ -17751,6 +17946,13 @@ __metadata: languageName: node linkType: hard +"deps-regex@npm:^0.2.0": + version: 0.2.0 + resolution: "deps-regex@npm:0.2.0" + checksum: 10/d8eeb89727037f2ae680a619f8eefbc8475d21c3d5273e2bbcb9838aabd1c93fd9e011f51bcda5bd65f042921c1bc156119d9a8a2f3539aa4009950b8f9c79b3 + languageName: node + linkType: hard + "deps-sort@npm:^2.0.0": version: 2.0.1 resolution: "deps-sort@npm:2.0.1" @@ -17798,6 +18000,13 @@ __metadata: languageName: node linkType: hard +"detect-file@npm:^1.0.0": + version: 1.0.0 + resolution: "detect-file@npm:1.0.0" + checksum: 10/1861e4146128622e847abe0e1ed80fef01e78532665858a792267adf89032b7a9c698436137707fcc6f02956c2a6a0052d6a0cef5be3d4b76b1ff0da88e2158a + languageName: node + linkType: hard + "detect-indent@npm:^6.0.0": version: 6.1.0 resolution: "detect-indent@npm:6.1.0" @@ -19307,6 +19516,13 @@ __metadata: languageName: node linkType: hard +"estree-walker@npm:^2.0.2": + version: 2.0.2 + resolution: "estree-walker@npm:2.0.2" + checksum: 10/b02109c5d46bc2ed47de4990eef770f7457b1159a229f0999a09224d2b85ffeed2d7679cffcff90aeb4448e94b0168feb5265b209cdec29aad50a3d6e93d21e2 + languageName: node + linkType: hard + "estree-walker@npm:^3.0.3": version: 3.0.3 resolution: "estree-walker@npm:3.0.3" @@ -19554,6 +19770,15 @@ __metadata: languageName: node linkType: hard +"expand-tilde@npm:^2.0.0, expand-tilde@npm:^2.0.2": + version: 2.0.2 + resolution: "expand-tilde@npm:2.0.2" + dependencies: + homedir-polyfill: "npm:^1.0.1" + checksum: 10/2efe6ed407d229981b1b6ceb552438fbc9e5c7d6a6751ad6ced3e0aa5cf12f0b299da695e90d6c2ac79191b5c53c613e508f7149e4573abfbb540698ddb7301a + languageName: node + linkType: hard + "expect-playwright@npm:^0.8.0": version: 0.8.0 resolution: "expect-playwright@npm:0.8.0" @@ -20203,6 +20428,18 @@ __metadata: languageName: node linkType: hard +"findup-sync@npm:^5.0.0": + version: 5.0.0 + resolution: "findup-sync@npm:5.0.0" + dependencies: + detect-file: "npm:^1.0.0" + is-glob: "npm:^4.0.3" + micromatch: "npm:^4.0.4" + resolve-dir: "npm:^1.0.1" + checksum: 10/576716c77a0e8330b17ae9cba27d1fda8907c8cda7bf33a47f1999e16e089bfc6df4dd62933e0760f430736183c054348c34aa45dd882d49c8c098f55b89ee1d + languageName: node + linkType: hard + "finity@npm:^0.5.4": version: 0.5.4 resolution: "finity@npm:0.5.4" @@ -20611,17 +20848,6 @@ __metadata: languageName: node linkType: hard -"gc-stats@npm:^1.4.0, gc-stats@npm:^1.4.1": - version: 1.4.1 - resolution: "gc-stats@npm:1.4.1" - dependencies: - nan: "npm:^2.18.0" - node-gyp: "npm:latest" - node-gyp-build: "npm:^4.8.0" - checksum: 10/acd1d74b71fc6ebfdb13eb84924abc55d8bc8b523335e5c8b0aa2ba15acd94bf80862913c1cee20b9f235c871d310b7271477df592a01eb964a3562e3105bf53 - languageName: node - linkType: hard - "gcp-metadata@npm:^6.1.0": version: 6.1.0 resolution: "gcp-metadata@npm:6.1.0" @@ -20913,6 +21139,17 @@ __metadata: languageName: node linkType: hard +"global-modules@npm:^1.0.0": + version: 1.0.0 + resolution: "global-modules@npm:1.0.0" + dependencies: + global-prefix: "npm:^1.0.1" + is-windows: "npm:^1.0.1" + resolve-dir: "npm:^1.0.0" + checksum: 10/e4031a01c0c7401349bb69e1499c7268d636552b16374c0002d677c7a6185da6782a2927a7a3a7c046eb7be97cd26b3c7b1b736f9818ecc7ac09e9d61449065e + languageName: node + linkType: hard + "global-modules@npm:^2.0.0": version: 2.0.0 resolution: "global-modules@npm:2.0.0" @@ -20934,6 +21171,19 @@ __metadata: languageName: node linkType: hard +"global-prefix@npm:^1.0.1": + version: 1.0.2 + resolution: "global-prefix@npm:1.0.2" + dependencies: + expand-tilde: "npm:^2.0.2" + homedir-polyfill: "npm:^1.0.1" + ini: "npm:^1.3.4" + is-windows: "npm:^1.0.1" + which: "npm:^1.2.14" + checksum: 10/68cf78f81cd85310095ca1f0ec22dd5f43a1059646b2c7b3fc4a7c9ce744356e66ca833adda4e5753e38021847aaec393a159a029ba2d257c08ccb3f00ca2899 + languageName: node + linkType: hard + "global-prefix@npm:^3.0.0": version: 3.0.0 resolution: "global-prefix@npm:3.0.0" @@ -21442,7 +21692,7 @@ __metadata: languageName: node linkType: hard -"homedir-polyfill@npm:^1.0.0": +"homedir-polyfill@npm:^1.0.0, homedir-polyfill@npm:^1.0.1": version: 1.0.3 resolution: "homedir-polyfill@npm:1.0.3" dependencies: @@ -21980,6 +22230,13 @@ __metadata: languageName: node linkType: hard +"ignore@npm:^5.2.4": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10/cceb6a457000f8f6a50e1196429750d782afce5680dd878aa4221bd79972d68b3a55b4b1458fc682be978f4d3c6a249046aa0880637367216444ab7b014cfc98 + languageName: node + linkType: hard + "ignore@npm:^6.0.2": version: 6.0.2 resolution: "ignore@npm:6.0.2" @@ -22527,6 +22784,15 @@ __metadata: languageName: node linkType: hard +"is-core-module@npm:^2.12.0, is-core-module@npm:^2.16.0": + version: 2.16.1 + resolution: "is-core-module@npm:2.16.1" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10/452b2c2fb7f889cbbf7e54609ef92cf6c24637c568acc7e63d166812a0fb365ae8a504c333a29add8bdb1686704068caa7f4e4b639b650dde4f00a038b8941fb + languageName: node + linkType: hard + "is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1, is-core-module@npm:^2.5.0": version: 2.15.1 resolution: "is-core-module@npm:2.15.1" @@ -23008,7 +23274,7 @@ __metadata: languageName: node linkType: hard -"is-windows@npm:^1.0.0, is-windows@npm:^1.0.2": +"is-windows@npm:^1.0.0, is-windows@npm:^1.0.1, is-windows@npm:^1.0.2": version: 1.0.2 resolution: "is-windows@npm:1.0.2" checksum: 10/438b7e52656fe3b9b293b180defb4e448088e7023a523ec21a91a80b9ff8cdb3377ddb5b6e60f7c7de4fa8b63ab56e121b6705fe081b3cf1b828b0a380009ad7 @@ -24389,7 +24655,7 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^3.13.1, js-yaml@npm:^3.2.7, js-yaml@npm:^3.6.1": +"js-yaml@npm:^3.13.1, js-yaml@npm:^3.14.1, js-yaml@npm:^3.2.7, js-yaml@npm:^3.6.1": version: 3.14.1 resolution: "js-yaml@npm:3.14.1" dependencies: @@ -25450,6 +25716,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.17": + version: 0.30.17 + resolution: "magic-string@npm:0.30.17" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + checksum: 10/2f71af2b0afd78c2e9012a29b066d2c8ba45a9cd0c8070f7fd72de982fb1c403b4e3afdb1dae00691d56885ede66b772ef6bedf765e02e3a7066208fe2fec4aa + languageName: node + linkType: hard + "magic-string@npm:^0.30.5": version: 0.30.11 resolution: "magic-string@npm:0.30.11" @@ -26170,6 +26445,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^7.4.6": + version: 7.4.6 + resolution: "minimatch@npm:7.4.6" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10/0046ba1161ac6414bde1b07c440792ebcdb2ed93e6714c85c73974332b709b7e692801550bc9da22028a8613407b3f13861e17dd0dd44f4babdeacd44950430b + languageName: node + linkType: hard + "minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": version: 9.0.5 resolution: "minimatch@npm:9.0.5" @@ -26673,6 +26957,19 @@ __metadata: languageName: node linkType: hard +"multimatch@npm:^5.0.0": + version: 5.0.0 + resolution: "multimatch@npm:5.0.0" + dependencies: + "@types/minimatch": "npm:^3.0.3" + array-differ: "npm:^3.0.0" + array-union: "npm:^2.1.0" + arrify: "npm:^2.0.1" + minimatch: "npm:^3.0.4" + checksum: 10/82c8030a53af965cab48da22f1b0f894ef99e16ee680dabdfbd38d2dfacc3c8208c475203d747afd9e26db44118ed0221d5a0d65268c864f06d6efc7ac6df812 + languageName: node + linkType: hard + "mute-stream@npm:0.0.8": version: 0.0.8 resolution: "mute-stream@npm:0.0.8" @@ -26680,7 +26977,7 @@ __metadata: languageName: node linkType: hard -"nan@npm:^2.14.0, nan@npm:^2.18.0": +"nan@npm:^2.14.0": version: 2.20.0 resolution: "nan@npm:2.20.0" dependencies: @@ -26689,6 +26986,15 @@ __metadata: languageName: node linkType: hard +"nan@npm:^2.17.0": + version: 2.23.0 + resolution: "nan@npm:2.23.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10/9822b384189769ebb9d69160d9f5276bb2644467fe6a8e23ae849d607da5b34940f9abf03605f5f747395c17b0dac5e470aea29e72b4988918edb082d78b5858 + languageName: node + linkType: hard + "nanoid@npm:3.3.1": version: 3.3.1 resolution: "nanoid@npm:3.3.1" @@ -26698,6 +27004,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10/73b5afe5975a307aaa3c95dfe3334c52cdf9ae71518176895229b8d65ab0d1c0417dd081426134eb7571c055720428ea5d57c645138161e7d10df80815527c48 + languageName: node + linkType: hard + "nanoid@npm:^3.3.7, nanoid@npm:^3.3.8": version: 3.3.8 resolution: "nanoid@npm:3.3.8" @@ -26898,17 +27213,6 @@ __metadata: languageName: node linkType: hard -"node-gyp-build@npm:^4.8.0": - version: 4.8.2 - resolution: "node-gyp-build@npm:4.8.2" - bin: - node-gyp-build: bin.js - node-gyp-build-optional: optional.js - node-gyp-build-test: build-test.js - checksum: 10/e3a365eed7a2d950864a1daa34527588c16fe43ae189d0aeb8fd1dfec91ba42a0e1b499322bff86c2832029fec4f5901bf26e32005e1e17a781dcd5177b6a657 - languageName: node - linkType: hard - "node-gyp@npm:^10.2.0": version: 10.3.1 resolution: "node-gyp@npm:10.3.1" @@ -28573,6 +28877,15 @@ __metadata: languageName: node linkType: hard +"please-upgrade-node@npm:^3.2.0": + version: 3.2.0 + resolution: "please-upgrade-node@npm:3.2.0" + dependencies: + semver-compare: "npm:^1.0.0" + checksum: 10/d87c41581a2a022fbe25965a97006238cd9b8cbbf49b39f78d262548149a9d30bd2bdf35fec3d810e0001e630cd46ef13c7e19c389dea8de7e64db271a2381bb + languageName: node + linkType: hard + "pngquant-bin@npm:^6.0.0": version: 6.0.1 resolution: "pngquant-bin@npm:6.0.1" @@ -29298,6 +29611,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.5.6": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10/9e4fbe97574091e9736d0e82a591e29aa100a0bf60276a926308f8c57249698935f35c5d2f4e80de778d0cbb8dcffab4f383d85fd50c5649aca421c3df729b86 + languageName: node + linkType: hard + "postcss@npm:~8.4.49": version: 8.4.49 resolution: "postcss@npm:8.4.49" @@ -29545,18 +29869,18 @@ __metadata: languageName: node linkType: hard -"prometheus-gc-stats@npm:^0.6.5": - version: 0.6.5 - resolution: "prometheus-gc-stats@npm:0.6.5" +"prometheus-gc-stats@npm:^1.1.0": + version: 1.1.0 + resolution: "prometheus-gc-stats@npm:1.1.0" dependencies: - gc-stats: "npm:^1.4.0" + "@sematext/gc-stats": "npm:^1.5.9" optional: "npm:^0.1.3" peerDependencies: - prom-client: ">= 10 <= 14" + prom-client: ">= 10 <= 15" dependenciesMeta: - gc-stats: + "@sematext/gc-stats": optional: true - checksum: 10/9fb10f8eab020f823d3d5cb63ce54afffec29225d3625cbb4dc30f1d80b01d5dc498a93c840ab7fdebf84e374e7ecd0b1b5f648308fbce8e00eb46c1b11c09ce + checksum: 10/2263d16b218ba5419c4a717e9eb7272453af8b966bab86382ede91964894d2a4fd2ba8555c3fd075c28437b0fba5ceb77318dd6ba85dde6a1a9d596c5034ca02 languageName: node linkType: hard @@ -30613,14 +30937,7 @@ __metadata: languageName: node linkType: hard -"readdirp@npm:^4.0.1": - version: 4.0.2 - resolution: "readdirp@npm:4.0.2" - checksum: 10/4ef93103307c7d5e42e78ecf201db58c984c4d66882a27c956250478b49c2444b1ff6aea8ce0f5e4157b2c07ce2fe870ad16c92ebd7c6ff30391ded6e42b9873 - languageName: node - linkType: hard - -"readdirp@npm:~3.6.0": +"readdirp@npm:^3.6.0, readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" dependencies: @@ -30629,6 +30946,13 @@ __metadata: languageName: node linkType: hard +"readdirp@npm:^4.0.1": + version: 4.0.2 + resolution: "readdirp@npm:4.0.2" + checksum: 10/4ef93103307c7d5e42e78ecf201db58c984c4d66882a27c956250478b49c2444b1ff6aea8ce0f5e4157b2c07ce2fe870ad16c92ebd7c6ff30391ded6e42b9873 + languageName: node + linkType: hard + "real-require@npm:^0.2.0": version: 0.2.0 resolution: "real-require@npm:0.2.0" @@ -30983,6 +31307,13 @@ __metadata: languageName: node linkType: hard +"require-package-name@npm:^2.0.1": + version: 2.0.1 + resolution: "require-package-name@npm:2.0.1" + checksum: 10/3332d4eec10a730627ca20f37a8a7d57badd9e8953f238472aa457b0084907f86ca5b2af94694a0c8bb2e1101bdb3ed6ddc964d2044b040fe076a9bf5b19755f + languageName: node + linkType: hard + "requireindex@npm:1.2.0": version: 1.2.0 resolution: "requireindex@npm:1.2.0" @@ -31016,6 +31347,16 @@ __metadata: languageName: node linkType: hard +"resolve-dir@npm:^1.0.0, resolve-dir@npm:^1.0.1": + version: 1.0.1 + resolution: "resolve-dir@npm:1.0.1" + dependencies: + expand-tilde: "npm:^2.0.0" + global-modules: "npm:^1.0.0" + checksum: 10/ef736b8ed60d6645c3b573da17d329bfb50ec4e1d6c5ffd6df49e3497acef9226f9810ea6823b8ece1560e01dcb13f77a9f6180d4f242d00cc9a8f4de909c65c + languageName: node + linkType: hard + "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -31063,6 +31404,19 @@ __metadata: languageName: node linkType: hard +"resolve@npm:^1.22.3": + version: 1.22.10 + resolution: "resolve@npm:1.22.10" + dependencies: + is-core-module: "npm:^2.16.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10/0a398b44da5c05e6e421d70108822c327675febb880eebe905587628de401854c61d5df02866ff34fc4cb1173a51c9f0e84a94702738df3611a62e2acdc68181 + languageName: node + linkType: hard + "resolve@npm:^2.0.0-next.5": version: 2.0.0-next.5 resolution: "resolve@npm:2.0.0-next.5" @@ -31089,6 +31443,19 @@ __metadata: languageName: node linkType: hard +"resolve@patch:resolve@npm%3A^1.22.3#optional!builtin": + version: 1.22.10 + resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.16.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10/d4d878bfe3702d215ea23e75e0e9caf99468e3db76f5ca100d27ebdc527366fee3877e54bce7d47cc72ca8952fc2782a070d238bfa79a550eeb0082384c3b81a + languageName: node + linkType: hard + "resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin": version: 2.0.0-next.5 resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin::version=2.0.0-next.5&hash=c3c19d" @@ -31666,6 +32033,13 @@ __metadata: languageName: node linkType: hard +"semver-compare@npm:^1.0.0": + version: 1.0.0 + resolution: "semver-compare@npm:1.0.0" + checksum: 10/75f9c7a7786d1756f64b1429017746721e07bd7691bdad6368f7643885d3a98a27586777e9699456564f4844b407e9f186cc1d588a3f9c0be71310e517e942c3 + languageName: node + linkType: hard + "semver-regex@npm:^2.0.0": version: 2.0.0 resolution: "semver-regex@npm:2.0.0" @@ -35939,7 +36313,7 @@ __metadata: languageName: node linkType: hard -"which@npm:^1.2.0, which@npm:^1.2.12, which@npm:^1.3.1": +"which@npm:^1.2.0, which@npm:^1.2.12, which@npm:^1.2.14, which@npm:^1.3.1": version: 1.3.1 resolution: "which@npm:1.3.1" dependencies: @@ -36352,7 +36726,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:16.2.0": +"yargs@npm:16.2.0, yargs@npm:^16.2.0": version: 16.2.0 resolution: "yargs@npm:16.2.0" dependencies: From d90b1857855ec22a88fa2fc7e1db60ca8c036375 Mon Sep 17 00:00:00 2001 From: Tasso Date: Mon, 14 Jul 2025 12:34:49 -0300 Subject: [PATCH 29/33] Adapt `useOmnichannelRoomInfo` and `useRoomInfoEndpoint` --- .../client/hooks/useRoomInfoEndpoint.ts | 44 ++++++++++++++----- .../hooks/useOmnichannelPrioritiesMenu.tsx | 3 +- .../directory/chats/ChatInfo/ChatInfo.tsx | 2 +- .../ChatInfo/RoomEdit/RoomEditWithData.tsx | 2 +- .../hooks/useOmnichannelRoomInfo.tsx | 15 +++---- .../directory/utils/formatQueuedAt.ts | 2 +- 6 files changed, 44 insertions(+), 24 deletions(-) diff --git a/apps/meteor/client/hooks/useRoomInfoEndpoint.ts b/apps/meteor/client/hooks/useRoomInfoEndpoint.ts index 1e14af00c205d..6857e2590a069 100644 --- a/apps/meteor/client/hooks/useRoomInfoEndpoint.ts +++ b/apps/meteor/client/hooks/useRoomInfoEndpoint.ts @@ -1,25 +1,49 @@ -import type { IRoom } from '@rocket.chat/core-typings'; +import type { IRoom, ITeam, Serialized } from '@rocket.chat/core-typings'; import { useEndpoint, useUserId } from '@rocket.chat/ui-contexts'; +import type { UseQueryOptions } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query'; import { minutesToMilliseconds } from 'date-fns'; import { roomsQueryKeys } from '../lib/queryKeys'; -export const useRoomInfoEndpoint = (rid: IRoom['_id']) => { +type UseRoomInfoEndpointOptions< + TData = Serialized<{ + room: IRoom | undefined; + parent?: Pick; + team?: Pick; + }>, +> = Omit< + UseQueryOptions< + Serialized<{ + room: IRoom | undefined; + parent?: Pick; + team?: Pick; + }>, + { success: boolean; error: string }, + TData, + ReturnType + >, + 'queryKey' | 'queryFn' +>; + +export const useRoomInfoEndpoint = < + TData = Serialized<{ + room: IRoom | undefined; + parent?: Pick; + team?: Pick; + }>, +>( + rid: IRoom['_id'], + options?: UseRoomInfoEndpointOptions, +) => { const getRoomInfo = useEndpoint('GET', '/v1/rooms.info'); const uid = useUserId(); return useQuery({ queryKey: roomsQueryKeys.info(rid), queryFn: () => getRoomInfo({ roomId: rid }), gcTime: minutesToMilliseconds(15), - - retry: (count, error: { success: boolean; error: string }) => { - if (count > 2 || error.error === 'not-allowed') { - return false; - } - return true; - }, - + retry: (count, error: { success: boolean; error: string }) => count <= 2 && error.error !== 'not-allowed', enabled: !!uid, + ...options, }); }; diff --git a/apps/meteor/client/omnichannel/hooks/useOmnichannelPrioritiesMenu.tsx b/apps/meteor/client/omnichannel/hooks/useOmnichannelPrioritiesMenu.tsx index 6fec76e810936..8ebc5f4ca00d6 100644 --- a/apps/meteor/client/omnichannel/hooks/useOmnichannelPrioritiesMenu.tsx +++ b/apps/meteor/client/omnichannel/hooks/useOmnichannelPrioritiesMenu.tsx @@ -1,3 +1,4 @@ +import type { IRoom } from '@rocket.chat/core-typings'; import { LivechatPriorityWeight } from '@rocket.chat/core-typings'; import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; import { useEndpoint } from '@rocket.chat/ui-contexts'; @@ -10,7 +11,7 @@ import { roomsQueryKeys } from '../../lib/queryKeys'; import { dispatchToastMessage } from '../../lib/toast'; import { PRIORITY_ICONS } from '../priorities/PriorityIcon'; -export const useOmnichannelPrioritiesMenu = (rid: string) => { +export const useOmnichannelPrioritiesMenu = (rid: IRoom['_id']) => { const { t } = useTranslation(); const queryClient = useQueryClient(); const updateRoomPriority = useEndpoint('POST', '/v1/livechat/room/:rid/priority', { rid }); diff --git a/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatInfo.tsx b/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatInfo.tsx index 7ea876d6dd600..cf7f2db99b368 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatInfo.tsx +++ b/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatInfo.tsx @@ -54,7 +54,7 @@ function ChatInfo({ id, route }: ChatInfoProps) { livechatData, source, queuedAt, - } = room || { v: {} }; + } = room ?? {}; const routePath = useRoute(route || 'omnichannel-directory'); const canViewCustomFields = usePermission('view-livechat-room-customfields'); diff --git a/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/RoomEdit/RoomEditWithData.tsx b/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/RoomEdit/RoomEditWithData.tsx index 23db25d608534..ee1a3d3bf2253 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/RoomEdit/RoomEditWithData.tsx +++ b/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/RoomEdit/RoomEditWithData.tsx @@ -19,7 +19,7 @@ function RoomEditWithData({ id: roomId, reload, reloadInfo, onClose }: RoomEditW const { data: room, isLoading: isRoomLoading, isError: isRoomError } = useOmnichannelRoomInfo(roomId); const { _id: visitorId } = room?.v ?? {}; - const { data: visitor, isLoading: isVisitorLoading, isError: isVisitorError } = useVisitorInfo(visitorId, { enabled: !!visitorId }); + const { data: visitor, isLoading: isVisitorLoading, isError: isVisitorError } = useVisitorInfo(visitorId!, { enabled: !!visitorId }); if (isRoomLoading || isVisitorLoading) { return ; diff --git a/apps/meteor/client/views/omnichannel/directory/hooks/useOmnichannelRoomInfo.tsx b/apps/meteor/client/views/omnichannel/directory/hooks/useOmnichannelRoomInfo.tsx index 13a2e55a40e76..c9f2180504984 100644 --- a/apps/meteor/client/views/omnichannel/directory/hooks/useOmnichannelRoomInfo.tsx +++ b/apps/meteor/client/views/omnichannel/directory/hooks/useOmnichannelRoomInfo.tsx @@ -1,13 +1,8 @@ -import type { IOmnichannelRoom, Serialized } from '@rocket.chat/core-typings'; +import type { IOmnichannelRoom, IRoom, Serialized } from '@rocket.chat/core-typings'; import { useRoomInfoEndpoint } from '../../../../hooks/useRoomInfoEndpoint'; -export const useOmnichannelRoomInfo = (roomId: string) => { - const { data: roomData, ...props } = useRoomInfoEndpoint(roomId); - const room = roomData?.room as Serialized; - - return { - data: room, - ...props, - }; -}; +export const useOmnichannelRoomInfo = (rid: IRoom['_id']) => + useRoomInfoEndpoint(rid, { + select: (data) => (data.room as Serialized) ?? null, + }); diff --git a/apps/meteor/client/views/omnichannel/directory/utils/formatQueuedAt.ts b/apps/meteor/client/views/omnichannel/directory/utils/formatQueuedAt.ts index a7ebb24ffc152..a1df53b9ff179 100644 --- a/apps/meteor/client/views/omnichannel/directory/utils/formatQueuedAt.ts +++ b/apps/meteor/client/views/omnichannel/directory/utils/formatQueuedAt.ts @@ -1,7 +1,7 @@ import type { IOmnichannelRoom, Serialized } from '@rocket.chat/core-typings'; import moment from 'moment'; -export const formatQueuedAt = (room: Serialized) => { +export const formatQueuedAt = (room: Serialized | undefined) => { const { servedBy, closedAt, open, queuedAt, ts } = room || {}; const queueStartedAt = queuedAt || ts; From ee97498201f72a6caad1c1c9c34ec27aea2392e8 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 14 Jul 2025 13:13:18 -0300 Subject: [PATCH 30/33] Refactor `info` query key to remove redundant room ID from the key structure --- apps/meteor/client/lib/queryKeys.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/lib/queryKeys.ts b/apps/meteor/client/lib/queryKeys.ts index 05dc31945ab34..e823d8d83cada 100644 --- a/apps/meteor/client/lib/queryKeys.ts +++ b/apps/meteor/client/lib/queryKeys.ts @@ -9,7 +9,7 @@ export const roomsQueryKeys = { message: (rid: IRoom['_id'], mid: IMessage['_id']) => [...roomsQueryKeys.messages(rid), mid] as const, threads: (rid: IRoom['_id']) => [...roomsQueryKeys.room(rid), 'threads'] as const, roles: (rid: IRoom['_id']) => [...roomsQueryKeys.room(rid), 'roles'] as const, - info: (rid: IRoom['_id']) => [...roomsQueryKeys.room(rid), 'info', rid] as const, + info: (rid: IRoom['_id']) => [...roomsQueryKeys.room(rid), 'info'] as const, }; export const subscriptionsQueryKeys = { From 483730878726a53f0b2d5f628fe88e4ff9e5a25c Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 14 Jul 2025 13:13:28 -0300 Subject: [PATCH 31/33] Refactor `handlePriorityChange` to use a local function within `useMemo` --- .../hooks/useOmnichannelPrioritiesMenu.tsx | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/meteor/client/omnichannel/hooks/useOmnichannelPrioritiesMenu.tsx b/apps/meteor/client/omnichannel/hooks/useOmnichannelPrioritiesMenu.tsx index 8ebc5f4ca00d6..2477341565dfa 100644 --- a/apps/meteor/client/omnichannel/hooks/useOmnichannelPrioritiesMenu.tsx +++ b/apps/meteor/client/omnichannel/hooks/useOmnichannelPrioritiesMenu.tsx @@ -1,6 +1,5 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { LivechatPriorityWeight } from '@rocket.chat/core-typings'; -import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useQueryClient } from '@tanstack/react-query'; import { useMemo } from 'react'; @@ -18,17 +17,17 @@ export const useOmnichannelPrioritiesMenu = (rid: IRoom['_id']) => { const removeRoomPriority = useEndpoint('DELETE', '/v1/livechat/room/:rid/priority', { rid }); const { data: priorities } = useOmnichannelPriorities(); - const handlePriorityChange = useEffectEvent((priorityId: string) => async () => { - try { - priorityId ? await updateRoomPriority({ priorityId }) : await removeRoomPriority(); - queryClient.invalidateQueries({ queryKey: ['current-chats'] }); - queryClient.invalidateQueries({ queryKey: roomsQueryKeys.info(rid) }); - } catch (error) { - dispatchToastMessage({ type: 'error', message: error }); - } - }); - return useMemo(() => { + const handlePriorityChange = (priorityId: string) => async () => { + try { + priorityId ? await updateRoomPriority({ priorityId }) : await removeRoomPriority(); + queryClient.invalidateQueries({ queryKey: ['current-chats'] }); + queryClient.invalidateQueries({ queryKey: roomsQueryKeys.info(rid) }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + }; + const unprioritizedOption = { id: 'unprioritized', icon: PRIORITY_ICONS[LivechatPriorityWeight.NOT_SPECIFIED].iconName, @@ -50,5 +49,5 @@ export const useOmnichannelPrioritiesMenu = (rid: IRoom['_id']) => { }); return priorities.length ? [unprioritizedOption, ...options] : []; - }, [t, handlePriorityChange, priorities]); + }, [t, priorities, updateRoomPriority, removeRoomPriority, queryClient, rid]); }; From a7d598622796ce4a18164282e498dc11e98736fa Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 14 Jul 2025 14:42:51 -0300 Subject: [PATCH 32/33] Debug hooks --- apps/meteor/client/hooks/useRoomInfoEndpoint.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/meteor/client/hooks/useRoomInfoEndpoint.ts b/apps/meteor/client/hooks/useRoomInfoEndpoint.ts index 6857e2590a069..af72b067aa969 100644 --- a/apps/meteor/client/hooks/useRoomInfoEndpoint.ts +++ b/apps/meteor/client/hooks/useRoomInfoEndpoint.ts @@ -2,7 +2,7 @@ import type { IRoom, ITeam, Serialized } from '@rocket.chat/core-typings'; import { useEndpoint, useUserId } from '@rocket.chat/ui-contexts'; import type { UseQueryOptions } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query'; -import { minutesToMilliseconds } from 'date-fns'; +// import { minutesToMilliseconds } from 'date-fns'; import { roomsQueryKeys } from '../lib/queryKeys'; @@ -40,8 +40,12 @@ export const useRoomInfoEndpoint = < const uid = useUserId(); return useQuery({ queryKey: roomsQueryKeys.info(rid), - queryFn: () => getRoomInfo({ roomId: rid }), - gcTime: minutesToMilliseconds(15), + queryFn: async () => { + const result = await getRoomInfo({ roomId: rid }); + console.log('useRoomInfoEndpoint queryFn', roomsQueryKeys.info(rid), JSON.stringify(result, null, 2)); + return result; + }, + // gcTime: minutesToMilliseconds(15), retry: (count, error: { success: boolean; error: string }) => count <= 2 && error.error !== 'not-allowed', enabled: !!uid, ...options, From 683992192d465eefef09394a12d67cb619f58503 Mon Sep 17 00:00:00 2001 From: Tasso Date: Mon, 14 Jul 2025 19:44:35 -0300 Subject: [PATCH 33/33] Use true invalidation --- .../app/livechat/client/lib/stream/queueManager.ts | 8 +++++--- .../client/hooks/roomActions/useRoomInfoRoomAction.ts | 7 +++---- apps/meteor/client/hooks/useRoomInfoEndpoint.ts | 10 +++------- .../{ChatInfoRouter.tsx => ChatsContextualBar.tsx} | 0 4 files changed, 11 insertions(+), 14 deletions(-) rename apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/{ChatInfoRouter.tsx => ChatsContextualBar.tsx} (100%) diff --git a/apps/meteor/app/livechat/client/lib/stream/queueManager.ts b/apps/meteor/app/livechat/client/lib/stream/queueManager.ts index 67d02eb90c0e1..3652780fbf6ec 100644 --- a/apps/meteor/app/livechat/client/lib/stream/queueManager.ts +++ b/apps/meteor/app/livechat/client/lib/stream/queueManager.ts @@ -45,9 +45,11 @@ const processInquiryEvent = async (args: unknown): Promise => { }; const invalidateRoomQueries = async (rid: string) => { - await queryClient.invalidateQueries({ queryKey: ['rooms', { reference: rid, type: 'l' }] }); - queryClient.removeQueries({ queryKey: roomsQueryKeys.room(rid) }); - queryClient.removeQueries({ queryKey: roomsQueryKeys.info(rid) }); + await Promise.all([ + queryClient.invalidateQueries({ queryKey: ['rooms', { reference: rid, type: 'l' }] }), + queryClient.invalidateQueries({ queryKey: roomsQueryKeys.room(rid) }), + queryClient.invalidateQueries({ queryKey: roomsQueryKeys.info(rid) }), + ]); }; const removeInquiry = async (inquiry: ILivechatInquiryRecord) => { diff --git a/apps/meteor/client/hooks/roomActions/useRoomInfoRoomAction.ts b/apps/meteor/client/hooks/roomActions/useRoomInfoRoomAction.ts index 573211b6fbd86..8e1ba5d5d8c71 100644 --- a/apps/meteor/client/hooks/roomActions/useRoomInfoRoomAction.ts +++ b/apps/meteor/client/hooks/roomActions/useRoomInfoRoomAction.ts @@ -2,10 +2,10 @@ import { lazy, useMemo } from 'react'; import type { RoomToolboxActionConfig } from '../../views/room/contexts/RoomToolboxContext'; -const ChatsContextualBar = lazy(() => import('../../views/omnichannel/directory/chats/ChatInfo/ChatInfoRouter')); +const ChatsContextualBar = lazy(() => import('../../views/omnichannel/directory/chats/ChatInfo/ChatsContextualBar')); -export const useRoomInfoRoomAction = () => { - return useMemo( +export const useRoomInfoRoomAction = () => + useMemo( (): RoomToolboxActionConfig => ({ id: 'room-info', groups: ['live'], @@ -16,4 +16,3 @@ export const useRoomInfoRoomAction = () => { }), [], ); -}; diff --git a/apps/meteor/client/hooks/useRoomInfoEndpoint.ts b/apps/meteor/client/hooks/useRoomInfoEndpoint.ts index af72b067aa969..6857e2590a069 100644 --- a/apps/meteor/client/hooks/useRoomInfoEndpoint.ts +++ b/apps/meteor/client/hooks/useRoomInfoEndpoint.ts @@ -2,7 +2,7 @@ import type { IRoom, ITeam, Serialized } from '@rocket.chat/core-typings'; import { useEndpoint, useUserId } from '@rocket.chat/ui-contexts'; import type { UseQueryOptions } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query'; -// import { minutesToMilliseconds } from 'date-fns'; +import { minutesToMilliseconds } from 'date-fns'; import { roomsQueryKeys } from '../lib/queryKeys'; @@ -40,12 +40,8 @@ export const useRoomInfoEndpoint = < const uid = useUserId(); return useQuery({ queryKey: roomsQueryKeys.info(rid), - queryFn: async () => { - const result = await getRoomInfo({ roomId: rid }); - console.log('useRoomInfoEndpoint queryFn', roomsQueryKeys.info(rid), JSON.stringify(result, null, 2)); - return result; - }, - // gcTime: minutesToMilliseconds(15), + queryFn: () => getRoomInfo({ roomId: rid }), + gcTime: minutesToMilliseconds(15), retry: (count, error: { success: boolean; error: string }) => count <= 2 && error.error !== 'not-allowed', enabled: !!uid, ...options, diff --git a/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatInfoRouter.tsx b/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatsContextualBar.tsx similarity index 100% rename from apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatInfoRouter.tsx rename to apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatsContextualBar.tsx