Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
376e55c
chore: adapt Rooms store: `state.get`
juliajforesti Jun 27, 2025
ab15079
chore: adapt Rooms store: `state.find`
juliajforesti Jun 27, 2025
9801b86
chore: adapt Rooms store in `useOpenRoom`
juliajforesti Jun 27, 2025
cfab17a
chore: adapt Rooms store in `RealAppsEngineUIHost`
juliajforesti Jun 27, 2025
ef88059
chore: `createDocumentMapStore` in `Rooms` model
juliajforesti Jun 27, 2025
b55fe1b
chore: create `applyQueryOptions` in pipe
juliajforesti Jun 30, 2025
bb129a5
chore: adjust Rooms store usage in `useRoomQuery`
juliajforesti Jul 1, 2025
c99d00e
chore: adjust Rooms store usage in `UserProvider` and create `convert…
juliajforesti Jul 1, 2025
80c44d6
chore: revert Subscriptions
juliajforesti Jul 2, 2025
f8d3684
chore: subscribe on `useRoomQuery`
juliajforesti Jul 2, 2025
db5737c
fix: `useOpenRoom` store
juliajforesti Jul 2, 2025
bf6c137
chore: reorg
juliajforesti Jul 2, 2025
0d110d8
chore: unsubscribe
juliajforesti Jul 7, 2025
9c8007d
chore: review
juliajforesti Jul 7, 2025
551b7d4
chore: remove eslint disable
juliajforesti Jul 7, 2025
c94af05
Replace `waitUntilFind`
tassoevan Jul 8, 2025
a295f05
Add missing translation
tassoevan Jul 8, 2025
bdb2c85
Replace `useRoomQuery`
tassoevan Jul 8, 2025
0d6f4a8
Implement `CachedStore` as alternative for `CachedCollection`
tassoevan Jul 9, 2025
39ff40d
Redesign `roomCoordinator.readOnly` to depend on room reactivity
tassoevan Jul 10, 2025
483973c
Remove `roomCoordinator.archived`
tassoevan Jul 10, 2025
d14fdbb
Remove `roomCoordinator.verifyCanSendMessage`
tassoevan Jul 10, 2025
49f924c
Fix reactivity in `UserProvider`
tassoevan Jul 10, 2025
dd67257
Adjust test locator
tassoevan Jul 10, 2025
da37028
test: improve locator specificity
juliajforesti Jul 10, 2025
5d05146
Adjust types
tassoevan Jul 11, 2025
41f01eb
Eliminate some auxiliar contexts from E2E test suite
tassoevan Jul 11, 2025
7e90e88
Adjust types
tassoevan Jul 12, 2025
0420b3c
Adapt `useOmnichannelRoomInfo` and `useRoomInfoEndpoint`
tassoevan Jul 14, 2025
0fc2824
Refactor `info` query key to remove redundant room ID from the key st…
ggazzo Jul 14, 2025
8f5b313
Refactor `handlePriorityChange` to use a local function within `useMemo`
ggazzo Jul 14, 2025
5d74215
Debug hooks
ggazzo Jul 14, 2025
8b4e340
Use true invalidation
tassoevan Jul 14, 2025
168fea9
Fix SAML tests
tassoevan Jul 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions apps/meteor/app/e2e/client/rocketchat.e2e.room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,9 +335,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);
Expand Down
28 changes: 22 additions & 6 deletions apps/meteor/app/e2e/client/rocketchat.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -254,8 +253,24 @@ class E2E extends Emitter {
);
}

private waitForRoom(rid: IRoom['_id']): Promise<IRoom> {
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<E2ERoom | null> {
const room = await waitUntilFind(() => Rooms.findOne({ _id: rid }));
const room = await this.waitForRoom(rid);

if (room.t !== 'd' && room.t !== 'p') {
return null;
Expand Down Expand Up @@ -846,11 +861,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;
}
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/lib/client/methods/sendMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Meteor.methods<ServerMethods>({
}

// 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;
}
Expand Down
8 changes: 5 additions & 3 deletions apps/meteor/app/livechat/client/lib/stream/queueManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ const processInquiryEvent = async (args: unknown): Promise<void> => {
};

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) => {
Expand Down
6 changes: 4 additions & 2 deletions apps/meteor/app/models/client/models/CachedChatRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<IRoom> {
class CachedChatRoom extends PrivateCachedStore<IRoom> {
constructor() {
super({
name: 'rooms',
eventType: 'notify-user',
store: createDocumentMapStore(),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class CachedChatSubscription extends PrivateCachedCollection<SubscriptionWithRoo
}

protected override mapRecord(subscription: ISubscription): SubscriptionWithRoom {
const room = CachedChatRoom.collection.state.find((r) => r._id === subscription.rid);
const room = CachedChatRoom.store.getState().find((r) => r._id === subscription.rid);

const lastRoomUpdate = room?.lm || subscription.ts || room?.ts;

Expand Down
7 changes: 6 additions & 1 deletion apps/meteor/app/models/client/models/Rooms.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { CachedChatRoom } from './CachedChatRoom';

/** @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 = {
use: CachedChatRoom.store,
get state() {
return this.use.getState();
},
} as const;
6 changes: 3 additions & 3 deletions apps/meteor/app/reactions/client/methods/setReaction.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -23,7 +23,7 @@ Meteor.methods<ServerMethods>({
return false;
}

const room: IRoom | undefined = Rooms.findOne({ _id: message.rid });
const room = Rooms.state.get(message.rid);
if (!room) {
return false;
}
Expand All @@ -36,7 +36,7 @@ Meteor.methods<ServerMethods>({
return false;
}

if (roomCoordinator.readOnly(room._id, user)) {
if (roomCoordinator.readOnly(room, user)) {
return false;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/slashcommands-topic/client/topic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/app/webrtc/client/actionLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand All @@ -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');
}
Expand Down
6 changes: 5 additions & 1 deletion apps/meteor/client/apps/RealAppsEngineUIHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ export class RealAppsEngineUIHost extends AppsEngineUIHost {
}

async getClientRoomInfo(): Promise<IExternalComponentRoomInfo> {
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ComponentProps<typeof AutoComplete>, 'filter'>;
Expand All @@ -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 },
Expand All @@ -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(
() =>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
}

Expand All @@ -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);
});
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -16,4 +16,3 @@ export const useRoomInfoRoomAction = () => {
}),
[],
);
};
44 changes: 34 additions & 10 deletions apps/meteor/client/hooks/useRoomInfoEndpoint.ts
Original file line number Diff line number Diff line change
@@ -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<IRoom, '_id' | 'name' | 'fname' | 't' | 'prid' | 'u' | 'sidepanel'>;
team?: Pick<ITeam, 'name' | 'roomId' | 'type' | '_id'>;
}>,
> = Omit<
UseQueryOptions<
Serialized<{
room: IRoom | undefined;
parent?: Pick<IRoom, '_id' | 'name' | 'fname' | 't' | 'prid' | 'u' | 'sidepanel'>;
team?: Pick<ITeam, 'name' | 'roomId' | 'type' | '_id'>;
}>,
{ success: boolean; error: string },
TData,
ReturnType<typeof roomsQueryKeys.info>
>,
'queryKey' | 'queryFn'
>;

export const useRoomInfoEndpoint = <
TData = Serialized<{
room: IRoom | undefined;
parent?: Pick<IRoom, '_id' | 'name' | 'fname' | 't' | 'prid' | 'u' | 'sidepanel'>;
team?: Pick<ITeam, 'name' | 'roomId' | 'type' | '_id'>;
}>,
>(
rid: IRoom['_id'],
options?: UseRoomInfoEndpointOptions<TData>,
) => {
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,
});
};
Loading
Loading