Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
448d441
chore: adapt Rooms store: `state.get`
juliajforesti Jun 27, 2025
3a92546
chore: adapt Rooms store: `state.find`
juliajforesti Jun 27, 2025
e89cbdf
chore: adapt Rooms store in `useOpenRoom`
juliajforesti Jun 27, 2025
a5131a9
chore: adapt Rooms store in `RealAppsEngineUIHost`
juliajforesti Jun 27, 2025
45dac63
chore: `createDocumentMapStore` in `Rooms` model
juliajforesti Jun 27, 2025
f0f8a4f
chore: create `applyQueryOptions` in pipe
juliajforesti Jun 30, 2025
f5f36a4
chore: adjust Rooms store usage in `useRoomQuery`
juliajforesti Jul 1, 2025
05a8ad8
chore: adjust Rooms store usage in `UserProvider` and create `convert…
juliajforesti Jul 1, 2025
d690fcc
chore: revert Subscriptions
juliajforesti Jul 2, 2025
9aa1928
chore: subscribe on `useRoomQuery`
juliajforesti Jul 2, 2025
ba7dcdc
fix: `useOpenRoom` store
juliajforesti Jul 2, 2025
65c24c4
chore: reorg
juliajforesti Jul 2, 2025
7811911
chore: unsubscribe
juliajforesti Jul 7, 2025
7d20ba9
chore: review
juliajforesti Jul 7, 2025
ee694f6
chore: remove eslint disable
juliajforesti Jul 7, 2025
37a5327
Replace `waitUntilFind`
tassoevan Jul 8, 2025
62b37f1
Add missing translation
tassoevan Jul 8, 2025
f5533a6
Replace `useRoomQuery`
tassoevan Jul 8, 2025
58fea4a
Implement `CachedStore` as alternative for `CachedCollection`
tassoevan Jul 9, 2025
3aae73c
Redesign `roomCoordinator.readOnly` to depend on room reactivity
tassoevan Jul 10, 2025
6b6efb0
Remove `roomCoordinator.archived`
tassoevan Jul 10, 2025
42b64de
Remove `roomCoordinator.verifyCanSendMessage`
tassoevan Jul 10, 2025
e83f29f
Fix reactivity in `UserProvider`
tassoevan Jul 10, 2025
f3ce708
Adjust test locator
tassoevan Jul 10, 2025
9738e88
test: improve locator specificity
juliajforesti Jul 10, 2025
8caec03
Adjust types
tassoevan Jul 11, 2025
47704cb
Eliminate some auxiliar contexts from E2E test suite
tassoevan Jul 11, 2025
a6afe60
Adjust types
tassoevan Jul 12, 2025
d90b185
Adapt `useOmnichannelRoomInfo` and `useRoomInfoEndpoint`
tassoevan Jul 14, 2025
ee97498
Refactor `info` query key to remove redundant room ID from the key st…
ggazzo Jul 14, 2025
4837308
Refactor `handlePriorityChange` to use a local function within `useMemo`
ggazzo Jul 14, 2025
a7d5986
Debug hooks
ggazzo Jul 14, 2025
6839921
Use true invalidation
tassoevan Jul 14, 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