snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1,227 @@
import type { IRoom, RoomType, IUser, AtLeast, ValueOf, ISubscription } from '@rocket.chat/core-typings';
import type { RouteName } from '@rocket.chat/ui-contexts';
import type {
RoomSettingsEnum,
RoomMemberActions,
UiTextContext,
IRoomTypeClientDirectives,
RoomIdentification,
IRoomTypeRouteConfig,
IRoomTypeClientConfig,
} from '../../../definition/IRoomTypeConfig';
import { RoomCoordinator } from '../../../lib/rooms/coordinator';
import { router } from '../../providers/RouterProvider';
import { Subscriptions } from '../../stores';
import RoomRoute from '../../views/room/RoomRoute';
import MainLayout from '../../views/root/MainLayout';
import { absoluteUrl } from '../absoluteUrl';
import { appLayout } from '../appLayout';
import { hasPermission } from '../authorization';
class RoomCoordinatorClient extends RoomCoordinator {
public add(roomConfig: IRoomTypeClientConfig, directives: Partial<IRoomTypeClientDirectives>): void {
this.addRoomType(roomConfig, {
allowRoomSettingChange(_room: Partial<IRoom>, _setting: ValueOf<typeof RoomSettingsEnum>): boolean {
return true;
},
allowMemberAction(
_room: Partial<IRoom>,
_action: ValueOf<typeof RoomMemberActions>,
_showingUserId: IUser['_id'],
_userSubscription?: ISubscription,
): boolean {
return false;
},
roomName(_room: AtLeast<IRoom, '_id' | 'name' | 'fname' | 'prid'>): string {
return '';
},
isGroupChat(_room: Partial<IRoom>): boolean {
return false;
},
getUiText(_context: ValueOf<typeof UiTextContext>): string {
return '';
},
getAvatarPath(_room): string {
return '';
},
findRoom(_identifier: string): IRoom | undefined {
return undefined;
},
isLivechatRoom(): boolean {
return false;
},
canSendMessage(room: IRoom): boolean {
return Subscriptions.state.count((record) => record.rid === room._id) > 0;
},
...directives,
config: roomConfig,
});
}
public getRoomDirectives(roomType: string): IRoomTypeClientDirectives {
return this.roomTypes[roomType].directives as IRoomTypeClientDirectives;
}
public openRouteLink(
roomType: RoomType,
subData: RoomIdentification,
queryParams?: Record<string, string>,
options: { replace?: boolean; routeParamsOverrides?: Record<string, string> } = {},
): void {
const config = this.getRoomTypeConfig(roomType);
if (!config?.route) {
return;
}
let _routeData = {};
if (config.route.link) {
_routeData = config.route.link(subData);
} else if (subData?.name) {
_routeData = {
name: subData.name,
};
} else {
return;
}
router.navigate(
{
pattern: config.route.path ?? '/home',
params: { ..._routeData, ...options.routeParamsOverrides },
search: queryParams,
},
options,
);
}
public isLivechatRoom(roomType: string): boolean {
return Boolean(this.getRoomDirectives(roomType).isLivechatRoom());
}
public getRoomName(roomType: string, roomData: AtLeast<IRoom, '_id' | 'name' | 'fname' | 'prid'>): string {
return this.getRoomDirectives(roomType).roomName(roomData) ?? '';
}
readOnly(room?: IRoom, user?: AtLeast<IUser, 'username'> | null): boolean {
if (!room) {
return false;
}
const directives = this.getRoomDirectives(room.t);
if (directives?.readOnly) {
return directives.readOnly(room, user);
}
if (!user?.username) {
return Boolean(room.ro);
}
if (!room) {
return false;
}
if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1) {
return true;
}
if (room.ro) {
if (Array.isArray(room.unmuted) && room.unmuted.indexOf(user.username) !== -1) {
return false;
}
if (hasPermission('post-readonly', room._id)) {
return false;
}
return true;
}
return false;
}
private validateRoute<TRouteName extends RouteName>(route: IRoomTypeRouteConfig<TRouteName>): void {
const { name, path, link } = route;
if (typeof name !== 'string' || name.length === 0) {
throw new Error('The route name must be a string.');
}
if (path !== undefined && (typeof path !== 'string' || path.length === 0)) {
throw new Error('The route path must be a string.');
}
if (!['undefined', 'function'].includes(typeof link)) {
throw new Error('The route link must be a function.');
}
}
protected override validateRoomConfig(roomConfig: IRoomTypeClientConfig): void {
super.validateRoomConfig(roomConfig);
const { route, label } = roomConfig;
if (route !== undefined) {
this.validateRoute(route);
}
if (label !== undefined && (typeof label !== 'string' || label.length === 0)) {
throw new Error('The label must be a string.');
}
}
protected override addRoomType(roomConfig: IRoomTypeClientConfig, directives: IRoomTypeClientDirectives): void {
super.addRoomType(roomConfig, directives);
if (roomConfig.route?.path && roomConfig.route.name && directives.extractOpenRoomParams) {
const {
route: { name, path },
} = roomConfig;
const { extractOpenRoomParams } = directives;
router.defineRoutes([
{
path,
id: name,
element: appLayout.wrap(
<MainLayout>
<RoomRoute key={name} extractOpenRoomParams={extractOpenRoomParams} />
</MainLayout>,
),
},
]);
}
}
public getURL(roomType: string, subData: RoomIdentification): string | false {
const config = this.getRoomTypeConfig(roomType);
if (!config?.route) {
return false;
}
const routeData = this.getRouteData(roomType, subData);
if (!routeData) {
return false;
}
return absoluteUrl(
router.buildRoutePath({
name: config.route.name,
params: routeData,
}),
);
}
public isRouteNameKnown(routeName: string): boolean {
return Boolean(this.getRouteNameIdentifier(routeName));
}
public getRouteNameIdentifier(routeName: string): string | undefined {
if (!routeName) {
return;
}
return Object.keys(this.roomTypes).find((key) => this.roomTypes[key].config.route?.name === routeName);
}
}
export const roomCoordinator = new RoomCoordinatorClient();

View File

@@ -0,0 +1,12 @@
import { getConversationRoomType } from '../../../../lib/rooms/roomTypes/conversation';
import { roomCoordinator } from '../roomCoordinator';
export const ConversationRoomType = getConversationRoomType(roomCoordinator);
roomCoordinator.add(
{
...ConversationRoomType,
label: 'Conversations',
},
{},
);

View File

@@ -0,0 +1,149 @@
import type { AtLeast } from '@rocket.chat/core-typings';
import { isRoomFederated } from '@rocket.chat/core-typings';
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
import type { IRoomTypeClientDirectives } from '../../../../definition/IRoomTypeConfig';
import { RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../../../../definition/IRoomTypeConfig';
import { getDirectMessageRoomType } from '../../../../lib/rooms/roomTypes/direct';
import { Users, Rooms, Subscriptions } from '../../../stores';
import * as Federation from '../../federation/Federation';
import { getAvatarURL } from '../../getAvatarURL';
import { getUserAvatarURL } from '../../getUserAvatarURL';
import { settings } from '../../settings';
import { roomCoordinator } from '../roomCoordinator';
export const DirectMessageRoomType = getDirectMessageRoomType(roomCoordinator);
roomCoordinator.add(
{
...DirectMessageRoomType,
label: 'Direct_Messages',
},
{
allowRoomSettingChange(_room, setting) {
if (isRoomFederated(_room)) {
return Federation.isRoomSettingAllowed(_room, setting);
}
switch (setting) {
case RoomSettingsEnum.TYPE:
case RoomSettingsEnum.NAME:
case RoomSettingsEnum.SYSTEM_MESSAGES:
case RoomSettingsEnum.DESCRIPTION:
case RoomSettingsEnum.READ_ONLY:
case RoomSettingsEnum.REACT_WHEN_READ_ONLY:
case RoomSettingsEnum.ARCHIVE_OR_UNARCHIVE:
case RoomSettingsEnum.JOIN_CODE:
return false;
case RoomSettingsEnum.E2E:
return settings.peek('E2E_Enable') === true;
default:
return true;
}
},
allowMemberAction(room, action, showingUserId, userSubscription) {
if (isRoomFederated(room)) {
return Federation.actionAllowed(room, action, showingUserId, userSubscription);
}
switch (action) {
case RoomMemberActions.BLOCK:
return !this.isGroupChat(room);
default:
return false;
}
},
roomName(roomData) {
const subscription = ((): { fname?: string; name?: string } | undefined => {
if (roomData.fname || roomData.name) {
return {
fname: roomData.fname,
name: roomData.name,
};
}
if (!roomData._id) {
return undefined;
}
return Subscriptions.state.find((record) => record.rid === roomData._id);
})();
if (!subscription) {
return;
}
if (settings.peek('UI_Use_Real_Name') && subscription.fname) {
return subscription.fname;
}
return subscription.name;
},
isGroupChat(room) {
return (room?.uids?.length || 0) > 2;
},
getUiText(context) {
switch (context) {
case UiTextContext.HIDE_WARNING:
return 'Hide_Private_Warning';
case UiTextContext.LEAVE_WARNING:
return 'Leave_Private_Warning';
default:
return '';
}
},
getAvatarPath(room) {
if (!room) {
return '';
}
// if coming from sidenav search
if (room.name && room.avatarETag) {
return getUserAvatarURL(room.name, room.avatarETag);
}
if (this.isGroupChat(room)) {
return getAvatarURL({
username: (room.uids || []).length + (room.usernames || []).join(),
cache: room.avatarETag,
}) as string;
}
const subscriptionName = Subscriptions.state.find((record) => record.rid === room._id)?.name;
if (subscriptionName) {
const { username, avatarETag } = Users.state.find((record) => record.username === subscriptionName) || {};
return getUserAvatarURL(username || subscriptionName, avatarETag);
}
return getUserAvatarURL(room.name || this.roomName(room) || '');
},
getIcon(room) {
if (isRoomFederated(room)) {
return 'globe';
}
if (this.isGroupChat(room)) {
return 'balloon';
}
return 'at';
},
extractOpenRoomParams({ rid }) {
return { type: 'd', reference: rid };
},
findRoom(identifier) {
const predicate = (record: SubscriptionWithRoom) => record.t === 'd' && (record.name === identifier || record.rid === identifier);
const subscription = Subscriptions.state.find(predicate);
if (subscription?.rid) {
return Rooms.state.get(subscription.rid);
}
},
} as AtLeast<IRoomTypeClientDirectives, 'isGroupChat' | 'roomName'>,
);

View File

@@ -0,0 +1,16 @@
import { getFavoriteRoomType } from '../../../../lib/rooms/roomTypes/favorite';
import { roomCoordinator } from '../roomCoordinator';
export const FavoriteRoomType = getFavoriteRoomType(roomCoordinator);
roomCoordinator.add(
{
...FavoriteRoomType,
label: 'Favorites',
},
{
getIcon() {
return 'star';
},
},
);

View File

@@ -0,0 +1,7 @@
import './conversation';
import './direct';
import './favorite';
import './livechat';
import './private';
import './public';
import './unread';

View File

@@ -0,0 +1,79 @@
import type { AtLeast, ValueOf } from '@rocket.chat/core-typings';
import type { IRoomTypeClientDirectives } from '../../../../definition/IRoomTypeConfig';
import { RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../../../../definition/IRoomTypeConfig';
import { getLivechatRoomType } from '../../../../lib/rooms/roomTypes/livechat';
import { Rooms, Subscriptions } from '../../../stores';
import { getAvatarURL } from '../../getAvatarURL';
import { roomCoordinator } from '../roomCoordinator';
export const LivechatRoomType = getLivechatRoomType(roomCoordinator);
roomCoordinator.add(
{
...LivechatRoomType,
label: 'Omnichannel',
},
{
allowRoomSettingChange(_room, setting) {
switch (setting) {
case RoomSettingsEnum.JOIN_CODE:
return false;
default:
return true;
}
},
allowMemberAction(_room, action) {
return ([RoomMemberActions.INVITE, RoomMemberActions.JOIN] as Array<ValueOf<typeof RoomMemberActions>>).includes(action);
},
roomName(room) {
return room.name || room.fname || (room as any).label;
},
getUiText(context) {
switch (context) {
case UiTextContext.HIDE_WARNING:
return 'Hide_Livechat_Warning';
case UiTextContext.LEAVE_WARNING:
return 'Leave_Livechat_Warning';
default:
return '';
}
},
getAvatarPath(room) {
return getAvatarURL({ username: `@${this.roomName(room)}` }) || '';
},
findRoom(identifier) {
return Rooms.state.get(identifier);
},
isLivechatRoom() {
return true;
},
canSendMessage(room) {
return Boolean(room?.open);
},
readOnly(room) {
if (!room?.open) {
return true;
}
const subscription = Subscriptions.state.find((record) => record.rid === room._id);
return !subscription;
},
getIcon() {
return 'livechat';
},
extractOpenRoomParams({ id }) {
return { type: 'l', reference: id };
},
} as AtLeast<IRoomTypeClientDirectives, 'roomName'>,
);

View File

@@ -0,0 +1,117 @@
import type { AtLeast, IRoom } from '@rocket.chat/core-typings';
import { isRoomFederated } from '@rocket.chat/core-typings';
import type { IRoomTypeClientDirectives } from '../../../../definition/IRoomTypeConfig';
import { RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../../../../definition/IRoomTypeConfig';
import { getPrivateRoomType } from '../../../../lib/rooms/roomTypes/private';
import { Rooms } from '../../../stores';
import * as Federation from '../../federation/Federation';
import { getRoomAvatarURL } from '../../getRoomAvatarURL';
import { settings } from '../../settings';
import { roomCoordinator } from '../roomCoordinator';
export const PrivateRoomType = getPrivateRoomType(roomCoordinator);
roomCoordinator.add(
{
...PrivateRoomType,
label: 'Private_Groups',
},
{
allowRoomSettingChange(room, setting) {
if (isRoomFederated(room)) {
return Federation.isRoomSettingAllowed(room, setting);
}
switch (setting) {
case RoomSettingsEnum.JOIN_CODE:
return false;
case RoomSettingsEnum.BROADCAST:
return Boolean(room.broadcast);
case RoomSettingsEnum.READ_ONLY:
return !room.broadcast;
case RoomSettingsEnum.REACT_WHEN_READ_ONLY:
return Boolean(!room.broadcast && room.ro);
case RoomSettingsEnum.E2E:
return settings.peek('E2E_Enable') === true;
case RoomSettingsEnum.SYSTEM_MESSAGES:
default:
return true;
}
},
allowMemberAction(_room, action, showingUserId, userSubscription) {
if (isRoomFederated(_room)) {
return Federation.actionAllowed(_room, action, showingUserId, userSubscription);
}
switch (action) {
case RoomMemberActions.BLOCK:
return false;
default:
return true;
}
},
roomName(roomData) {
if (roomData.prid || isRoomFederated(roomData)) {
return roomData.fname;
}
if (settings.peek('UI_Allow_room_names_with_special_chars')) {
return roomData.fname || roomData.name;
}
return roomData.name;
},
isGroupChat(_room) {
return true;
},
getUiText(context) {
switch (context) {
case UiTextContext.HIDE_WARNING:
return 'Hide_Group_Warning';
case UiTextContext.LEAVE_WARNING:
return 'Leave_Group_Warning';
default:
return '';
}
},
getAvatarPath(room) {
return getRoomAvatarURL({ roomId: room._id, cache: room.avatarETag });
},
getIcon(room) {
if (room.abacAttributes) {
if (room.teamMain) {
return 'team-shield';
}
return 'hash-shield';
}
if (room.prid) {
return 'discussion';
}
if (room.teamMain) {
return 'team-lock';
}
if (isRoomFederated(room)) {
return 'globe';
}
return 'hashtag-lock';
},
extractOpenRoomParams({ name }) {
return { type: 'p', reference: name };
},
findRoom(identifier) {
const predicate = (record: IRoom): boolean => {
return record.t === 'p' && record.name === identifier;
};
return Rooms.state.find(predicate);
},
} as AtLeast<IRoomTypeClientDirectives, 'isGroupChat' | 'roomName'>,
);

View File

@@ -0,0 +1,106 @@
import type { AtLeast, IRoom } from '@rocket.chat/core-typings';
import { isRoomFederated } from '@rocket.chat/core-typings';
import type { IRoomTypeClientDirectives } from '../../../../definition/IRoomTypeConfig';
import { RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../../../../definition/IRoomTypeConfig';
import { getPublicRoomType } from '../../../../lib/rooms/roomTypes/public';
import { Rooms } from '../../../stores';
import * as Federation from '../../federation/Federation';
import { getRoomAvatarURL } from '../../getRoomAvatarURL';
import { settings } from '../../settings';
import { roomCoordinator } from '../roomCoordinator';
export const PublicRoomType = getPublicRoomType(roomCoordinator);
roomCoordinator.add(
{
...PublicRoomType,
label: 'Channels',
},
{
allowRoomSettingChange(room, setting) {
if (isRoomFederated(room)) {
return Federation.isRoomSettingAllowed(room, setting);
}
switch (setting) {
case RoomSettingsEnum.BROADCAST:
return Boolean(room.broadcast);
case RoomSettingsEnum.READ_ONLY:
return Boolean(!room.broadcast);
case RoomSettingsEnum.REACT_WHEN_READ_ONLY:
return Boolean(!room.broadcast && room.ro);
case RoomSettingsEnum.E2E:
return false;
case RoomSettingsEnum.SYSTEM_MESSAGES:
default:
return true;
}
},
allowMemberAction(_room, action, showingUserId, userSubscription) {
if (isRoomFederated(_room)) {
return Federation.actionAllowed(_room, action, showingUserId, userSubscription);
}
switch (action) {
case RoomMemberActions.BLOCK:
return false;
default:
return true;
}
},
roomName(roomData) {
if (roomData.prid || isRoomFederated(roomData)) {
return roomData.fname;
}
if (settings.peek('UI_Allow_room_names_with_special_chars')) {
return roomData.fname || roomData.name;
}
return roomData.name;
},
isGroupChat(_room) {
return true;
},
getUiText(context) {
switch (context) {
case UiTextContext.HIDE_WARNING:
return 'Hide_Room_Warning';
case UiTextContext.LEAVE_WARNING:
return 'Leave_Room_Warning';
default:
return '';
}
},
getAvatarPath(room) {
return getRoomAvatarURL({ roomId: room._id, cache: room.avatarETag });
},
getIcon(room) {
if (room.prid) {
return 'discussion';
}
if (room.teamMain) {
return 'team';
}
if (isRoomFederated(room)) {
return 'globe';
}
return 'hashtag';
},
extractOpenRoomParams({ name }) {
return { type: 'c', reference: name };
},
findRoom(identifier) {
const predicate = (record: IRoom): boolean => {
return record.t === 'c' && record.name === identifier;
};
return Rooms.state.find(predicate);
},
} as AtLeast<IRoomTypeClientDirectives, 'isGroupChat' | 'roomName'>,
);

View File

@@ -0,0 +1,12 @@
import { getUnreadRoomType } from '../../../../lib/rooms/roomTypes/unread';
import { roomCoordinator } from '../roomCoordinator';
export const UnreadRoomType = getUnreadRoomType(roomCoordinator);
roomCoordinator.add(
{
...UnreadRoomType,
label: 'Unread',
},
{},
);