Chore: roomTypes: Stop mixing client and server code together (#24536)

* [Chore] roomTypes: Stop mixing client and server code together

* Moving roomTypes

* client

* client

* livechat

* imports

* errors

* removed commented code

* Avoid typecasts

* Fixed invalid reference

* Removed typecast

* comments

* Prefer `RoomType`

* Fix weird component prop

* Prefer assertion

* Revert "Prefer assertion"

This reverts commit 08e8a58fcb.

* Imply by typing that `getRoomTypeConfig(identifier: RoomType)` returns a route

Co-authored-by: Tasso Evangelista <tasso.evangelista@rocket.chat>
pull/24573/head
Pierre Lehnen 4 years ago committed by GitHub
parent 16a3bc9489
commit 8bf7fef9e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 50
      app/api/server/lib/rooms.js
  2. 4
      app/apps/client/gameCenter/invitePlayers.js
  3. 5
      app/authorization/server/functions/canSendMessage.js
  4. 5
      app/channel-settings/server/functions/saveRoomName.js
  5. 5
      app/channel-settings/server/functions/saveRoomType.js
  6. 5
      app/channel-settings/server/methods/saveRoomSettings.js
  7. 13
      app/chatpal-search/client/template/result.js
  8. 4
      app/discussion/client/createDiscussionMessageAction.js
  9. 2
      app/discussion/client/index.js
  10. 13
      app/discussion/lib/discussionRoomType.js
  11. 3
      app/discussion/server/index.js
  12. 4
      app/discussion/server/methods/createDiscussion.js
  13. 5
      app/e2e/client/rocketchat.e2e.room.js
  14. 5
      app/file-upload/server/lib/FileUpload.js
  15. 5
      app/invites/server/functions/findOrCreateInvite.js
  16. 5
      app/invites/server/functions/useInviteToken.js
  17. 4
      app/invites/server/functions/validateInviteToken.js
  18. 88
      app/lib/README.md
  19. 1
      app/lib/client/index.js
  20. 18
      app/lib/lib/roomTypes/conversation.js
  21. 215
      app/lib/lib/roomTypes/direct.js
  22. 20
      app/lib/lib/roomTypes/favorite.js
  23. 8
      app/lib/lib/roomTypes/index.js
  24. 126
      app/lib/lib/roomTypes/private.js
  25. 136
      app/lib/lib/roomTypes/public.js
  26. 19
      app/lib/lib/roomTypes/unread.js
  27. 10
      app/lib/server/functions/addUserToRoom.js
  28. 4
      app/lib/server/functions/attachMessage.js
  29. 4
      app/lib/server/functions/notifications/desktop.js
  30. 18
      app/lib/server/functions/notifications/email.js
  31. 6
      app/lib/server/functions/notifications/mobile.js
  32. 1
      app/lib/server/index.js
  33. 4
      app/lib/server/lib/sendNotificationsOnMessage.js
  34. 5
      app/lib/server/methods/archiveRoom.ts
  35. 5
      app/lib/server/methods/blockUser.ts
  36. 5
      app/lib/server/methods/joinRoom.ts
  37. 5
      app/lib/server/methods/leaveRoom.ts
  38. 16
      app/lib/startup/defaultRoomTypes.js
  39. 1
      app/livechat/client/index.js
  40. 4
      app/livechat/client/roomType.js
  41. 5
      app/livechat/client/views/app/tabbar/visitorInfo.js
  42. 5
      app/livechat/client/views/app/tabbar/visitorTranscript.js
  43. 133
      app/livechat/lib/LivechatRoomType.js
  44. 1
      app/livechat/server/index.js
  45. 35
      app/livechat/server/roomType.js
  46. 4
      app/livechat/server/startup.js
  47. 4
      app/message-mark-as-unread/client/actionButton.js
  48. 4
      app/message-pin/client/actionButton.js
  49. 4
      app/message-star/client/actionButton.js
  50. 8
      app/reactions/client/init.js
  51. 4
      app/reactions/client/methods/setReaction.js
  52. 7
      app/slashcommands-open/client/client.js
  53. 5
      app/slashcommands-unarchiveroom/server/server.ts
  54. 4
      app/threads/client/messageAction/follow.js
  55. 4
      app/threads/client/messageAction/replyInThread.js
  56. 1
      app/tokenpass/client/index.js
  57. 23
      app/tokenpass/client/roomType.js
  58. 7
      app/ui-message/client/message.js
  59. 9
      app/ui-message/client/messageBox/messageBox.js
  60. 8
      app/ui-message/client/messageBox/messageBoxNotSubscribed.js
  61. 4
      app/ui-message/client/messageBox/messageBoxReadOnly.js
  62. 4
      app/ui-message/client/popup/messagePopupChannel.js
  63. 6
      app/ui-sidenav/client/roomList.js
  64. 16
      app/ui-sidenav/client/sideNav.js
  65. 12
      app/ui-utils/client/lib/MessageAction.js
  66. 11
      app/ui-utils/client/lib/RoomManager.js
  67. 10
      app/ui-utils/client/lib/SideNav.js
  68. 5
      app/ui-utils/client/lib/messageContext.js
  69. 4
      app/ui-utils/client/lib/openRoom.js
  70. 18
      app/ui/client/views/app/room.js
  71. 4
      app/ui/client/views/app/roomSearch.js
  72. 3
      app/utils/client/index.js
  73. 213
      app/utils/client/lib/roomTypes.js
  74. 291
      app/utils/lib/RoomTypeConfig.js
  75. 120
      app/utils/lib/RoomTypesCommon.js
  76. 2
      app/utils/lib/getAvatarURL.ts
  77. 40
      app/utils/lib/roomExit.js
  78. 3
      app/utils/server/index.js
  79. 55
      app/utils/server/lib/roomTypes.js
  80. 14
      client/components/CreateDiscussion/DefaultParentRoomField.tsx
  81. 194
      client/lib/rooms/roomCoordinator.ts
  82. 33
      client/lib/rooms/roomExit.ts
  83. 14
      client/lib/rooms/roomTypes/conversation.ts
  84. 152
      client/lib/rooms/roomTypes/direct.ts
  85. 14
      client/lib/rooms/roomTypes/favorite.ts
  86. 7
      client/lib/rooms/roomTypes/index.ts
  87. 94
      client/lib/rooms/roomTypes/livechat.ts
  88. 99
      client/lib/rooms/roomTypes/private.ts
  89. 102
      client/lib/rooms/roomTypes/public.ts
  90. 13
      client/lib/rooms/roomTypes/unread.ts
  91. 9
      client/lib/utils/goToRoomById.ts
  92. 4
      client/providers/AvatarUrlProvider.tsx
  93. 6
      client/sidebar/RoomList/SideBarItemTemplateWithData.js
  94. 7
      client/sidebar/RoomMenu.js
  95. 4
      client/sidebar/search/UserItem.js
  96. 1
      client/startup/index.ts
  97. 7
      client/views/admin/rooms/EditRoom.js
  98. 6
      client/views/admin/rooms/RoomsTable.js
  99. 6
      client/views/directory/ChannelsTable.js
  100. 6
      client/views/directory/TeamsTable.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,34 +1,12 @@
import { hasPermissionAsync, hasAtLeastOnePermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { Rooms } from '../../../models/server/raw';
import { Subscriptions } from '../../../models/server';
import { adminFields } from '../../../../lib/rooms/adminFields';
export async function findAdminRooms({ uid, filter, types = [], pagination: { offset, count, sort } }) {
if (!(await hasPermissionAsync(uid, 'view-room-administration'))) {
throw new Error('error-not-authorized');
}
const fields = {
prid: 1,
fname: 1,
name: 1,
t: 1,
cl: 1,
u: 1,
usernames: 1,
usersCount: 1,
muted: 1,
unmuted: 1,
ro: 1,
default: 1,
favorite: 1,
featured: 1,
topic: 1,
msgs: 1,
archived: 1,
tokenpass: 1,
teamId: 1,
teamMain: 1,
};
const name = filter && filter.trim();
const discussion = types && types.includes('discussions');
const includeTeams = types && types.includes('teams');
@ -36,7 +14,7 @@ export async function findAdminRooms({ uid, filter, types = [], pagination: { of
const typesToRemove = ['discussions', 'teams'];
const showTypes = Array.isArray(types) ? types.filter((type) => !typesToRemove.includes(type)) : [];
const options = {
fields,
fields: adminFields,
sort: sort || { default: -1, name: 1 },
skip: offset,
limit: count,
@ -67,30 +45,8 @@ export async function findAdminRoom({ uid, rid }) {
if (!(await hasPermissionAsync(uid, 'view-room-administration'))) {
throw new Error('error-not-authorized');
}
const fields = {
prid: 1,
fname: 1,
name: 1,
t: 1,
cl: 1,
u: 1,
usernames: 1,
usersCount: 1,
muted: 1,
unmuted: 1,
ro: 1,
default: 1,
favorite: 1,
featured: 1,
topic: 1,
msgs: 1,
archived: 1,
tokenpass: 1,
announcement: 1,
description: 1,
};
return Rooms.findOneById(rid, { fields });
return Rooms.findOneById(rid, { fields: adminFields });
}
export async function findChannelAndPrivateAutocomplete({ uid, selector }) {

@ -8,7 +8,7 @@ import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
import { AutoComplete } from '../../../meteor-autocomplete/client';
import { roomTypes } from '../../../utils/client';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
import { ChatRoom } from '../../../models/client';
import { modal } from '../../../ui-utils/client';
import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling';
@ -84,7 +84,7 @@ Template.InvitePlayers.events({
try {
const result = await callWithErrorHandling('createPrivateGroup', privateGroupName, users);
roomTypes.openRouteLink(result.t, result);
roomCoordinator.openRouteLink(result.t, result);
// This ensures the message is only sent after the
// user has been redirected to the new room, preventing a

@ -1,7 +1,8 @@
import { canAccessRoomAsync } from './canAccessRoom';
import { hasPermissionAsync } from './hasPermission';
import { Subscriptions, Rooms } from '../../../models/server/raw';
import { roomTypes, RoomMemberActions } from '../../../utils/server';
import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
const subscriptionOptions = {
projection: {
@ -19,7 +20,7 @@ export const validateRoomMessagePermissionsAsync = async (room, { uid, username,
throw new Error('error-not-allowed');
}
if (roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.BLOCK)) {
if (roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.BLOCK)) {
const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, uid, subscriptionOptions);
if (subscription && (subscription.blocked || subscription.blocker)) {
throw new Error('room_is_blocked');

@ -2,9 +2,10 @@ import { Meteor } from 'meteor/meteor';
import { Rooms, Messages, Subscriptions } from '../../../models/server';
import { Integrations } from '../../../models/server/raw';
import { roomTypes, getValidRoomName } from '../../../utils/server';
import { getValidRoomName } from '../../../utils/server';
import { callbacks } from '../../../../lib/callbacks';
import { checkUsernameAvailability } from '../../../lib/server/functions';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
const updateRoomName = (rid, displayName, isDiscussion) => {
if (isDiscussion) {
@ -27,7 +28,7 @@ const updateRoomName = (rid, displayName, isDiscussion) => {
export async function saveRoomName(rid, displayName, user, sendMessage = true) {
const room = Rooms.findOneById(rid);
if (roomTypes.getConfig(room.t).preventRenaming()) {
if (roomCoordinator.getRoomDirectives(room.t)?.preventRenaming()) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', {
function: 'RocketChat.saveRoomdisplayName',
});

@ -4,7 +4,8 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { Rooms, Subscriptions, Messages } from '../../../models/server';
import { settings } from '../../../settings/server';
import { roomTypes, RoomSettingsEnum } from '../../../utils/server';
import { RoomSettingsEnum } from '../../../../definition/IRoomTypeConfig';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
export const saveRoomType = function (rid, roomType, user, sendMessage = true) {
if (!Match.test(rid, String)) {
@ -26,7 +27,7 @@ export const saveRoomType = function (rid, roomType, user, sendMessage = true) {
});
}
if (!roomTypes.getConfig(room.t).allowRoomSettingChange(room, RoomSettingsEnum.TYPE)) {
if (!roomCoordinator.getRoomDirectives(room.t)?.allowRoomSettingChange(room, RoomSettingsEnum.TYPE)) {
throw new Meteor.Error('error-direct-room', "Can't change type of direct rooms", {
function: 'RocketChat.saveRoomType',
});

@ -17,9 +17,10 @@ import { saveRoomSystemMessages } from '../functions/saveRoomSystemMessages';
import { saveRoomTokenpass } from '../functions/saveRoomTokens';
import { saveRoomEncrypted } from '../functions/saveRoomEncrypted';
import { saveStreamingOptions } from '../functions/saveStreamingOptions';
import { RoomSettingsEnum, roomTypes } from '../../../utils';
import { Team } from '../../../../server/sdk';
import { TEAM_TYPE } from '../../../../definition/ITeam';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
import { RoomSettingsEnum } from '../../../../definition/IRoomTypeConfig';
const fields = [
'roomAvatar',
@ -85,7 +86,7 @@ const validators = {
},
encrypted({ userId, value, room, rid }) {
if (value !== room.encrypted) {
if (!roomTypes.getConfig(room.t).allowRoomSettingChange(room, RoomSettingsEnum.E2E)) {
if (!roomCoordinator.getRoomDirectives(room.t)?.allowRoomSettingChange(room, RoomSettingsEnum.E2E)) {
throw new Meteor.Error('error-action-not-allowed', 'Only groups or direct channels can enable encryption', {
method: 'saveRoomSettings',
action: 'Change_Room_Encrypted',

@ -2,11 +2,12 @@ import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { roomTypes, getURL } from '../../../utils';
import { getURL } from '../../../utils';
import { Subscriptions } from '../../../models';
import { getUserAvatarURL as getAvatarUrl } from '../../../utils/lib/getUserAvatarURL';
import { formatTime } from '../../../../client/lib/utils/formatTime';
import { formatDate } from '../../../../client/lib/utils/formatDate';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
const getDMUrl = (username) => getURL(`/direct/${username}`);
@ -101,15 +102,15 @@ Template.ChatpalSearchSingleMessage.helpers({
if (room && room.t === 'd') {
return 'at';
}
return roomTypes.getIcon(room);
return roomCoordinator.getIcon(room);
},
roomLink() {
return roomTypes.getRouteLink(this.r.t, this.r);
return roomCoordinator.getRouteLink(this.r.t, this.r);
},
roomName() {
return roomTypes.getRoomName(this.r.t, this.r);
return roomCoordinator.getRoomName(this.r.t, this.r);
},
roomNotSubscribed() {
@ -131,10 +132,10 @@ Template.ChatpalSearchSingleRoom.helpers({
if (this.t === 'd') {
return 'at';
}
return roomTypes.getIcon(this);
return roomCoordinator.getIcon(this);
},
roomLink() {
return roomTypes.getRouteLink(this.t, this);
return roomCoordinator.getRouteLink(this.t, this);
},
roomNotSubscribed() {
const subscription = Subscriptions.findOne({ rid: this.rid });

@ -5,9 +5,9 @@ import { settings } from '../../settings/client';
import { hasPermission } from '../../authorization/client';
import { MessageAction } from '../../ui-utils/client';
import { messageArgs } from '../../ui-utils/client/lib/messageArgs';
import { roomTypes } from '../../utils/client';
import { imperativeModal } from '../../../client/lib/imperativeModal';
import CreateDiscussion from '../../../client/components/CreateDiscussion/CreateDiscussion';
import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
Meteor.startup(function () {
Tracker.autorun(() => {
@ -49,7 +49,7 @@ Meteor.startup(function () {
if (!subscription) {
return false;
}
const isLivechatRoom = roomTypes.isLivechatRoom(room.t);
const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
if (isLivechatRoom) {
return false;
}

@ -3,5 +3,3 @@ import './lib/messageTypes/discussionMessage';
import './createDiscussionMessageAction';
import './discussionFromMessageBox';
import './tabBar';
import '../lib/discussionRoomType';

@ -1,13 +0,0 @@
import { RoomTypeConfig, roomTypes } from '../../utils';
export class DiscussionRoomType extends RoomTypeConfig {
constructor() {
super({
identifier: 't',
order: 25,
label: 'Discussion',
});
}
}
roomTypes.add(new DiscussionRoomType());

@ -5,6 +5,3 @@ import './hooks/propagateDiscussionMetadata';
// Methods
import './methods/createDiscussion';
// Lib
import '../lib/discussionRoomType';

@ -7,8 +7,8 @@ import { hasAtLeastOnePermission, canSendMessage } from '../../../authorization/
import { Messages, Rooms } from '../../../models/server';
import { createRoom, addUserToRoom, sendMessage, attachMessage } from '../../../lib/server';
import { settings } from '../../../settings/server';
import { roomTypes } from '../../../utils/server';
import { callbacks } from '../../../../lib/callbacks';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
const getParentRoom = (rid) => {
const room = Rooms.findOne(rid);
@ -111,7 +111,7 @@ const create = ({ prid, pmid, t_name, reply, users, user, encrypted }) => {
// auto invite the replied message owner
const invitedUsers = message ? [message.u.username, ...users] : users;
const type = roomTypes.getConfig(p_room.t).getDiscussionType();
const type = roomCoordinator.getRoomDirectives(p_room.t)?.getDiscussionType();
const description = p_room.encrypted ? '' : message.msg;
const topic = p_room.name;

@ -24,10 +24,11 @@ import {
} from './helper';
import { Notifications } from '../../notifications/client';
import { Rooms, Subscriptions, Messages } from '../../models/client';
import { roomTypes, RoomSettingsEnum } from '../../utils/client';
import { log, logError } from './logger';
import { E2ERoomState } from './E2ERoomState';
import { call } from '../../../client/lib/utils/call';
import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
import { RoomSettingsEnum } from '../../../definition/IRoomTypeConfig';
const KEY_ID = Symbol('keyID');
const PAUSED = Symbol('PAUSED');
@ -249,7 +250,7 @@ export class E2ERoom extends Emitter {
}
isSupportedRoomType(type) {
return roomTypes.getConfig(type).allowRoomSettingChange({}, RoomSettingsEnum.E2E);
return roomCoordinator.getRoomDirectives(type)?.allowRoomSettingChange({}, RoomSettingsEnum.E2E);
}
async importGroupKey(groupKey) {

@ -19,7 +19,6 @@ import Users from '../../../models/server/models/Users';
import Rooms from '../../../models/server/models/Rooms';
import Settings from '../../../models/server/models/Settings';
import { mime } from '../../../utils/lib/mimeTypes';
import { roomTypes } from '../../../utils/server/lib/roomTypes';
import { hasPermission } from '../../../authorization/server/functions/hasPermission';
import { canAccessRoom } from '../../../authorization/server/functions/canAccessRoom';
import { fileUploadIsValidContentType } from '../../../utils/lib/fileUploadRestrictions';
@ -28,6 +27,7 @@ import { Messages } from '../../../models/server';
import { AppEvents, Apps } from '../../../apps/server';
import { streamToBuffer } from './streamToBuffer';
import { SystemLogger } from '../../../../server/lib/logger/system';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
const cookie = new Cookies();
let maxFileSize = 0;
@ -442,7 +442,8 @@ export const FileUpload = {
const isAuthorizedByCookies = rc_uid && rc_token && Users.findOneByIdAndLoginToken(rc_uid, rc_token);
const isAuthorizedByHeaders =
headers['x-user-id'] && headers['x-auth-token'] && Users.findOneByIdAndLoginToken(headers['x-user-id'], headers['x-auth-token']);
const isAuthorizedByRoom = rc_room_type && roomTypes.getConfig(rc_room_type).canAccessUploadedFile({ rc_uid, rc_rid, rc_token });
const isAuthorizedByRoom =
rc_room_type && roomCoordinator.getRoomDirectives(rc_room_type)?.canAccessUploadedFile({ rc_uid, rc_rid, rc_token });
const isAuthorizedByJWT =
settings.get('FileUpload_Enable_json_web_token_for_files') &&
token &&

@ -7,7 +7,8 @@ import { Subscriptions, Rooms } from '../../../models/server';
import { Invites } from '../../../models/server/raw';
import { settings } from '../../../settings';
import { getURL } from '../../../utils/lib/getURL';
import { roomTypes, RoomMemberActions } from '../../../utils/server';
import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
function getInviteUrl(invite) {
const { _id } = invite;
@ -51,7 +52,7 @@ export const findOrCreateInvite = async (userId, invite) => {
}
const room = Rooms.findOneById(invite.rid);
if (!roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.INVITE)) {
if (!roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.INVITE)) {
throw new Meteor.Error('error-room-type-not-allowed', 'Cannot create invite links for this room type', {
method: 'findOrCreateInvite',
});

@ -4,7 +4,8 @@ import { Users, Subscriptions } from '../../../models/server';
import { Invites } from '../../../models/server/raw';
import { validateInviteToken } from './validateInviteToken';
import { addUserToRoom } from '../../../lib/server/functions/addUserToRoom';
import { roomTypes, RoomMemberActions } from '../../../utils/server';
import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
export const useInviteToken = async (userId, token) => {
if (!userId) {
@ -23,7 +24,7 @@ export const useInviteToken = async (userId, token) => {
const { inviteData, room } = await validateInviteToken(token);
if (!roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.INVITE)) {
if (!roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.INVITE)) {
throw new Meteor.Error('error-room-type-not-allowed', "Can't join room of this type via invite", {
method: 'useInviteToken',
field: 'token',

@ -19,9 +19,7 @@ export const validateInviteToken = async (token) => {
});
}
const room = Rooms.findOneById(inviteData.rid, {
fields: { _id: 1, name: 1, fname: 1, t: 1, prid: 1 },
});
const room = Rooms.findOneById(inviteData.rid);
if (!room) {
throw new Meteor.Error('error-invalid-room', 'The invite token is invalid.', {
method: 'validateInviteToken',

@ -47,94 +47,6 @@ settingsRegistry.addGroup('Settings_Group', function() {
* `enableQuery` - Only enable this setting if the correspondent setting has the value specified
* `alert` - Shows an alert message with the given text
#### Custom Room Types
Custom room types are now a regular and expected customization to Rocket.Chat. As a result of this, we have expanded
the capabilities which custom room types are given. Custom room types now have full control over the settings which
display on the room's setting tab. To achieve this, however, we had to forcefully break the previous behavior in order
to force the new behavior. If, after merging, you are getting the error `Error: Invalid Room Configuration object, it must extend "RoomTypeConfig"`,
then you will need to modify your custom room code to the class setup explained below.
Implementing the new types requires two steps. First step is to implement a your own custom class to define the route configuration.
Then the second step would be to implement your own room class, which will contain all of the required methods and configuration
for usage in the clients.
##### First Step: RoomTypeRouteConfig
If your room adds a custom route to the browser, then you will need to create a class which extends `RoomTypeRouteConfig`.
This class can be imported using es6 style imports `import { RoomTypeRouteConfig } from 'meteor/rocketchat:lib';`. There
are two fields which are required when constructing your route which is `{ name, path }` and both are strings. Then your
class must implement the `action(params, queryParams)` method.
```javascript
class LivechatRoomRoute extends RoomTypeRouteConfig {
constructor() {
super({
name: 'live',
path: '/live/:code(\\d+)'
});
}
action(params) {
openRoom('l', params.code);
}
link(sub) {
return {
code: sub.code
};
}
}
```
##### Second Step: RoomTypeConfig
Next you need to create a class which extends `RoomTypeConfig`. This class can be imported using the es6 style import
such as `import { RoomTypeConfig } from 'meteor/rocketchat:lib';`. There are two required properties when constructing
your class which is `{ identifier, order }` with the `identifier` being a string and `order` being a number. There are
default implementations of the required methods, so unless you want to overwrite the default behavior, such as disallowing
certain settings, then you will need to implement that method and handle it.
```javascript
class LivechatRoomType extends RoomTypeConfig {
constructor() {
super({
identifier: 'l',
order: 5,
icon: 'livechat',
label: 'Livechat',
route: new LivechatRoomRoute() //defined above, see the example
});
}
roomName(roomData) {
if (!roomData.name) {
return roomData.label;
} else {
return roomData.name;
}
}
condition() {
return RocketChat.settings.get('Livechat_enabled') && RocketChat.authz.hasPermission('view-l-room');
}
}
```
Then for publishing of the data, you will need to provide the information about the new room with (on the server):
```javascript
RocketChat.roomTypes.setPublish('l', (identifier) => {
return RocketChat.models.Rooms.findByTypeAndName('l', identifier, {
fields: {
name: 1,
t: 1,
cl: 1,
u: 1,
usernames: 1,
v: 1
}
});
});
```
### AccountBox
You can add items to the left upper corner drop menu:

@ -4,6 +4,5 @@ import './OAuthProxy';
import './methods/sendMessage';
import './views/customFieldsForm.html';
import './views/customFieldsForm';
import '../startup/defaultRoomTypes';
export * from './lib';

@ -1,18 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { getUserPreference, RoomTypeConfig } from '../../../utils';
export class ConversationRoomType extends RoomTypeConfig {
constructor() {
super({
identifier: 'merged',
order: 30,
label: 'Conversations',
});
}
condition() {
// returns true only if sidebarGroupByType is not set
return !getUserPreference(Meteor.userId(), 'sidebarGroupByType');
}
}

@ -1,215 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { ChatRoom, Subscriptions } from '../../../models';
import { openRoom } from '../../../ui-utils';
import { getUserPreference, RoomTypeConfig, RoomTypeRouteConfig, RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../../../utils';
import { hasPermission, hasAtLeastOnePermission } from '../../../authorization';
import { settings } from '../../../settings';
import { getUserAvatarURL } from '../../../utils/lib/getUserAvatarURL';
import { getAvatarURL } from '../../../utils/lib/getAvatarURL';
export class DirectMessageRoomRoute extends RoomTypeRouteConfig {
constructor() {
super({
name: 'direct',
path: '/direct/:rid/:tab?/:context?',
});
}
action(params) {
return openRoom('d', params.rid);
}
link(sub) {
return { rid: sub.rid || sub.name };
}
}
export class DirectMessageRoomType extends RoomTypeConfig {
constructor() {
super({
identifier: 'd',
order: 50,
icon: 'at',
label: 'Direct_Messages',
route: new DirectMessageRoomRoute(),
});
}
getIcon(roomData) {
if (this.isGroupChat(roomData)) {
return 'balloon';
}
return this.icon;
}
findRoom(identifier) {
if (!hasPermission('view-d-room')) {
return null;
}
const query = {
t: 'd',
$or: [{ name: identifier }, { rid: identifier }],
};
const subscription = Subscriptions.findOne(query);
if (subscription && subscription.rid) {
return ChatRoom.findOne(subscription.rid);
}
}
roomName(roomData) {
// this function can receive different types of data
// if it doesn't have fname and name properties, should be a Room object
// so, need to find the related subscription
const subscription = roomData && (roomData.fname || roomData.name) ? roomData : Subscriptions.findOne({ rid: roomData._id });
if (subscription === undefined) {
return;
}
if (settings.get('UI_Use_Real_Name') && subscription.fname) {
return subscription.fname;
}
return subscription.name;
}
secondaryRoomName(roomData) {
if (settings.get('UI_Use_Real_Name')) {
const subscription = Subscriptions.findOne({ rid: roomData._id }, { fields: { name: 1 } });
return subscription && subscription.name;
}
}
condition() {
const groupByType = getUserPreference(Meteor.userId(), 'sidebarGroupByType');
return groupByType && hasAtLeastOnePermission(['view-d-room', 'view-joined-room']);
}
getUserStatus(roomId) {
const subscription = Subscriptions.findOne({ rid: roomId });
if (subscription == null) {
return;
}
return Session.get(`user_${subscription.name}_status`);
}
getUserStatusText(roomId) {
const subscription = Subscriptions.findOne({ rid: roomId });
if (subscription == null) {
return;
}
return Session.get(`user_${subscription.name}_status_text`);
}
allowRoomSettingChange(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.get('E2E_Enable') === true;
default:
return true;
}
}
allowMemberAction(room, action) {
switch (action) {
case RoomMemberActions.BLOCK:
return !this.isGroupChat(room);
default:
return false;
}
}
enableMembersListProfile() {
return true;
}
userDetailShowAll(/* room */) {
return true;
}
getUiText(context) {
switch (context) {
case UiTextContext.HIDE_WARNING:
return 'Hide_Private_Warning';
case UiTextContext.LEAVE_WARNING:
return 'Leave_Private_Warning';
default:
return '';
}
}
/**
* Returns details to use on notifications
*
* @param {object} room
* @param {object} user
* @param {string} notificationMessage
* @return {object} Notification details
*/
getNotificationDetails(room, user, notificationMessage) {
if (!Meteor.isServer) {
return {};
}
if (this.isGroupChat(room)) {
return {
title: this.roomName(room),
text: `${(settings.get('UI_Use_Real_Name') && user.name) || user.username}: ${notificationMessage}`,
};
}
return {
title: (settings.get('UI_Use_Real_Name') && user.name) || user.username,
text: notificationMessage,
};
}
getAvatarPath(roomData, subData) {
if (!roomData && !subData) {
return '';
}
// if coming from sidenav search
if (roomData.name && roomData.avatarETag) {
return getUserAvatarURL(roomData.name, roomData.avatarETag);
}
if (this.isGroupChat(roomData)) {
return getAvatarURL({ username: roomData.uids.length + roomData.usernames.join() });
}
const sub = subData || Subscriptions.findOne({ rid: roomData._id }, { fields: { name: 1 } });
if (sub && sub.name) {
const user = Meteor.users.findOne({ username: sub.name }, { fields: { username: 1, avatarETag: 1 } });
return getUserAvatarURL(user?.username || sub.name, user?.avatarETag);
}
if (roomData) {
return getUserAvatarURL(roomData.name || this.roomName(roomData)); // rooms should have no name for direct messages...
}
}
includeInDashboard() {
return true;
}
isGroupChat(room) {
return room && room.uids && room.uids.length > 2;
}
}

@ -1,20 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { settings } from '../../../settings';
import { getUserPreference, RoomTypeConfig } from '../../../utils';
export class FavoriteRoomType extends RoomTypeConfig {
constructor() {
super({
identifier: 'f',
order: 20,
header: 'favorite',
icon: 'star',
label: 'Favorites',
});
}
condition() {
return settings.get('Favorite_Rooms') && getUserPreference(Meteor.userId(), 'sidebarShowFavorites');
}
}

@ -1,8 +0,0 @@
import { ConversationRoomType } from './conversation';
import { DirectMessageRoomType } from './direct';
import { FavoriteRoomType } from './favorite';
import { PrivateRoomType } from './private';
import { PublicRoomType } from './public';
import { UnreadRoomType } from './unread';
export { ConversationRoomType, DirectMessageRoomType, FavoriteRoomType, PrivateRoomType, PublicRoomType, UnreadRoomType };

@ -1,126 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { ChatRoom } from '../../../models';
import { openRoom } from '../../../ui-utils';
import { settings } from '../../../settings';
import { hasAtLeastOnePermission, hasPermission } from '../../../authorization';
import { getUserPreference, RoomSettingsEnum, RoomTypeConfig, RoomTypeRouteConfig, UiTextContext, RoomMemberActions } from '../../../utils';
import { getAvatarURL } from '../../../utils/lib/getAvatarURL';
export class PrivateRoomRoute extends RoomTypeRouteConfig {
constructor() {
super({
name: 'group',
path: '/group/:name/:tab?/:context?',
});
}
action(params) {
return openRoom('p', params.name);
}
}
export class PrivateRoomType extends RoomTypeConfig {
constructor() {
super({
identifier: 'p',
order: 40,
icon: 'hashtag-lock',
label: 'Private_Groups',
route: new PrivateRoomRoute(),
});
}
getIcon(roomData) {
if (roomData.prid) {
return 'discussion';
}
if (roomData.teamMain) {
return 'team-lock';
}
return this.icon;
}
findRoom(identifier) {
const query = {
t: 'p',
name: identifier,
};
return ChatRoom.findOne(query);
}
roomName(roomData) {
if (roomData.prid) {
return roomData.fname;
}
if (settings.get('UI_Allow_room_names_with_special_chars')) {
return roomData.fname || roomData.name;
}
return roomData.name;
}
condition() {
const groupByType = getUserPreference(Meteor.userId(), 'sidebarGroupByType');
return groupByType && hasPermission('view-p-room');
}
isGroupChat() {
return true;
}
canAddUser(room) {
return hasAtLeastOnePermission(['add-user-to-any-p-room', 'add-user-to-joined-room'], room._id);
}
allowRoomSettingChange(room, setting) {
switch (setting) {
case RoomSettingsEnum.JOIN_CODE:
return false;
case RoomSettingsEnum.BROADCAST:
return room.broadcast;
case RoomSettingsEnum.READ_ONLY:
return !room.broadcast;
case RoomSettingsEnum.REACT_WHEN_READ_ONLY:
return !room.broadcast && room.ro;
case RoomSettingsEnum.E2E:
return settings.get('E2E_Enable') === true;
case RoomSettingsEnum.SYSTEM_MESSAGES:
default:
return true;
}
}
allowMemberAction(room, action) {
switch (action) {
case RoomMemberActions.BLOCK:
return false;
default:
return true;
}
}
enableMembersListProfile() {
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(roomData) {
return getAvatarURL({ roomId: roomData._id, cache: roomData.avatarETag });
}
includeInDashboard() {
return true;
}
}

@ -1,136 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { openRoom } from '../../../ui-utils';
import { ChatRoom } from '../../../models';
import { settings } from '../../../settings';
import { hasAtLeastOnePermission } from '../../../authorization';
import { getUserPreference, RoomTypeConfig, RoomTypeRouteConfig, RoomSettingsEnum, UiTextContext, RoomMemberActions } from '../../../utils';
import { getAvatarURL } from '../../../utils/lib/getAvatarURL';
export class PublicRoomRoute extends RoomTypeRouteConfig {
constructor() {
super({
name: 'channel',
path: '/channel/:name/:tab?/:context?',
});
}
action(params) {
return openRoom('c', params.name);
}
}
export class PublicRoomType extends RoomTypeConfig {
constructor() {
super({
identifier: 'c',
order: 30,
icon: 'hashtag',
label: 'Channels',
route: new PublicRoomRoute(),
});
}
getIcon(roomData) {
if (roomData.prid) {
return 'discussion';
}
if (roomData.teamMain) {
return 'team';
}
return this.icon;
}
findRoom(identifier) {
const query = {
t: 'c',
name: identifier,
};
return ChatRoom.findOne(query);
}
roomName(roomData) {
if (roomData.prid) {
return roomData.fname;
}
if (settings.get('UI_Allow_room_names_with_special_chars')) {
return roomData.fname || roomData.name;
}
return roomData.name;
}
condition() {
const groupByType = getUserPreference(Meteor.userId(), 'sidebarGroupByType');
return (
groupByType && (hasAtLeastOnePermission(['view-c-room', 'view-joined-room']) || settings.get('Accounts_AllowAnonymousRead') === true)
);
}
showJoinLink(roomId) {
return !!ChatRoom.findOne({ _id: roomId, t: 'c' });
}
includeInRoomSearch() {
return true;
}
isGroupChat() {
return true;
}
includeInDashboard() {
return true;
}
canAddUser(room) {
return hasAtLeastOnePermission(['add-user-to-any-c-room', 'add-user-to-joined-room'], room._id);
}
enableMembersListProfile() {
return true;
}
allowRoomSettingChange(room, setting) {
switch (setting) {
case RoomSettingsEnum.BROADCAST:
return room.broadcast;
case RoomSettingsEnum.READ_ONLY:
return !room.broadcast;
case RoomSettingsEnum.REACT_WHEN_READ_ONLY:
return !room.broadcast && room.ro;
case RoomSettingsEnum.E2E:
return false;
case RoomSettingsEnum.SYSTEM_MESSAGES:
default:
return true;
}
}
allowMemberAction(room, action) {
switch (action) {
case RoomMemberActions.BLOCK:
return false;
default:
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(roomData) {
return getAvatarURL({ roomId: roomData._id, cache: roomData.avatarETag });
}
getDiscussionType() {
return 'c';
}
}

@ -1,19 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { getUserPreference, RoomTypeConfig } from '../../../utils';
export class UnreadRoomType extends RoomTypeConfig {
constructor() {
super({
identifier: 'unread',
order: 10,
label: 'Unread',
});
this.unread = true;
}
condition() {
return getUserPreference(Meteor.userId(), 'sidebarShowUnread');
}
}

@ -5,14 +5,18 @@ import { AppEvents, Apps } from '../../../apps/server';
import { callbacks } from '../../../../lib/callbacks';
import { Messages, Rooms, Subscriptions } from '../../../models';
import { Team } from '../../../../server/sdk';
import { RoomMemberActions, roomTypes } from '../../../utils/server';
import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
export const addUserToRoom = function (rid, user, inviter, silenced) {
const now = new Date();
const room = Rooms.findOneById(rid);
const roomConfig = roomTypes.getConfig(room.t);
if (!roomConfig.allowMemberAction(room, RoomMemberActions.JOIN) && !roomConfig.allowMemberAction(room, RoomMemberActions.INVITE)) {
const roomDirectives = roomCoordinator.getRoomDirectives(room.t);
if (
!roomDirectives?.allowMemberAction(room, RoomMemberActions.JOIN) &&
!roomDirectives?.allowMemberAction(room, RoomMemberActions.INVITE)
) {
return;
}

@ -1,5 +1,5 @@
import { getUserAvatarURL } from '../../../utils/lib/getUserAvatarURL';
import { roomTypes } from '../../../utils';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
export const attachMessage = function (message, room) {
const {
@ -13,7 +13,7 @@ export const attachMessage = function (message, room) {
text: msg,
author_name: username,
author_icon: getUserAvatarURL(username),
message_link: `${roomTypes.getRouteLink(room.t, room)}?msg=${_id}`,
message_link: `${roomCoordinator.getRouteLink(room.t, room)}?msg=${_id}`,
attachments,
ts,
};

@ -1,7 +1,7 @@
import { metrics } from '../../../../metrics';
import { settings } from '../../../../settings';
import { Notifications } from '../../../../notifications';
import { roomTypes } from '../../../../utils';
import { roomCoordinator } from '../../../../../server/lib/rooms/roomCoordinator';
/**
* Send notification to user
*
@ -13,7 +13,7 @@ import { roomTypes } from '../../../../utils';
* @param {string} notificationMessage The message text to send on notification body
*/
export function notifyDesktopUser({ userId, user, message, room, duration, notificationMessage }) {
const { title, text } = roomTypes.getConfig(room.t).getNotificationDetails(room, user, notificationMessage);
const { title, text } = roomCoordinator.getRoomDirectives(room.t)?.getNotificationDetails(room, user, notificationMessage);
metrics.notificationsSent.inc({ notification_type: 'desktop' });
Notifications.notifyUser(userId, 'notification', {

@ -5,10 +5,10 @@ import { escapeHTML } from '@rocket.chat/string-helpers';
import * as Mailer from '../../../../mailer';
import { settings } from '../../../../settings/server';
import { roomTypes } from '../../../../utils';
import { metrics } from '../../../../metrics';
import { callbacks } from '../../../../../lib/callbacks';
import { getURL } from '../../../../utils/server';
import { roomCoordinator } from '../../../../../server/lib/rooms/roomCoordinator';
let advice = '';
let goToMessage = '';
@ -24,12 +24,12 @@ Meteor.startup(() => {
function getEmailContent({ message, user, room }) {
const lng = (user && user.language) || settings.get('Language') || 'en';
const roomName = escapeHTML(`#${roomTypes.getRoomName(room.t, room)}`);
const roomName = escapeHTML(`#${roomCoordinator.getRoomName(room.t, room)}`);
const userName = escapeHTML(settings.get('UI_Use_Real_Name') ? message.u.name || message.u.username : message.u.username);
const roomType = roomTypes.getConfig(room.t);
const roomDirectives = roomCoordinator.getRoomDirectives(room.t);
const header = TAPi18n.__(!roomType.isGroupChat(room) ? 'User_sent_a_message_to_you' : 'User_sent_a_message_on_channel', {
const header = TAPi18n.__(!roomDirectives.isGroupChat(room) ? 'User_sent_a_message_to_you' : 'User_sent_a_message_on_channel', {
username: userName,
channel: roomName,
lng,
@ -57,7 +57,7 @@ function getEmailContent({ message, user, room }) {
}
if (message.file) {
const fileHeader = TAPi18n.__(!roomType.isGroupChat(room) ? 'User_uploaded_a_file_to_you' : 'User_uploaded_a_file_on_channel', {
const fileHeader = TAPi18n.__(!roomDirectives.isGroupChat(room) ? 'User_uploaded_a_file_to_you' : 'User_uploaded_a_file_on_channel', {
username: userName,
channel: roomName,
lng,
@ -99,7 +99,9 @@ function getEmailContent({ message, user, room }) {
}
const getButtonUrl = (room, subscription, message) => {
const path = `${s.ltrim(roomTypes.getRelativePath(room.t, subscription), '/')}?msg=${message._id}`;
const basePath = roomCoordinator.getRouteLink(room.t, subscription).replace(Meteor.absoluteUrl(), '');
const path = `${s.ltrim(basePath, '/')}?msg=${message._id}`;
return getURL(path, {
full: true,
cloud: settings.get('Offline_Message_Use_DeepLink'),
@ -119,7 +121,7 @@ export function getEmailData({ message, receiver, sender, subscription, room, em
const username = settings.get('UI_Use_Real_Name') ? message.u.name || message.u.username : message.u.username;
let subjectKey = 'Offline_Mention_All_Email';
if (!roomTypes.getConfig(room.t).isGroupChat(room)) {
if (!roomCoordinator.getRoomDirectives(room.t)?.isGroupChat(room)) {
subjectKey = 'Offline_DM_Email';
} else if (hasMentionToUser) {
subjectKey = 'Offline_Mention_Email';
@ -127,7 +129,7 @@ export function getEmailData({ message, receiver, sender, subscription, room, em
const emailSubject = Mailer.replace(settings.get(subjectKey), {
user: username,
room: roomTypes.getRoomName(room.t, room),
room: roomCoordinator.getRoomName(room.t, room),
});
const content = getEmailContent({
message,

@ -2,7 +2,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { settings } from '../../../../settings';
import { Subscriptions } from '../../../../models/server/raw';
import { roomTypes } from '../../../../utils';
import { roomCoordinator } from '../../../../../server/lib/rooms/roomCoordinator';
const CATEGORY_MESSAGE = 'MESSAGE';
const CATEGORY_MESSAGE_NOREPLY = 'MESSAGE_NOREPLY';
@ -54,8 +54,8 @@ export async function getPushData({
...(message.t === 'e2e' && { msg: message.msg }),
},
roomName:
settings.get('Push_show_username_room') && roomTypes.getConfig(room.t).isGroupChat(room)
? `#${roomTypes.getRoomName(room.t, room)}`
settings.get('Push_show_username_room') && roomCoordinator.getRoomDirectives(room.t)?.isGroupChat(room)
? `#${roomCoordinator.getRoomName(room.t, room)}`
: '',
username,
message: messageText,

@ -7,7 +7,6 @@ import './startup/settingsOnLoadCdnPrefix';
import './startup/settingsOnLoadDirectReply';
import './startup/settingsOnLoadSMTP';
import '../lib/MessageTypes';
import '../startup/defaultRoomTypes';
import './lib/bugsnag';
import './lib/debug';
import './lib/loginErrorMessageOverride';

@ -5,7 +5,6 @@ import { hasPermission } from '../../../authorization';
import { settings } from '../../../settings/server';
import { callbacks } from '../../../../lib/callbacks';
import { Subscriptions, Users } from '../../../models/server';
import { roomTypes } from '../../../utils';
import {
callJoinRoom,
messageContainsHighlight,
@ -17,6 +16,7 @@ import { getPushData, shouldNotifyMobile } from '../functions/notifications/mobi
import { notifyDesktopUser, shouldNotifyDesktop } from '../functions/notifications/desktop';
import { Notification } from '../../../notification-queue/server/NotificationQueue';
import { getMentions } from './notifyUsersOnMessage';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
let TroubleshootDisableNotifications;
@ -217,7 +217,7 @@ export async function sendMessageNotifications(message, room, usersInThread = []
return;
}
const sender = roomTypes.getConfig(room.t).getMsgSender(message.u._id);
const sender = roomCoordinator.getRoomDirectives(room.t)?.getMsgSender(message.u._id);
if (!sender) {
return message;
}

@ -4,7 +4,8 @@ import { check } from 'meteor/check';
import { Rooms } from '../../../models/server';
import { hasPermission } from '../../../authorization/server';
import { archiveRoom } from '../functions';
import { roomTypes, RoomMemberActions } from '../../../utils/server';
import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
Meteor.methods({
archiveRoom(rid) {
@ -20,7 +21,7 @@ Meteor.methods({
throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'archiveRoom' });
}
if (!roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.ARCHIVE)) {
if (!roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.ARCHIVE)) {
throw new Meteor.Error('error-direct-message-room', `rooms type: ${room.t} can not be archived`, { method: 'archiveRoom' });
}

@ -1,8 +1,9 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { roomTypes, RoomMemberActions } from '../../../utils/server';
import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig';
import { Rooms, Subscriptions } from '../../../models/server';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
Meteor.methods({
blockUser({ rid, blocked }) {
@ -15,7 +16,7 @@ Meteor.methods({
const room = Rooms.findOne({ _id: rid });
if (!roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.BLOCK)) {
if (!roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.BLOCK)) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'blockUser' });
}

@ -5,7 +5,8 @@ import { hasPermission, canAccessRoom } from '../../../authorization/server';
import { Rooms } from '../../../models/server';
import { Tokenpass, updateUserTokenpassBalances } from '../../../tokenpass/server';
import { addUserToRoom } from '../functions';
import { roomTypes, RoomMemberActions } from '../../../utils/server';
import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
Meteor.methods({
joinRoom(rid, code) {
@ -23,7 +24,7 @@ Meteor.methods({
throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'joinRoom' });
}
if (!roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.JOIN)) {
if (!roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.JOIN)) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'joinRoom' });
}

@ -4,8 +4,9 @@ import { check } from 'meteor/check';
import { hasPermission, hasRole } from '../../../authorization/server';
import { Subscriptions, Rooms } from '../../../models/server';
import { removeUserFromRoom } from '../functions';
import { roomTypes, RoomMemberActions } from '../../../utils/server';
import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig';
import { Roles } from '../../../models/server/raw';
import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
Meteor.methods({
async leaveRoom(rid) {
@ -18,7 +19,7 @@ Meteor.methods({
const room = Rooms.findOneById(rid);
const user = Meteor.user();
if (!user || !roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.LEAVE)) {
if (!user || !roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.LEAVE)) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'leaveRoom' });
}

@ -1,16 +0,0 @@
import { roomTypes } from '../../utils';
import {
ConversationRoomType,
DirectMessageRoomType,
FavoriteRoomType,
PrivateRoomType,
PublicRoomType,
UnreadRoomType,
} from '../lib/roomTypes';
roomTypes.add(new UnreadRoomType());
roomTypes.add(new FavoriteRoomType());
roomTypes.add(new ConversationRoomType());
roomTypes.add(new PublicRoomType());
roomTypes.add(new PrivateRoomType());
roomTypes.add(new DirectMessageRoomType());

@ -1,5 +1,4 @@
import '../lib/messageTypes';
import './roomType';
import './route';
import './ui';
import './tabBar';

@ -1,4 +0,0 @@
import { roomTypes } from '../../utils';
import LivechatRoomType from '../lib/LivechatRoomType';
roomTypes.add(new LivechatRoomType());

@ -11,7 +11,7 @@ import UAParser from 'ua-parser-js';
import { modal } from '../../../../../ui-utils';
import { Subscriptions } from '../../../../../models';
import { settings } from '../../../../../settings';
import { t, roomTypes } from '../../../../../utils';
import { t } from '../../../../../utils';
import { hasRole, hasPermission, hasAtLeastOnePermission } from '../../../../../authorization';
import './visitorInfo.html';
import { APIClient } from '../../../../../utils/client';
@ -20,6 +20,7 @@ import { getCustomFormTemplate } from '../customTemplates/register';
import { Markdown } from '../../../../../markdown/client';
import { handleError } from '../../../../../../client/lib/utils/handleError';
import { formatDateAndTime } from '../../../../../../client/lib/utils/formatDateAndTime';
import { roomCoordinator } from '../../../../../../client/lib/rooms/roomCoordinator';
const isSubscribedToRoom = () => {
const data = Template.currentData();
@ -55,7 +56,7 @@ Template.visitorInfo.helpers({
user.browser = `${ua.getBrowser().name} ${ua.getBrowser().version}`;
user.browserIcon = `icon-${ua.getBrowser().name.toLowerCase()}`;
user.status = roomTypes.getUserStatus('l', this.rid) || 'offline';
user.status = roomCoordinator.getRoomDirectives('l')?.getUserStatus(this.rid) || 'offline';
}
return user;
},

@ -4,10 +4,11 @@ import { Template } from 'meteor/templating';
import { dispatchToastMessage } from '../../../../../../client/lib/toast';
import { handleError } from '../../../../../../client/lib/utils/handleError';
import { t, roomTypes } from '../../../../../utils';
import { t } from '../../../../../utils';
import { APIClient } from '../../../../../utils/client';
import './visitorTranscript.html';
import { validateEmail } from '../../../../../../lib/emailValidator';
import { roomCoordinator } from '../../../../../../client/lib/rooms/roomCoordinator';
const validateTranscriptData = (instance) => {
const subject = instance.$('[name="subject"]').val();
@ -63,7 +64,7 @@ Template.visitorTranscript.helpers({
return room.transcriptRequest.subject;
}
return t('Transcript_of_your_livechat_conversation') || (room && roomTypes.getRoomName(room.t, room));
return t('Transcript_of_your_livechat_conversation') || (room && roomCoordinator.getRoomName(room.t, room));
},
errorEmail() {
const instance = Template.instance();

@ -1,133 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { ChatRoom, ChatSubscription } from '../../models';
import { settings } from '../../settings';
import { hasPermission } from '../../authorization';
import { openRoom } from '../../ui-utils';
import { RoomMemberActions, RoomSettingsEnum, UiTextContext, RoomTypeRouteConfig, RoomTypeConfig } from '../../utils';
import { getAvatarURL } from '../../utils/lib/getAvatarURL';
let LivechatInquiry;
if (Meteor.isClient) {
({ LivechatInquiry } = require('../client/collections/LivechatInquiry'));
}
class LivechatRoomRoute extends RoomTypeRouteConfig {
constructor() {
super({
name: 'live',
path: '/live/:id/:tab?/:context?',
});
}
action(params) {
openRoom('l', params.id);
}
link(sub) {
return {
id: sub.rid,
};
}
}
export default class LivechatRoomType extends RoomTypeConfig {
constructor() {
super({
identifier: 'l',
order: 5,
icon: 'omnichannel',
label: 'Omnichannel',
route: new LivechatRoomRoute(),
});
this.notSubscribedTpl = 'livechatNotSubscribed';
this.readOnlyTpl = 'livechatReadOnly';
}
enableMembersListProfile() {
return true;
}
findRoom(identifier) {
return ChatRoom.findOne({ _id: identifier });
}
roomName(roomData) {
return roomData.name || roomData.fname || roomData.label;
}
condition() {
return settings.get('Livechat_enabled') && hasPermission('view-l-room');
}
canSendMessage(rid) {
const room = ChatRoom.findOne({ _id: rid }, { fields: { open: 1 } });
return room && room.open === true;
}
getUserStatus(rid) {
const room = Session.get(`roomData${rid}`);
if (room) {
return room.v && room.v.status;
}
const inquiry = LivechatInquiry.findOne({ rid });
return inquiry && inquiry.v && inquiry.v.status;
}
allowRoomSettingChange(room, setting) {
switch (setting) {
case RoomSettingsEnum.JOIN_CODE:
return false;
default:
return true;
}
}
allowMemberAction(room, action) {
return [RoomMemberActions.INVITE, RoomMemberActions.JOIN].includes(action);
}
getUiText(context) {
switch (context) {
case UiTextContext.HIDE_WARNING:
return 'Hide_Livechat_Warning';
case UiTextContext.LEAVE_WARNING:
return 'Hide_Livechat_Warning';
default:
return '';
}
}
readOnly(rid) {
const room = ChatRoom.findOne({ _id: rid }, { fields: { open: 1, servedBy: 1 } });
if (!room || !room.open) {
return true;
}
const subscription = ChatSubscription.findOne({ rid });
return !subscription;
}
getAvatarPath(roomData) {
return getAvatarURL({ username: `@${this.roomName(roomData)}` });
}
openCustomProfileTab(instance, room, username) {
if (!room || !room.v || room.v.username !== username) {
return false;
}
instance.tabBar.openUserInfo();
return true;
}
showQuickActionButtons() {
return true;
}
isLivechatRoom() {
return true;
}
}

@ -4,7 +4,6 @@ import './startup';
import './visitorStatus';
import './agentStatus';
import '../lib/messageTypes';
import './roomType';
import './hooks/beforeCloseRoom';
import './hooks/beforeDelegateAgent';
import './hooks/leadCapture';

@ -1,35 +0,0 @@
import { LivechatRooms, LivechatVisitors } from '../../models';
import { roomTypes } from '../../utils';
import LivechatRoomType from '../lib/LivechatRoomType';
class LivechatRoomTypeServer extends LivechatRoomType {
getMsgSender(senderId) {
return LivechatVisitors.findOneById(senderId);
}
/**
* Returns details to use on notifications
*
* @param {object} room
* @param {object} user
* @param {string} notificationMessage
* @return {object} Notification details
*/
getNotificationDetails(room, user, notificationMessage) {
const title = `[Omnichannel] ${this.roomName(room)}`;
const text = notificationMessage;
return { title, text };
}
canAccessUploadedFile({ rc_token, rc_rid } = {}) {
return rc_token && rc_rid && LivechatRooms.findOneOpenByRoomIdAndVisitorToken(rc_rid, rc_token);
}
getReadReceiptsExtraData(message) {
const { token } = message;
return { token };
}
}
roomTypes.add(new LivechatRoomTypeServer());

@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { roomTypes } from '../../utils';
import { roomCoordinator } from '../../../server/lib/rooms/roomCoordinator';
import { LivechatRooms } from '../../models';
import { callbacks } from '../../../lib/callbacks';
import { settings } from '../../settings/server';
@ -16,7 +16,7 @@ import { RoutingManager } from './lib/RoutingManager';
import './roomAccessValidator.internalService';
Meteor.startup(async () => {
roomTypes.setRoomFind('l', (_id) => LivechatRooms.findOneById(_id));
roomCoordinator.setRoomFind('l', (_id) => LivechatRooms.findOneById(_id));
callbacks.add(
'beforeLeaveRoom',

@ -4,8 +4,8 @@ import { FlowRouter } from 'meteor/kadira:flow-router';
import { RoomManager, MessageAction } from '../../ui-utils';
import { messageArgs } from '../../ui-utils/client/lib/messageArgs';
import { ChatSubscription } from '../../models';
import { roomTypes } from '../../utils/client';
import { handleError } from '../../../client/lib/utils/handleError';
import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
Meteor.startup(() => {
MessageAction.addButton({
@ -30,7 +30,7 @@ Meteor.startup(() => {
});
},
condition({ msg, u, room }) {
const isLivechatRoom = roomTypes.isLivechatRoom(room.t);
const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
if (isLivechatRoom) {
return false;
}

@ -8,9 +8,9 @@ import { messageArgs } from '../../ui-utils/client/lib/messageArgs';
import { settings } from '../../settings';
import { hasAtLeastOnePermission } from '../../authorization';
import { Rooms } from '../../models/client';
import { roomTypes } from '../../utils/client';
import { handleError } from '../../../client/lib/utils/handleError';
import { dispatchToastMessage } from '../../../client/lib/toast';
import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
Meteor.startup(function () {
MessageAction.addButton({
@ -31,7 +31,7 @@ Meteor.startup(function () {
if (!settings.get('Message_AllowPinning') || msg.pinned || !subscription) {
return false;
}
const isLivechatRoom = roomTypes.isLivechatRoom(room.t);
const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
if (isLivechatRoom) {
return false;
}

@ -7,9 +7,9 @@ import { settings } from '../../settings';
import { RoomHistoryManager, MessageAction } from '../../ui-utils';
import { messageArgs } from '../../ui-utils/client/lib/messageArgs';
import { Rooms } from '../../models/client';
import { roomTypes } from '../../utils/client';
import { handleError } from '../../../client/lib/utils/handleError';
import { dispatchToastMessage } from '../../../client/lib/toast';
import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
Meteor.startup(function () {
MessageAction.addButton({
@ -30,7 +30,7 @@ Meteor.startup(function () {
if (subscription == null && settings.get('Message_AllowStarring')) {
return false;
}
const isLivechatRoom = roomTypes.isLivechatRoom(room.t);
const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
if (isLivechatRoom) {
return false;
}

@ -1,12 +1,12 @@
import { Meteor } from 'meteor/meteor';
import { Blaze } from 'meteor/blaze';
import { roomTypes } from '../../utils/client';
import { Rooms, Subscriptions } from '../../models';
import { MessageAction } from '../../ui-utils';
import { messageArgs } from '../../ui-utils/client/lib/messageArgs';
import { EmojiPicker } from '../../emoji';
import { tooltip } from '../../ui/client/components/tooltip';
import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
export const EmojiEvents = {
'click .add-reaction'(event) {
@ -31,7 +31,7 @@ export const EmojiEvents = {
return false;
}
if (roomTypes.readOnly(room._id, user._id) && !room.reactWhenReadOnly) {
if (roomCoordinator.readOnly(room._id, user) && !room.reactWhenReadOnly) {
return false;
}
@ -87,10 +87,10 @@ Meteor.startup(function () {
return false;
}
if (roomTypes.readOnly(room._id, user._id) && !room.reactWhenReadOnly) {
if (roomCoordinator.readOnly(room._id, user) && !room.reactWhenReadOnly) {
return false;
}
const isLivechatRoom = roomTypes.isLivechatRoom(room.t);
const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
if (isLivechatRoom) {
return false;
}

@ -4,7 +4,7 @@ import _ from 'underscore';
import { Messages, Rooms, Subscriptions } from '../../../models';
import { callbacks } from '../../../../lib/callbacks';
import { emoji } from '../../../emoji';
import { roomTypes } from '../../../utils/client';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
Meteor.methods({
setReaction(reaction, messageId) {
@ -25,7 +25,7 @@ Meteor.methods({
return false;
}
if (roomTypes.readOnly(room._id, user._id)) {
if (roomCoordinator.readOnly(room._id, user)) {
return false;
}

@ -2,7 +2,8 @@ import { Meteor } from 'meteor/meteor';
import { Match } from 'meteor/check';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { slashCommands, roomTypes } from '../../utils';
import { slashCommands } from '../../utils';
import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
import { ChatSubscription, Subscriptions } from '../../models';
function Open(command, params /* , item*/) {
@ -32,7 +33,7 @@ function Open(command, params /* , item*/) {
const subscription = ChatSubscription.findOne(query);
if (subscription) {
roomTypes.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams);
roomCoordinator.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams);
}
if (type && type.indexOf('d') === -1) {
@ -43,7 +44,7 @@ function Open(command, params /* , item*/) {
return;
}
const subscription = Subscriptions.findOne(query);
roomTypes.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams);
roomCoordinator.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams);
});
}

@ -3,9 +3,10 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { Rooms, Messages } from '../../models/server';
import { slashCommands } from '../../utils/lib/slashCommand';
import { roomTypes, RoomMemberActions } from '../../utils/server';
import { RoomMemberActions } from '../../../definition/IRoomTypeConfig';
import { settings } from '../../settings/server';
import { api } from '../../../server/sdk/api';
import { roomCoordinator } from '../../../server/lib/rooms/roomCoordinator';
function Unarchive(_command: 'unarchive', params: string, item: Record<string, string>): void | Promise<void> | Function {
let channel = params.trim();
@ -32,7 +33,7 @@ function Unarchive(_command: 'unarchive', params: string, item: Record<string, s
}
// You can not archive direct messages.
if (!roomTypes.getConfig(room.t).allowMemberAction(room, RoomMemberActions.ARCHIVE)) {
if (!roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.ARCHIVE)) {
return;
}

@ -6,9 +6,9 @@ import { Messages } from '../../../models/client';
import { settings } from '../../../settings/client';
import { MessageAction } from '../../../ui-utils/client';
import { messageArgs } from '../../../ui-utils/client/lib/messageArgs';
import { roomTypes } from '../../../utils/client';
import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling';
import { dispatchToastMessage } from '../../../../client/lib/toast';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
Meteor.startup(function () {
Tracker.autorun(() => {
@ -36,7 +36,7 @@ Meteor.startup(function () {
replies = parentMessage.replies || [];
}
}
const isLivechatRoom = roomTypes.isLivechatRoom(room.t);
const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
if (isLivechatRoom) {
return false;
}

@ -5,7 +5,7 @@ import { FlowRouter } from 'meteor/kadira:flow-router';
import { settings } from '../../../settings/client';
import { MessageAction } from '../../../ui-utils/client';
import { messageArgs } from '../../../ui-utils/client/lib/messageArgs';
import { roomTypes } from '../../../utils/client';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
Meteor.startup(function () {
Tracker.autorun(() => {
@ -26,7 +26,7 @@ Meteor.startup(function () {
});
},
condition({ subscription, room }) {
const isLivechatRoom = roomTypes.isLivechatRoom(room.t);
const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
if (isLivechatRoom) {
return false;
}

@ -1,6 +1,5 @@
import '../lib/common';
import './startup';
import './roomType';
import './tokenChannelsList.html';
import './tokenChannelsList';
import './tokenpassChannelSettings.html';

@ -1,23 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { roomTypes, RoomTypeConfig } from '../../utils';
class TokenPassRoomType extends RoomTypeConfig {
constructor() {
super({
identifier: 'tokenpass',
order: 1,
});
this.customTemplate = 'tokenChannelsList';
}
condition() {
const user = Meteor.users.findOne(Meteor.userId(), { fields: { 'services.tokenpass': 1 } });
const hasTokenpass = !!(user && user.services && user.services.tokenpass);
return hasTokenpass;
}
}
roomTypes.add(new TokenPassRoomType());

@ -12,7 +12,7 @@ import { normalizeThreadTitle } from '../../threads/client/lib/normalizeThreadTi
import { MessageTypes, MessageAction } from '../../ui-utils/client';
import { RoomRoles, UserRoles, Roles } from '../../models/client';
import { Markdown } from '../../markdown/client';
import { t, roomTypes } from '../../utils';
import { t } from '../../utils';
import { AutoTranslate } from '../../autotranslate/client';
import { renderMentions } from '../../mentions/client/client';
import { renderMessageBody } from '../../../client/lib/utils/renderMessageBody';
@ -21,6 +21,7 @@ import { formatTime } from '../../../client/lib/utils/formatTime';
import { formatDate } from '../../../client/lib/utils/formatDate';
import './messageThread';
import './message.html';
import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
const renderBody = (msg, settings) => {
const searchedText = msg.searchedText ? msg.searchedText : '';
@ -373,7 +374,7 @@ Template.message.helpers({
return true;
}
if (roomTypes.readOnly(room._id, u._id) && !room.reactWhenReadOnly) {
if (roomCoordinator.readOnly(room._id, u) && !room.reactWhenReadOnly) {
return true;
}
},
@ -422,7 +423,7 @@ Template.message.helpers({
if (room && room.t === 'd') {
return 'at';
}
return roomTypes.getIcon(room);
return roomCoordinator.getIcon(room);
},
customClass() {
const { customClass, msg } = this;

@ -13,7 +13,7 @@ import { Users } from '../../../models';
import { settings } from '../../../settings';
import { fileUpload, KonchatNotification } from '../../../ui';
import { messageBox, popover } from '../../../ui-utils';
import { t, roomTypes, getUserPreference } from '../../../utils/client';
import { t, getUserPreference } from '../../../utils/client';
import './messageBoxActions';
import './messageBoxReplyPreview';
import './userActionIndicator.ts';
@ -25,6 +25,7 @@ import { getImageExtensionFromMime } from '../../../../lib/getImageExtensionFrom
import { keyCodes } from '../../../../client/lib/utils/keyCodes';
import { isRTL } from '../../../../client/lib/utils/isRTL';
import { call } from '../../../../client/lib/utils/call';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
Template.messageBox.onCreated(function () {
this.state = new ReactiveDict();
@ -207,8 +208,8 @@ Template.messageBox.helpers({
return false;
}
const isReadOnly = roomTypes.readOnly(rid, Users.findOne({ _id: Meteor.userId() }, { fields: { username: 1 } }));
const isArchived = roomTypes.archived(rid) || (subscription && subscription.t === 'd' && subscription.archived);
const isReadOnly = roomCoordinator.readOnly(rid, Users.findOne({ _id: Meteor.userId() }, { fields: { username: 1 } }));
const isArchived = roomCoordinator.archived(rid) || (subscription && subscription.t === 'd' && subscription.archived);
return !isReadOnly && !isArchived;
},
@ -236,7 +237,7 @@ Template.messageBox.helpers({
return true;
}
return roomTypes.verifyCanSendMessage(rid);
return roomCoordinator.verifyCanSendMessage(rid);
},
actions() {
const actionGroups = messageBox.actions.get();

@ -4,21 +4,21 @@ import { Template } from 'meteor/templating';
import { settings } from '../../../settings/client';
import { RoomManager, RoomHistoryManager } from '../../../ui-utils/client';
import { roomTypes } from '../../../utils/client';
import { hasAllPermission } from '../../../authorization/client';
import './messageBoxNotSubscribed.html';
import { call } from '../../../../client/lib/utils/call';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
Template.messageBoxNotSubscribed.helpers({
customTemplate() {
return roomTypes.getNotSubscribedTpl(this.rid);
return roomCoordinator.getRoomTypeConfigById(this.rid)?.notSubscribedTpl;
},
canJoinRoom() {
return Meteor.userId() && roomTypes.verifyShowJoinLink(this.rid);
return Meteor.userId() && roomCoordinator.getRoomDirectivesById(this.rid)?.showJoinLink(this.rid);
},
roomName() {
const room = Session.get(`roomData${this.rid}`);
return roomTypes.getRoomName(room.t, room);
return roomCoordinator.getRoomName(room.t, room);
},
isJoinCodeRequired() {
const room = Session.get(`roomData${this.rid}`);

@ -1,10 +1,10 @@
import { Template } from 'meteor/templating';
import { roomTypes } from '../../../utils';
import './messageBoxReadOnly.html';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
Template.messageBoxReadOnly.helpers({
customTemplate() {
return roomTypes.getReadOnlyTpl(this.rid);
return roomCoordinator.getRoomTypeConfigById(this.rid).readOnlyTpl;
},
});

@ -1,10 +1,10 @@
import { Template } from 'meteor/templating';
import { roomTypes } from '../../../utils';
import './messagePopupChannel.html';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
Template.messagePopupChannel.helpers({
channelIcon() {
return roomTypes.getIcon(this);
return roomCoordinator.getIcon(this);
},
});

@ -3,8 +3,10 @@ import { Template } from 'meteor/templating';
import { callbacks } from '../../../lib/callbacks';
import { ChatSubscription, Rooms, Users, Subscriptions } from '../../models';
import { UiTextContext, getUserPreference, roomTypes } from '../../utils';
import { getUserPreference } from '../../utils';
import { UiTextContext } from '../../../definition/IRoomTypeConfig';
import { settings } from '../../settings';
import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
Template.roomList.helpers({
rooms() {
@ -119,7 +121,7 @@ Template.roomList.helpers({
if (instance.data.anonymous) {
return 'No_channels_yet';
}
return roomTypes.getConfig(instance.data.identifier).getUiText(UiTextContext.NO_ROOMS_SUBSCRIBED) || 'No_channels_yet';
return roomCoordinator.getRoomDirectives(instance.data.identifier)?.getUiText(UiTextContext.NO_ROOMS_SUBSCRIBED) || 'No_channels_yet';
},
});

@ -6,8 +6,9 @@ import { Template } from 'meteor/templating';
import { SideNav, menu } from '../../ui-utils';
import { settings } from '../../settings';
import { roomTypes, getUserPreference } from '../../utils';
import { getUserPreference } from '../../utils';
import { Users } from '../../models';
import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator';
Template.sideNav.helpers({
flexTemplate() {
@ -19,13 +20,12 @@ Template.sideNav.helpers({
},
roomType() {
return roomTypes.getTypes().map((roomType) => ({
template: roomType.customTemplate || 'roomList',
return roomCoordinator.getSortedTypes().map(({ config }) => ({
template: config.customTemplate || 'roomList',
data: {
header: roomType.header,
identifier: roomType.identifier,
isCombined: roomType.isCombined,
label: roomType.label,
header: config.header,
identifier: config.identifier,
label: config.label,
},
}));
},
@ -89,7 +89,7 @@ const redirectToDefaultChannelIfNeeded = () => {
return c.stop();
}
const room = roomTypes.findRoom('c', firstChannelAfterLogin, Meteor.userId());
const room = roomCoordinator.getRoomDirectives('c')?.findRoom(firstChannelAfterLogin);
if (!room) {
return;

@ -9,7 +9,7 @@ import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
import { messageArgs } from './messageArgs';
import { roomTypes } from '../../../utils/client';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
import { Messages, Rooms, Subscriptions } from '../../../models/client';
import { hasAtLeastOnePermission, hasPermission } from '../../../authorization/client';
import { modal } from './modal';
@ -139,7 +139,7 @@ export const MessageAction = new (class {
}
const subData = Subscriptions.findOne({ 'rid': roomData._id, 'u._id': Meteor.userId() });
const roomURL = roomTypes.getURL(roomData.t, subData || roomData);
const roomURL = roomCoordinator.getURL(roomData.t, subData || roomData);
return `${roomURL}?msg=${msgId}`;
}
})();
@ -160,7 +160,7 @@ Meteor.startup(async function () {
context: ['message', 'message-mobile', 'threads'],
action() {
const { msg } = messageArgs(this);
roomTypes.openRouteLink(
roomCoordinator.openRouteLink(
'd',
{ name: msg.u.username },
{
@ -211,7 +211,7 @@ Meteor.startup(async function () {
if (subscription == null) {
return false;
}
const isLivechatRoom = roomTypes.isLivechatRoom(room.t);
const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
if (isLivechatRoom) {
return false;
}
@ -312,7 +312,7 @@ Meteor.startup(async function () {
if (!subscription) {
return false;
}
const isLivechatRoom = roomTypes.isLivechatRoom(room.t);
const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
if (isLivechatRoom) {
return false;
}
@ -371,7 +371,7 @@ Meteor.startup(async function () {
);
},
condition({ subscription, room }) {
const isLivechatRoom = roomTypes.isLivechatRoom(room.t);
const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
if (isLivechatRoom) {
return false;
}

@ -10,7 +10,6 @@ import { fireGlobalEvent } from '../../../../client/lib/utils/fireGlobalEvent';
import { upsertMessage, RoomHistoryManager } from './RoomHistoryManager';
import { mainReady } from './mainReady';
import { menu } from './menu';
import { roomTypes } from '../../../utils';
import { callbacks } from '../../../../lib/callbacks';
import { Notifications } from '../../../notifications';
import { CachedChatRoom, ChatMessage, ChatSubscription, CachedChatSubscription, ChatRoom } from '../../../models';
@ -19,6 +18,7 @@ import { getConfig } from '../../../../client/lib/utils/getConfig';
import { ROOM_DATA_STREAM } from '../../../utils/stream/constants';
import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling';
import { RoomManager as NewRoomManager } from '../../../../client/lib/RoomManager';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
const maxRoomsOpen = parseInt(getConfig('maxRoomsOpen')) || 5;
@ -69,7 +69,7 @@ export const RoomManager = new (function () {
const room = ChatRoom.findOne(msg.rid);
if (room.name !== FlowRouter.getParam('name')) {
RoomManager.close(room.t + FlowRouter.getParam('name'));
roomTypes.openRouteLink(room.t, room, FlowRouter.current().queryParams);
roomCoordinator.openRouteLink(room.t, room, FlowRouter.current().queryParams);
}
}
});
@ -85,7 +85,6 @@ export const RoomManager = new (function () {
if (ready !== true) {
return;
}
const user = Meteor.user();
Tracker.nonreactive(() =>
Object.entries(openedRooms).forEach(([typeName, record]) => {
if (record.active !== true || record.ready === true) {
@ -95,7 +94,7 @@ export const RoomManager = new (function () {
const type = typeName.substr(0, 1);
const name = typeName.substr(1);
const room = roomTypes.findRoom(type, name, user);
const room = roomCoordinator.getRoomDirectives(type)?.findRoom(name);
if (room != null) {
record.rid = room._id;
@ -321,10 +320,8 @@ Meteor.startup(() => {
if (currentUsername === undefined && (user != null ? user.username : undefined) != null) {
currentUsername = user.username;
RoomManager.closeAllRooms();
const { roomTypes: types } = roomTypes;
// Reload only if the current route is a channel route
const roomType = Object.keys(types).find((key) => types[key].route && types[key].route.name === FlowRouter.current().route.name);
const roomType = roomCoordinator.getRouteNameIdentifier(FlowRouter.current().route.name);
if (roomType) {
FlowRouter.reload();
}

@ -2,9 +2,9 @@ import { FlowRouter } from 'meteor/kadira:flow-router';
import { Session } from 'meteor/session';
import { AccountBox } from './AccountBox';
import { roomTypes } from '../../../utils/client/lib/roomTypes';
import { Subscriptions } from '../../../models';
import { RoomManager } from '../../../../client/lib/RoomManager';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
export const SideNav = new (class {
constructor() {
@ -46,14 +46,10 @@ export const SideNav = new (class {
}
closeFlex(callback = null) {
const routesNamesForRooms = roomTypes
.getTypes()
.filter((i) => i.route)
.map((i) => i.route.name);
if (!routesNamesForRooms.includes(FlowRouter.current().route.name)) {
if (!roomCoordinator.isRouteNameKnown(FlowRouter.current().route.name)) {
const subscription = Subscriptions.findOne({ rid: RoomManager.lastRid });
if (subscription) {
roomTypes.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams);
roomCoordinator.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams);
} else {
FlowRouter.go('home');
}

@ -6,13 +6,14 @@ import { Tracker } from 'meteor/tracker';
import { Subscriptions, Rooms, Users } from '../../../models/client';
import { hasPermission } from '../../../authorization/client';
import { settings } from '../../../settings/client';
import { getUserPreference, roomTypes } from '../../../utils/client';
import { getUserPreference } from '../../../utils/client';
import { AutoTranslate } from '../../../autotranslate/client';
import { fireGlobalEvent } from '../../../../client/lib/utils/fireGlobalEvent';
import { actionLinks } from '../../../action-links/client';
import { goToRoomById } from '../../../../client/lib/utils/goToRoomById';
import { isLayoutEmbedded } from '../../../../client/lib/utils/isLayoutEmbedded';
import { handleError } from '../../../../client/lib/utils/handleError';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
const fields = {
'name': 1,
@ -75,7 +76,7 @@ export function messageContext({ rid } = Template.instance()) {
const replyBroadcast = (e) => {
const { username, mid } = e.currentTarget.dataset;
roomTypes.openRouteLink('d', { name: username }, { ...FlowRouter.current().queryParams, reply: mid });
roomCoordinator.openRouteLink('d', { name: username }, { ...FlowRouter.current().queryParams, reply: mid });
};
return {

@ -8,13 +8,13 @@ import { appLayout } from '../../../../client/lib/appLayout';
import { Messages, ChatSubscription } from '../../../models';
import { settings } from '../../../settings';
import { callbacks } from '../../../../lib/callbacks';
import { roomTypes } from '../../../utils';
import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling';
import { call } from '../../../../client/lib/utils/call';
import { RoomManager, RoomHistoryManager } from '..';
import { RoomManager as NewRoomManager } from '../../../../client/lib/RoomManager';
import { Rooms } from '../../../models/client';
import { fireGlobalEvent } from '../../../../client/lib/utils/fireGlobalEvent';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
window.currentTracker = undefined;
@ -36,7 +36,7 @@ export const openRoom = async function (type, name, render = true) {
}
try {
const room = roomTypes.findRoom(type, name, user) || (await call('getRoomByTypeAndName', type, name));
const room = roomCoordinator.getRoomDirectives(type)?.findRoom(name) || (await call('getRoomByTypeAndName', type, name));
Rooms.upsert({ _id: room._id }, _.omit(room, '_id'));
if (room._id !== name && type === 'd') {

@ -10,7 +10,7 @@ import { FlowRouter } from 'meteor/kadira:flow-router';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { t, roomTypes, getUserPreference } from '../../../../utils/client';
import { t, getUserPreference } from '../../../../utils/client';
import { WebRTC } from '../../../../webrtc/client';
import { ChatMessage, RoomRoles, Users, Subscriptions, Rooms } from '../../../../models';
import { RoomHistoryManager, RoomManager, readMessage } from '../../../../ui-utils/client';
@ -26,10 +26,11 @@ import { getCommonRoomEvents } from './lib/getCommonRoomEvents';
import { RoomManager as NewRoomManager } from '../../../../../client/lib/RoomManager';
import { isLayoutEmbedded } from '../../../../../client/lib/utils/isLayoutEmbedded';
import { handleError } from '../../../../../client/lib/utils/handleError';
import { roomCoordinator } from '../../../../../client/lib/rooms/roomCoordinator';
export const chatMessages = {};
const userCanDrop = (_id) => !roomTypes.readOnly(_id, Users.findOne({ _id: Meteor.userId() }, { fields: { username: 1 } }));
const userCanDrop = (_id) => !roomCoordinator.readOnly(_id, Users.findOne({ _id: Meteor.userId() }, { fields: { username: 1 } }));
const wipeFailedUploads = () => {
const uploads = Session.get('uploading');
@ -247,7 +248,7 @@ Template.roomOld.helpers({
},
chatNowLink() {
return roomTypes.getRouteLink('d', { name: this.username });
return roomCoordinator.getRouteLink('d', { name: this.username });
},
announcement() {
@ -658,7 +659,7 @@ Meteor.startup(() => {
return c.stop();
}
if (roomTypes.getConfig(room.t).isGroupChat(room)) {
if (roomCoordinator.getRoomDirectives(room.t)?.isGroupChat(room)) {
return;
}
const usernames = Array.from(new Set(room.usernames));
@ -940,17 +941,12 @@ Meteor.startup(() => {
if (room?.t === 'l') {
room = Tracker.nonreactive(() => Rooms.findOne({ _id: rid }));
roomTypes.getConfig(room.t).openCustomProfileTab(this, room, room.v.username);
roomCoordinator.getRoomDirectives(room.t)?.openCustomProfileTab(this, room, room.v.username);
}
});
this.autorun(() => {
if (
!Object.values(roomTypes.roomTypes)
.map(({ route }) => route && route.name)
.filter(Boolean)
.includes(FlowRouter.getRouteName())
) {
if (!roomCoordinator.isRouteNameKnown(FlowRouter.getRouteName())) {
return;
}

@ -1,6 +1,6 @@
import { Template } from 'meteor/templating';
import { roomTypes } from '../../../../utils';
import { roomCoordinator } from '../../../../../client/lib/rooms/roomCoordinator';
Template.roomSearch.helpers({
roomIcon() {
@ -8,7 +8,7 @@ Template.roomSearch.helpers({
return 'icon-at';
}
if (this.type === 'r') {
return roomTypes.getIcon(this);
return roomCoordinator.getIcon(this);
}
},
userStatus() {

@ -3,9 +3,6 @@ export { getDefaultSubscriptionPref } from '../lib/getDefaultSubscriptionPref';
export { Info } from '../rocketchat.info';
export { getUserPreference } from '../lib/getUserPreference';
export { fileUploadMediaWhiteList, fileUploadIsValidContentType } from '../lib/fileUploadRestrictions';
export { roomTypes } from './lib/roomTypes';
export { RoomTypeRouteConfig, RoomTypeConfig, RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../lib/RoomTypeConfig';
export { RoomTypesCommon } from '../lib/RoomTypesCommon';
export { getUserAvatarURL } from '../lib/getUserAvatarURL';
export { slashCommands } from '../lib/slashCommand';
export { getUserNotificationPreference } from '../lib/getUserNotificationPreference';

@ -1,213 +0,0 @@
import { FlowRouter } from 'meteor/kadira:flow-router';
import _ from 'underscore';
import { RoomTypesCommon } from '../../lib/RoomTypesCommon';
import { hasAtLeastOnePermission } from '../../../authorization';
import { ChatRoom, ChatSubscription } from '../../../models';
export const roomTypes = new (class RocketChatRoomTypes extends RoomTypesCommon {
checkCondition(roomType) {
return roomType.condition == null || roomType.condition();
}
getTypes() {
return _.sortBy(this.roomTypesOrder, 'order')
.map((type) => this.roomTypes[type.identifier])
.filter((type) => !type.condition || type.condition());
}
getIcon(roomData) {
if (!roomData || !roomData.t || !this.roomTypes[roomData.t]) {
return;
}
return (this.roomTypes[roomData.t].getIcon && this.roomTypes[roomData.t].getIcon(roomData)) || this.roomTypes[roomData.t].icon;
}
getRoomName(roomType, roomData) {
return this.roomTypes[roomType] && this.roomTypes[roomType].roomName && this.roomTypes[roomType].roomName(roomData);
}
getSecondaryRoomName(roomType, roomData) {
return (
this.roomTypes[roomType] &&
typeof this.roomTypes[roomType].secondaryRoomName === 'function' &&
this.roomTypes[roomType].secondaryRoomName(roomData)
);
}
getIdentifiers(e) {
const except = [].concat(e);
const list = _.reject(this.roomTypesOrder, (t) => except.indexOf(t.identifier) !== -1);
return _.map(list, (t) => t.identifier);
}
getUserStatus(roomType, rid) {
return (
this.roomTypes[roomType] &&
typeof this.roomTypes[roomType].getUserStatus === 'function' &&
this.roomTypes[roomType].getUserStatus(rid)
);
}
getRoomType(roomId) {
const fields = {
t: 1,
};
const room = ChatRoom.findOne(
{
_id: roomId,
},
{
fields,
},
);
return room && room.t;
}
isLivechatRoom(roomType) {
return (
this.roomTypes[roomType] && typeof this.roomTypes[roomType].isLivechatRoom === 'function' && this.roomTypes[roomType].isLivechatRoom()
);
}
showQuickActionButtons(roomType) {
return (
this.roomTypes[roomType] &&
typeof this.roomTypes[roomType].showQuickActionButtons === 'function' &&
this.roomTypes[roomType].showQuickActionButtons()
);
}
getUserStatusText(roomType, rid) {
return (
this.roomTypes[roomType] &&
typeof this.roomTypes[roomType].getUserStatusText === 'function' &&
this.roomTypes[roomType].getUserStatusText(rid)
);
}
findRoom(roomType, identifier, user) {
return this.roomTypes[roomType] && this.roomTypes[roomType].findRoom(identifier, user);
}
canSendMessage(rid) {
return ChatSubscription.find({ rid }).count() > 0;
}
readOnly(rid, user) {
const fields = {
ro: 1,
t: 1,
};
if (user) {
fields.muted = 1;
fields.unmuted = 1;
}
const room = ChatRoom.findOne(
{
_id: rid,
},
{
fields,
},
);
const roomType = room && room.t;
if (roomType && this.roomTypes[roomType] && this.roomTypes[roomType].readOnly) {
return this.roomTypes[roomType].readOnly(rid, user);
}
if (!user) {
return room && room.ro;
}
if (room) {
if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1) {
return true;
}
if (room.ro === true) {
if (Array.isArray(room.unmuted) && room.unmuted.indexOf(user.username) !== -1) {
return false;
}
if (hasAtLeastOnePermission('post-readonly', room._id)) {
return false;
}
return true;
}
}
return false;
}
archived(rid) {
const room = ChatRoom.findOne({ _id: rid }, { fields: { archived: 1 } });
return room && room.archived === true;
}
verifyCanSendMessage(rid) {
const room = ChatRoom.findOne({ _id: rid }, { fields: { t: 1 } });
if (!room || !room.t) {
return;
}
const roomType = room.t;
if (this.roomTypes[roomType] && this.roomTypes[roomType].canSendMessage) {
return this.roomTypes[roomType].canSendMessage(rid);
}
return this.canSendMessage(rid);
}
verifyShowJoinLink(rid) {
const room = ChatRoom.findOne({ _id: rid, t: { $exists: true, $ne: null } }, { fields: { t: 1 } });
if (!room || !room.t) {
return;
}
const roomType = room.t;
if (this.roomTypes[roomType] && !this.roomTypes[roomType].showJoinLink) {
return false;
}
return this.roomTypes[roomType].showJoinLink(rid);
}
getNotSubscribedTpl(rid) {
const room = ChatRoom.findOne({ _id: rid, t: { $exists: true, $ne: null } }, { fields: { t: 1 } });
if (!room || !room.t) {
return;
}
const roomType = room.t;
if (this.roomTypes[roomType] && !this.roomTypes[roomType].notSubscribedTpl) {
return false;
}
return this.roomTypes[roomType].notSubscribedTpl;
}
getReadOnlyTpl(rid) {
const room = ChatRoom.findOne({ _id: rid, t: { $exists: true, $ne: null } }, { fields: { t: 1 } });
if (!room || !room.t) {
return;
}
const roomType = room.t;
return this.roomTypes[roomType] && this.roomTypes[roomType].readOnlyTpl;
}
openRouteLink(roomType, subData, queryParams) {
if (!this.roomTypes[roomType]) {
return false;
}
let routeData = {};
if (this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link) {
routeData = this.roomTypes[roomType].route.link(subData);
} else if (subData && subData.name) {
routeData = {
name: subData.name,
};
}
return FlowRouter.go(this.roomTypes[roomType].route.name, routeData, queryParams);
}
})();

@ -1,291 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
let Users;
let settings;
if (Meteor.isServer) {
({ settings } = require('../../settings/server'));
Users = require('../../models/server/models/Users').default;
} else {
({ settings } = require('../../settings/client'));
}
export const RoomSettingsEnum = {
TYPE: 'type',
NAME: 'roomName',
TOPIC: 'roomTopic',
ANNOUNCEMENT: 'roomAnnouncement',
DESCRIPTION: 'roomDescription',
READ_ONLY: 'readOnly',
REACT_WHEN_READ_ONLY: 'reactWhenReadOnly',
ARCHIVE_OR_UNARCHIVE: 'archiveOrUnarchive',
JOIN_CODE: 'joinCode',
BROADCAST: 'broadcast',
SYSTEM_MESSAGES: 'systemMessages',
E2E: 'encrypted',
};
export const RoomMemberActions = {
ARCHIVE: 'archive',
IGNORE: 'ignore',
BLOCK: 'block',
MUTE: 'mute',
SET_AS_OWNER: 'setAsOwner',
SET_AS_LEADER: 'setAsLeader',
SET_AS_MODERATOR: 'setAsModerator',
LEAVE: 'leave',
REMOVE_USER: 'removeUser',
JOIN: 'join',
INVITE: 'invite',
};
export const UiTextContext = {
CLOSE_WARNING: 'closeWarning',
HIDE_WARNING: 'hideWarning',
LEAVE_WARNING: 'leaveWarning',
NO_ROOMS_SUBSCRIBED: 'noRoomsSubscribed',
};
export class RoomTypeRouteConfig {
constructor({ name, path }) {
if (typeof name !== 'undefined' && (typeof name !== 'string' || name.length === 0)) {
throw new Error('The name must be a string.');
}
if (typeof path !== 'undefined' && (typeof path !== 'string' || path.length === 0)) {
throw new Error('The path must be a string.');
}
this._name = name;
this._path = path;
}
get name() {
return this._name;
}
get path() {
return this._path;
}
}
export class RoomTypeConfig {
constructor({ identifier = Random.id(), order, icon, header, label, route }) {
if (typeof identifier !== 'string' || identifier.length === 0) {
throw new Error('The identifier must be a string.');
}
if (typeof order !== 'number') {
throw new Error('The order must be a number.');
}
if (typeof icon !== 'undefined' && (typeof icon !== 'string' || icon.length === 0)) {
throw new Error('The icon must be a string.');
}
if (typeof header !== 'undefined' && (typeof header !== 'string' || header.length === 0)) {
throw new Error('The header must be a string.');
}
if (typeof label !== 'undefined' && (typeof label !== 'string' || label.length === 0)) {
throw new Error('The label must be a string.');
}
if (typeof route !== 'undefined' && !(route instanceof RoomTypeRouteConfig)) {
throw new Error('Room\'s route is not a valid route configuration. Must be an instance of "RoomTypeRouteConfig".');
}
this._identifier = identifier;
this._order = order;
this._icon = icon;
this._header = header;
this._label = label;
this._route = route;
}
/**
* The room type's internal identifier.
*/
get identifier() {
return this._identifier;
}
/**
* The order of this room type for the display.
*/
get order() {
return this._order;
}
/**
* Sets the order of this room type for the display.
*
* @param {number} order the number value for the order
*/
set order(order) {
if (typeof order !== 'number') {
throw new Error('The order must be a number.');
}
this._order = order;
}
/**
* The icon class, css, to use as the visual aid.
*/
get icon() {
return this._icon;
}
/**
* The header name of this type.
*/
get header() {
return this._header;
}
/**
* The i18n label for this room type.
*/
get label() {
return this._label;
}
/**
* The route config for this room type.
*/
get route() {
return this._route;
}
allowRoomSettingChange(/* room, setting */) {
return true;
}
allowMemberAction(/* room, action */) {
return false;
}
/**
* Return a room's name
*
* @abstract
* @return {string} Room's name according to it's type
*/
roomName(/* room */) {
return '';
}
canBeCreated(hasPermission) {
if (!hasPermission && typeof hasPermission !== 'function') {
throw new Error('You MUST provide the "hasPermission" to canBeCreated function');
}
return Meteor.isServer ? hasPermission(Meteor.userId(), `create-${this._identifier}`) : hasPermission([`create-${this._identifier}`]);
}
canBeDeleted(hasPermission, room) {
if (!hasPermission && typeof hasPermission !== 'function') {
throw new Error('You MUST provide the "hasPermission" to canBeDeleted function');
}
return Meteor.isServer ? hasPermission(Meteor.userId(), `delete-${room.t}`, room._id) : hasPermission(`delete-${room.t}`, room._id);
}
supportMembersList(/* room */) {
return true;
}
isGroupChat() {
return false;
}
canAddUser(/* userId, room */) {
return false;
}
userDetailShowAll(/* room */) {
return true;
}
userDetailShowAdmin(/* room */) {
return true;
}
preventRenaming(/* room */) {
return false;
}
includeInRoomSearch() {
return false;
}
enableMembersListProfile() {
return false;
}
/**
* Returns a text which can be used in generic UIs.
* @param context The role of the text in the UI-Element
* @return {string} A text or a translation key - the consumers of this method will pass the
* returned value to an internationalization library
*/
getUiText(/* context */) {
return '';
}
/**
* Returns the full object of message sender
* @param {string} senderId Sender's _id
* @return {object} Sender's object from db
*/
getMsgSender(senderId) {
if (Meteor.isServer && Users) {
return Users.findOneById(senderId);
}
return {};
}
/**
* Returns details to use on notifications
*
* @param {object} room
* @param {object} user
* @param {string} notificationMessage
* @return {object} Notification details
*/
getNotificationDetails(room, user, notificationMessage) {
if (!Meteor.isServer) {
return {};
}
const title = `#${this.roomName(room)}`;
const text = `${settings.get('UI_Use_Real_Name') ? user.name : user.username}: ${notificationMessage}`;
return { title, text };
}
/**
* Check if there is an user with the same id and loginToken
* @param {object} allowData
* @return {object} User's object from db
*/
canAccessUploadedFile(/* accessData */) {
return false;
}
getReadReceiptsExtraData(/* message */) {
return {};
}
getAvatarPath(/* roomData */) {
return '';
}
openCustomProfileTab() {
return false;
}
getDiscussionType() {
return 'p';
}
}

@ -1,120 +0,0 @@
import { Meteor } from 'meteor/meteor';
// import { Session } from 'meteor/session';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { RoomTypeConfig } from './RoomTypeConfig';
import { roomExit } from './roomExit';
export class RoomTypesCommon {
constructor() {
this.roomTypes = {};
this.roomTypesOrder = [];
this.mainOrder = 1;
}
getTypesToShowOnDashboard() {
return Object.keys(this.roomTypes).filter((key) => this.roomTypes[key].includeInDashboard && this.roomTypes[key].includeInDashboard());
}
/**
* Adds a room type to the application.
*
* @param {RoomTypeConfig} roomConfig
* @returns {void}
*/
add(roomConfig) {
if (!(roomConfig instanceof RoomTypeConfig)) {
throw new Error('Invalid Room Configuration object, it must extend "RoomTypeConfig"');
}
if (this.roomTypes[roomConfig.identifier]) {
return false;
}
if (!roomConfig.order) {
roomConfig.order = this.mainOrder + 10;
this.mainOrder += 10;
}
this.roomTypesOrder.push({
identifier: roomConfig.identifier,
order: roomConfig.order,
});
this.roomTypes[roomConfig.identifier] = roomConfig;
if (roomConfig.route && roomConfig.route.path && roomConfig.route.name && roomConfig.route.action) {
const routeConfig = {
name: roomConfig.route.name,
action: roomConfig.route.action,
// triggersExit: [() => Session.set('openedRoom', '')],
};
if (Meteor.isClient) {
routeConfig.triggersExit = [roomExit];
}
return FlowRouter.route(roomConfig.route.path, routeConfig);
}
}
hasCustomLink(roomType) {
return this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link != null;
}
/**
* @param {string} roomType room type (e.g.: c (for channels), d (for direct channels))
* @param {object} subData the user's subscription data
*/
getRouteLink(roomType, subData) {
const routeData = this.getRouteData(roomType, subData);
if (!routeData) {
return false;
}
return FlowRouter.path(this.roomTypes[roomType].route.name, routeData);
}
/**
* @param {string} roomType room type (e.g.: c (for channels), d (for direct channels))
* @param {RoomTypeConfig} roomConfig room's type configuration
*/
getConfig(roomType) {
return this.roomTypes[roomType];
}
/**
* @param {string} roomType room type (e.g.: c (for channels), d (for direct channels))
* @param {object} subData the user's subscription data
*/
getURL(roomType, subData) {
const routeData = this.getRouteData(roomType, subData);
if (!routeData) {
return false;
}
return FlowRouter.url(this.roomTypes[roomType].route.name, routeData);
}
getRelativePath(roomType, subData) {
return this.getRouteLink(roomType, subData).replace(Meteor.absoluteUrl(), '');
}
getRouteData(roomType, subData) {
if (!this.roomTypes[roomType]) {
return false;
}
let routeData = {};
if (this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link) {
routeData = this.roomTypes[roomType].route.link(subData);
} else if (subData && subData.name) {
routeData = {
rid: subData.rid || subData._id,
name: subData.name,
};
}
return routeData;
}
}

@ -1,6 +1,6 @@
import { getURL } from './getURL';
export const getAvatarURL = ({ username, roomId, cache }) => {
export const getAvatarURL = ({ username, roomId, cache }: { username?: string; roomId?: string; cache?: string }): string | undefined => {
if (username) {
return getURL(`/avatar/${encodeURIComponent(username)}${cache ? `?etag=${cache}` : ''}`);
}

@ -1,40 +0,0 @@
import { Blaze } from 'meteor/blaze';
// import { Session } from 'meteor/session';
import { Tracker } from 'meteor/tracker';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { callbacks } from '../../../lib/callbacks';
const testIfPathAreEquals = (oldPath = '', newPath = '') => oldPath.replace(/"/g, '') === newPath;
export const roomExit = function () {
const oldRoute = FlowRouter.current();
Tracker.afterFlush(() => {
const context = FlowRouter.current();
if (
oldRoute &&
testIfPathAreEquals(
oldRoute.params.name || oldRoute.params.rid || oldRoute.params.id,
context.params.name || context.params.rid || context.params.id,
)
) {
return;
}
// 7370 - Close flex-tab when opening a room on mobile UI
if (window.matchMedia('(max-width: 500px)').matches) {
const flex = document.querySelector('.flex-tab');
if (flex) {
const templateData = Blaze.getData(flex);
templateData && templateData.tabBar && templateData.tabBar.close();
}
}
callbacks.run('roomExit');
// Session.set('lastOpenedRoom', Session.get('openedRoom'));
// Session.set('openedRoom', null);
// RoomManager.openedRoom = null;
});
if (typeof window.currentTracker !== 'undefined') {
window.currentTracker.stop();
}
};

@ -3,9 +3,6 @@ export { getDefaultSubscriptionPref } from '../lib/getDefaultSubscriptionPref';
export { Info } from '../rocketchat.info';
export { getUserPreference } from '../lib/getUserPreference';
export { fileUploadMediaWhiteList, fileUploadIsValidContentType } from '../lib/fileUploadRestrictions';
export { roomTypes, searchableRoomTypes } from './lib/roomTypes';
export { RoomTypeRouteConfig, RoomTypeConfig, RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../lib/RoomTypeConfig';
export { RoomTypesCommon } from '../lib/RoomTypesCommon';
export { isDocker } from './functions/isDocker';
export { getMongoInfo, getOplogInfo } from './functions/getMongoInfo';
export { getUserAvatarURL } from '../lib/getUserAvatarURL';

@ -1,55 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { RoomTypesCommon } from '../../lib/RoomTypesCommon';
export const roomTypes = new (class roomTypesServer extends RoomTypesCommon {
/**
* Add a publish for a room type
*
* @param {string} roomType room type (e.g.: c (for channels), d (for direct channels))
* @param {function} callback function that will return the publish's data
*/
setPublish(roomType, callback) {
if (this.roomTypes[roomType] && this.roomTypes[roomType].publish != null) {
throw new Meteor.Error('route-publish-exists', 'Publish for the given type already exists');
}
if (this.roomTypes[roomType] == null) {
this.roomTypes[roomType] = {};
}
this.roomTypes[roomType].publish = callback;
}
setRoomFind(roomType, callback) {
if (this.roomTypes[roomType] && this.roomTypes[roomType].roomFind != null) {
throw new Meteor.Error('room-find-exists', 'Room find for the given type already exists');
}
if (this.roomTypes[roomType] == null) {
this.roomTypes[roomType] = {};
}
this.roomTypes[roomType].roomFind = callback;
}
getRoomFind(roomType) {
return this.roomTypes[roomType] && this.roomTypes[roomType].roomFind;
}
getRoomName(roomType, roomData) {
return this.roomTypes[roomType] && this.roomTypes[roomType].roomName && this.roomTypes[roomType].roomName(roomData);
}
/**
* Run the publish for a room type
*
* @param scope Meteor publish scope
* @param {string} roomType room type (e.g.: c (for channels), d (for direct channels))
* @param identifier identifier of the room
*/
runPublish(scope, roomType, identifier) {
return this.roomTypes[roomType] && this.roomTypes[roomType].publish && this.roomTypes[roomType].publish.call(scope, identifier);
}
})();
export const searchableRoomTypes = () =>
Object.entries(roomTypes.roomTypes)
.filter((roomType) => roomType[1].includeInRoomSearch())
.map((roomType) => roomType[0]);

@ -1,10 +1,10 @@
import { Skeleton, TextInput, Callout } from '@rocket.chat/fuselage';
import React, { useMemo, ReactElement } from 'react';
import { roomTypes } from '../../../app/utils/client/lib/roomTypes';
import { useTranslation } from '../../contexts/TranslationContext';
import { AsyncStatePhase } from '../../hooks/useAsyncState';
import { useEndpointData } from '../../hooks/useEndpointData';
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
const DefaultParentRoomField = ({ defaultParentRoom }: { defaultParentRoom: string }): ReactElement => {
const t = useTranslation();
@ -26,7 +26,17 @@ const DefaultParentRoomField = ({ defaultParentRoom }: { defaultParentRoom: stri
return <Callout type={'danger'}>{t('Error')}</Callout>;
}
return <TextInput value={roomTypes.getRoomName(value.room.t, value.room)} disabled onChange={(): string => ''} />;
return (
<TextInput
defaultValue={roomCoordinator.getRoomName(value.room.t, {
_id: value.room._id,
fname: value.room.fname,
name: value.room.name,
prid: value.room.prid,
})}
disabled
/>
);
};
export default DefaultParentRoomField;

@ -0,0 +1,194 @@
import { FlowRouter } from 'meteor/kadira:flow-router';
import type { RouteOptions } from 'meteor/kadira:flow-router';
import _ from 'underscore';
import { hasPermission } from '../../../app/authorization/client';
import { ChatRoom, ChatSubscription } from '../../../app/models/client';
import { openRoom } from '../../../app/ui-utils/client/lib/openRoom';
import type { IRoom, RoomType } from '../../../definition/IRoom';
import type { IRoomTypeConfig, IRoomTypeClientDirectives, RoomIdentification } from '../../../definition/IRoomTypeConfig';
import { RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../../../definition/IRoomTypeConfig';
import type { IUser } from '../../../definition/IUser';
import type { AtLeast, ValueOf } from '../../../definition/utils';
import { RoomCoordinator } from '../../../lib/rooms/coordinator';
import { roomExit } from './roomExit';
class RoomCoordinatorClient extends RoomCoordinator {
add(roomConfig: IRoomTypeConfig, 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>): boolean {
return false;
},
roomName(_room: AtLeast<IRoom, '_id' | 'name' | 'fname' | 'prid'>): string {
return '';
},
isGroupChat(_room: Partial<IRoom>): boolean {
return false;
},
openCustomProfileTab(_instance: any, _room: IRoom, _username: string): boolean {
return false;
},
getUiText(_context: ValueOf<typeof UiTextContext>): string {
return '';
},
condition(): boolean {
return true;
},
getAvatarPath(_room): string {
return '';
},
getIcon(_room: Partial<IRoom>): string | undefined {
return this.config.icon;
},
getUserStatus(_roomId: string): string | undefined {
return undefined;
},
findRoom(_identifier: string): IRoom | undefined {
return undefined;
},
showJoinLink(_roomId: string): boolean {
return false;
},
isLivechatRoom(): boolean {
return false;
},
canSendMessage(rid: string): boolean {
return ChatSubscription.find({ rid }).count() > 0;
},
...directives,
config: roomConfig,
});
}
protected addRoute(path: string, routeConfig: RouteOptions): void {
super.addRoute(path, { ...routeConfig, triggersExit: [roomExit] });
}
getRoomDirectives(roomType: string): IRoomTypeClientDirectives | undefined {
return this.roomTypes[roomType]?.directives as IRoomTypeClientDirectives;
}
getRoomTypeById(rid: string): RoomType | undefined {
const room = ChatRoom.findOne({ _id: rid, t: { $exists: true, $ne: null } }, { fields: { t: 1 } });
return room?.t;
}
getRoomDirectivesById(rid: string): IRoomTypeClientDirectives | undefined {
const roomType = this.getRoomTypeById(rid);
if (roomType) {
return this.getRoomDirectives(roomType);
}
}
getRoomTypeConfigById(rid: string): IRoomTypeConfig | undefined {
const roomType = this.getRoomTypeById(rid);
if (roomType) {
return this.getRoomTypeConfig(roomType);
}
}
openRoom(type: string, name: string, render = true): void {
openRoom(type, name, render);
}
getIcon(room: Partial<IRoom>): string | undefined {
return room?.t && this.getRoomDirectives(room.t)?.getIcon(room);
}
openRouteLink(roomType: RoomType, subData: RoomIdentification, queryParams?: 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;
}
FlowRouter.go(config.route.name, routeData, queryParams);
}
isLivechatRoom(roomType: string): boolean {
return Boolean(this.getRoomDirectives(roomType)?.isLivechatRoom());
}
getRoomName(roomType: string, roomData: AtLeast<IRoom, '_id' | 'name' | 'fname' | 'prid'>): string {
return this.getRoomDirectives(roomType)?.roomName(roomData) ?? '';
}
readOnly(rid: string, user: AtLeast<IUser, 'username'>): boolean {
const fields = {
ro: 1,
t: 1,
...(user && { muted: 1, unmuted: 1 }),
};
const room = ChatRoom.findOne({ _id: rid }, { fields });
if (!room) {
return false;
}
const directives = this.getRoomDirectives(room.t);
if (directives?.readOnly) {
return directives.readOnly(rid, 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;
}
// #ToDo: Move this out of the RoomCoordinator
archived(rid: string): boolean {
const room = ChatRoom.findOne({ _id: rid }, { fields: { archived: 1 } });
return Boolean(room?.archived);
}
verifyCanSendMessage(rid: string): boolean {
const room = ChatRoom.findOne({ _id: rid }, { fields: { t: 1 } });
if (!room?.t) {
return false;
}
return Boolean(this.getRoomDirectives(room.t)?.canSendMessage(rid));
}
getSortedTypes(): Array<{ config: IRoomTypeConfig; directives: IRoomTypeClientDirectives }> {
return _.sortBy(this.roomTypesOrder, 'order')
.map((type) => this.roomTypes[type.identifier] as { config: IRoomTypeConfig; directives: IRoomTypeClientDirectives })
.filter((type) => type.directives.condition());
}
}
export const roomCoordinator = new RoomCoordinatorClient();

@ -0,0 +1,33 @@
import { Blaze } from 'meteor/blaze';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Tracker } from 'meteor/tracker';
const testIfPathAreEquals = (oldPath = '', newPath = ''): boolean => oldPath.replace(/"/g, '') === newPath;
export const roomExit = function (_context: { params: Record<string, string>; queryParams: Record<string, string> }): void {
const oldRoute = FlowRouter.current();
Tracker.afterFlush(() => {
const context = FlowRouter.current();
if (
oldRoute &&
testIfPathAreEquals(
oldRoute.params.name || oldRoute.params.rid || oldRoute.params.id,
context.params.name || context.params.rid || context.params.id,
)
) {
return;
}
// 7370 - Close flex-tab when opening a room on mobile UI
if (window.matchMedia('(max-width: 500px)').matches) {
const flex = document.querySelector<HTMLElement>('.flex-tab');
if (flex) {
const templateData = Blaze.getData(flex) as any;
templateData?.tabBar?.close();
}
}
});
if (typeof (window as any).currentTracker !== 'undefined') {
(window as any).currentTracker.stop();
}
};

@ -0,0 +1,14 @@
import { Meteor } from 'meteor/meteor';
import { getUserPreference } from '../../../../app/utils/client';
import { getConversationRoomType } from '../../../../lib/rooms/roomTypes/conversation';
import { roomCoordinator } from '../roomCoordinator';
export const ConversationRoomType = getConversationRoomType(roomCoordinator);
roomCoordinator.add(ConversationRoomType, {
condition(): boolean {
// returns true only if sidebarGroupByType is not set
return !getUserPreference(Meteor.userId(), 'sidebarGroupByType');
},
});

@ -0,0 +1,152 @@
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { hasAtLeastOnePermission, hasPermission } from '../../../../app/authorization/client';
import { Subscriptions, Users, ChatRoom } from '../../../../app/models/client';
import { settings } from '../../../../app/settings/client';
import { getUserPreference } from '../../../../app/utils/client';
import { getAvatarURL } from '../../../../app/utils/lib/getAvatarURL';
import { getUserAvatarURL } from '../../../../app/utils/lib/getUserAvatarURL';
import type { IRoom } from '../../../../definition/IRoom';
import type { IRoomTypeClientDirectives } from '../../../../definition/IRoomTypeConfig';
import { RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../../../../definition/IRoomTypeConfig';
import type { AtLeast, ValueOf } from '../../../../definition/utils';
import { getDirectMessageRoomType } from '../../../../lib/rooms/roomTypes/direct';
import { roomCoordinator } from '../roomCoordinator';
export const DirectMessageRoomType = getDirectMessageRoomType(roomCoordinator);
roomCoordinator.add(DirectMessageRoomType, {
allowRoomSettingChange(_room: Partial<IRoom>, setting: ValueOf<typeof RoomSettingsEnum>): boolean {
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.get('E2E_Enable') === true;
default:
return true;
}
},
allowMemberAction(room: Partial<IRoom>, action: ValueOf<typeof RoomMemberActions>): boolean {
switch (action) {
case RoomMemberActions.BLOCK:
return !this.isGroupChat(room);
default:
return false;
}
},
roomName(roomData): string | undefined {
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.findOne({ rid: roomData._id });
})();
if (!subscription) {
return;
}
if (settings.get('UI_Use_Real_Name') && subscription.fname) {
return subscription.fname;
}
return subscription.name;
},
isGroupChat(room: Partial<IRoom>): boolean {
return (room?.uids?.length || 0) > 2;
},
getUiText(context: ValueOf<typeof UiTextContext>): string {
switch (context) {
case UiTextContext.HIDE_WARNING:
return 'Hide_Private_Warning';
case UiTextContext.LEAVE_WARNING:
return 'Leave_Private_Warning';
default:
return '';
}
},
condition(): boolean {
const groupByType = getUserPreference(Meteor.userId(), 'sidebarGroupByType');
return groupByType && hasAtLeastOnePermission(['view-d-room', 'view-joined-room']);
},
getAvatarPath(room): string {
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 sub = Subscriptions.findOne({ rid: room._id }, { fields: { name: 1 } });
if (sub?.name) {
const user = Users.findOne({ username: sub.name }, { fields: { username: 1, avatarETag: 1 } });
return getUserAvatarURL(user?.username || sub.name, user?.avatarETag);
}
return getUserAvatarURL(room.name || this.roomName(room));
},
getIcon(room: Partial<IRoom>): string | undefined {
if (this.isGroupChat(room)) {
return 'balloon';
}
return DirectMessageRoomType.icon;
},
getUserStatus(roomId: string): string | undefined {
const subscription = Subscriptions.findOne({ rid: roomId });
if (!subscription) {
return;
}
return Session.get(`user_${subscription.name}_status`);
},
findRoom(identifier: string): IRoom | undefined {
if (!hasPermission('view-d-room')) {
return;
}
const query = {
t: 'd',
$or: [{ name: identifier }, { rid: identifier }],
};
const subscription = Subscriptions.findOne(query);
if (subscription?.rid) {
return ChatRoom.findOne(subscription.rid);
}
},
} as AtLeast<IRoomTypeClientDirectives, 'isGroupChat' | 'roomName'>);

@ -0,0 +1,14 @@
import { Meteor } from 'meteor/meteor';
import { settings } from '../../../../app/settings/client';
import { getUserPreference } from '../../../../app/utils/client';
import { getFavoriteRoomType } from '../../../../lib/rooms/roomTypes/favorite';
import { roomCoordinator } from '../roomCoordinator';
export const FavoriteRoomType = getFavoriteRoomType(roomCoordinator);
roomCoordinator.add(FavoriteRoomType, {
condition(): boolean {
return settings.get('Favorite_Rooms') && getUserPreference(Meteor.userId(), 'sidebarShowFavorites');
},
});

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

@ -0,0 +1,94 @@
import { Session } from 'meteor/session';
import { hasPermission } from '../../../../app/authorization/client';
import { LivechatInquiry } from '../../../../app/livechat/client/collections/LivechatInquiry';
import { ChatRoom, ChatSubscription } from '../../../../app/models/client';
import { settings } from '../../../../app/settings/client';
import { getAvatarURL } from '../../../../app/utils/lib/getAvatarURL';
import type { IRoom, IOmnichannelRoom } from '../../../../definition/IRoom';
import type { IRoomTypeClientDirectives } from '../../../../definition/IRoomTypeConfig';
import { RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../../../../definition/IRoomTypeConfig';
import type { AtLeast, ValueOf } from '../../../../definition/utils';
import { getLivechatRoomType } from '../../../../lib/rooms/roomTypes/livechat';
import { roomCoordinator } from '../roomCoordinator';
export const LivechatRoomType = getLivechatRoomType(roomCoordinator);
roomCoordinator.add(LivechatRoomType, {
allowRoomSettingChange(_room: Partial<IRoom>, setting: ValueOf<typeof RoomSettingsEnum>): boolean {
switch (setting) {
case RoomSettingsEnum.JOIN_CODE:
return false;
default:
return true;
}
},
allowMemberAction(_room: Partial<IRoom>, action: ValueOf<typeof RoomMemberActions>): boolean {
return ([RoomMemberActions.INVITE, RoomMemberActions.JOIN] as Array<ValueOf<typeof RoomMemberActions>>).includes(action);
},
roomName(room: any): string | undefined {
return room.name || room.fname || room.label;
},
openCustomProfileTab(instance: any, room: IOmnichannelRoom, username: string): boolean {
if (!room?.v || (room.v as any).username !== username) {
return false;
}
instance.tabBar.openUserInfo();
return true;
},
getUiText(context: ValueOf<typeof UiTextContext>): string {
switch (context) {
case UiTextContext.HIDE_WARNING:
return 'Hide_Livechat_Warning';
case UiTextContext.LEAVE_WARNING:
return 'Hide_Livechat_Warning';
default:
return '';
}
},
condition(): boolean {
return settings.get('Livechat_enabled') && hasPermission('view-l-room');
},
getAvatarPath(room): string {
return getAvatarURL({ username: `@${this.roomName(room)}` }) || '';
},
getUserStatus(rid: string): string | undefined {
const room = Session.get(`roomData${rid}`);
if (room) {
return room.v && room.v.status;
}
const inquiry = LivechatInquiry.findOne({ rid });
return inquiry?.v?.status;
},
findRoom(identifier: string): IRoom | undefined {
return ChatRoom.findOne({ _id: identifier });
},
isLivechatRoom(): boolean {
return true;
},
canSendMessage(rid: string): boolean {
const room = ChatRoom.findOne({ _id: rid }, { fields: { open: 1 } });
return Boolean(room?.open);
},
readOnly(rid: string, _user): boolean {
const room = ChatRoom.findOne({ _id: rid }, { fields: { open: 1, servedBy: 1 } });
if (!room || !room.open) {
return true;
}
const subscription = ChatSubscription.findOne({ rid });
return !subscription;
},
} as AtLeast<IRoomTypeClientDirectives, 'roomName'>);

@ -0,0 +1,99 @@
import { Meteor } from 'meteor/meteor';
import { hasPermission } from '../../../../app/authorization/client';
import { ChatRoom } from '../../../../app/models/client';
import { settings } from '../../../../app/settings/client';
import { getUserPreference } from '../../../../app/utils/client';
import { getAvatarURL } from '../../../../app/utils/lib/getAvatarURL';
import type { IRoom } from '../../../../definition/IRoom';
import type { IRoomTypeClientDirectives } from '../../../../definition/IRoomTypeConfig';
import { RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../../../../definition/IRoomTypeConfig';
import type { AtLeast, ValueOf } from '../../../../definition/utils';
import { getPrivateRoomType } from '../../../../lib/rooms/roomTypes/private';
import { roomCoordinator } from '../roomCoordinator';
export const PrivateRoomType = getPrivateRoomType(roomCoordinator);
roomCoordinator.add(PrivateRoomType, {
allowRoomSettingChange(room: Partial<IRoom>, setting: ValueOf<typeof RoomSettingsEnum>): boolean {
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.get('E2E_Enable') === true;
case RoomSettingsEnum.SYSTEM_MESSAGES:
default:
return true;
}
},
allowMemberAction(_room: Partial<IRoom>, action: ValueOf<typeof RoomMemberActions>): boolean {
switch (action) {
case RoomMemberActions.BLOCK:
return false;
default:
return true;
}
},
roomName(roomData: AtLeast<IRoom, '_id' | 'name' | 'fname' | 'prid'>): string | undefined {
if (roomData.prid) {
return roomData.fname;
}
if (settings.get('UI_Allow_room_names_with_special_chars')) {
return roomData.fname || roomData.name;
}
return roomData.name;
},
isGroupChat(_room: Partial<IRoom>): boolean {
return true;
},
getUiText(context: ValueOf<typeof UiTextContext>): string {
switch (context) {
case UiTextContext.HIDE_WARNING:
return 'Hide_Group_Warning';
case UiTextContext.LEAVE_WARNING:
return 'Leave_Group_Warning';
default:
return '';
}
},
condition(): boolean {
const groupByType = getUserPreference(Meteor.userId(), 'sidebarGroupByType');
return groupByType && hasPermission('view-p-room');
},
getAvatarPath(room): string {
return getAvatarURL({ roomId: room._id, cache: room.avatarETag }) as string;
},
getIcon(room: Partial<IRoom>): string | undefined {
if (room.prid) {
return 'discussion';
}
if (room.teamMain) {
return 'team-lock';
}
return PrivateRoomType.icon;
},
findRoom(identifier: string): IRoom | undefined {
const query = {
t: 'p',
name: identifier,
};
return ChatRoom.findOne(query);
},
} as AtLeast<IRoomTypeClientDirectives, 'isGroupChat' | 'roomName'>);

@ -0,0 +1,102 @@
import { Meteor } from 'meteor/meteor';
import { hasAtLeastOnePermission } from '../../../../app/authorization/client';
import { ChatRoom } from '../../../../app/models/client';
import { settings } from '../../../../app/settings/client';
import { getUserPreference } from '../../../../app/utils/client';
import { getAvatarURL } from '../../../../app/utils/lib/getAvatarURL';
import type { IRoom } from '../../../../definition/IRoom';
import type { IRoomTypeClientDirectives } from '../../../../definition/IRoomTypeConfig';
import { RoomSettingsEnum, RoomMemberActions, UiTextContext } from '../../../../definition/IRoomTypeConfig';
import type { AtLeast, ValueOf } from '../../../../definition/utils';
import { getPublicRoomType } from '../../../../lib/rooms/roomTypes/public';
import { roomCoordinator } from '../roomCoordinator';
export const PublicRoomType = getPublicRoomType(roomCoordinator);
roomCoordinator.add(PublicRoomType, {
allowRoomSettingChange(room: Partial<IRoom>, setting: ValueOf<typeof RoomSettingsEnum>): boolean {
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: Partial<IRoom>, action: ValueOf<typeof RoomMemberActions>): boolean {
switch (action) {
case RoomMemberActions.BLOCK:
return false;
default:
return true;
}
},
roomName(roomData: AtLeast<IRoom, '_id' | 'name' | 'fname' | 'prid'>): string | undefined {
if (roomData.prid) {
return roomData.fname;
}
if (settings.get('UI_Allow_room_names_with_special_chars')) {
return roomData.fname || roomData.name;
}
return roomData.name;
},
isGroupChat(_room: Partial<IRoom>): boolean {
return true;
},
getUiText(context: ValueOf<typeof UiTextContext>): string {
switch (context) {
case UiTextContext.HIDE_WARNING:
return 'Hide_Room_Warning';
case UiTextContext.LEAVE_WARNING:
return 'Leave_Room_Warning';
default:
return '';
}
},
condition(): boolean {
const groupByType = getUserPreference(Meteor.userId(), 'sidebarGroupByType');
return (
groupByType && (hasAtLeastOnePermission(['view-c-room', 'view-joined-room']) || settings.get('Accounts_AllowAnonymousRead') === true)
);
},
getAvatarPath(room): string {
return getAvatarURL({ roomId: room._id, cache: room.avatarETag }) as string;
},
getIcon(room: Partial<IRoom>): string | undefined {
if (room.prid) {
return 'discussion';
}
if (room.teamMain) {
return 'team';
}
return PublicRoomType.icon;
},
findRoom(identifier: string): IRoom | undefined {
const query = {
t: 'c',
name: identifier,
};
return ChatRoom.findOne(query);
},
showJoinLink(roomId: string): boolean {
return !!ChatRoom.findOne({ _id: roomId, t: 'c' });
},
} as AtLeast<IRoomTypeClientDirectives, 'isGroupChat' | 'roomName'>);

@ -0,0 +1,13 @@
import { Meteor } from 'meteor/meteor';
import { getUserPreference } from '../../../../app/utils/client';
import { getUnreadRoomType } from '../../../../lib/rooms/roomTypes/unread';
import { roomCoordinator } from '../roomCoordinator';
export const UnreadRoomType = getUnreadRoomType(roomCoordinator);
roomCoordinator.add(UnreadRoomType, {
condition(): boolean {
return getUserPreference(Meteor.userId(), 'sidebarShowUnread');
},
});

@ -2,8 +2,9 @@ import { memoize } from '@rocket.chat/memo';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { ChatSubscription } from '../../../app/models/client';
import { roomTypes } from '../../../app/utils/client';
import { IRoom } from '../../../definition/IRoom';
import { ISubscription } from '../../../definition/ISubscription';
import { roomCoordinator } from '../rooms/roomCoordinator';
import { callWithErrorHandling } from './callWithErrorHandling';
const getRoomById = memoize((rid: IRoom['_id']) => callWithErrorHandling('getRoomById', rid));
@ -13,13 +14,13 @@ export const goToRoomById = async (rid: IRoom['_id']): Promise<void> => {
return;
}
const subscription = ChatSubscription.findOne({ rid });
const subscription: ISubscription | undefined = ChatSubscription.findOne({ rid });
if (subscription) {
roomTypes.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams);
roomCoordinator.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams);
return;
}
const room = await getRoomById(rid);
roomTypes.openRouteLink(room.t, { rid: room._id, ...room }, FlowRouter.current().queryParams);
roomCoordinator.openRouteLink(room.t, { rid: room._id, ...room }, FlowRouter.current().queryParams);
};

@ -1,9 +1,9 @@
import React, { useMemo, FC } from 'react';
import { roomTypes } from '../../app/utils/client';
import { getURL } from '../../app/utils/lib/getURL';
import { AvatarUrlContext } from '../contexts/AvatarUrlContext';
import { useSetting } from '../contexts/SettingsContext';
import { roomCoordinator } from '../lib/rooms/roomCoordinator';
const AvatarUrlProvider: FC = ({ children }) => {
const cdnAvatarUrl = String(useSetting('CDN_PREFIX') || '');
@ -20,7 +20,7 @@ const AvatarUrlProvider: FC = ({ children }) => {
return (uid: string, etag?: string): string => getURL(`/avatar/${uid}${etag ? `?etag=${etag}` : ''}`);
})(),
getRoomPathAvatar: ({ type, ...room }: any): string =>
roomTypes.getConfig(type || room.t).getAvatarPath({ username: room._id, ...room }),
roomCoordinator.getRoomDirectives(type || room.t)?.getAvatarPath({ username: room._id, ...room }) || '',
}),
[externalProviderUrl, cdnAvatarUrl],
);

@ -1,9 +1,9 @@
import { Badge, Sidebar } from '@rocket.chat/fuselage';
import React, { memo } from 'react';
import { roomTypes } from '../../../app/utils/client';
import { RoomIcon } from '../../components/RoomIcon';
import { useLayout } from '../../contexts/LayoutContext';
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
import RoomMenu from '../RoomMenu';
import { normalizeSidebarMessage } from './normalizeSidebarMessage';
@ -37,8 +37,8 @@ function SideBarItemTemplateWithData({
}) {
const { sidebar } = useLayout();
const href = roomTypes.getRouteLink(room.t, room);
const title = roomTypes.getRoomName(room.t, room);
const href = roomCoordinator.getRouteLink(room.t, room);
const title = roomCoordinator.getRoomName(room.t, room);
const {
lastMessage,

@ -3,7 +3,7 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import React, { memo, useMemo } from 'react';
import { RoomManager } from '../../app/ui-utils/client/lib/RoomManager';
import { roomTypes, UiTextContext } from '../../app/utils/client';
import { UiTextContext } from '../../definition/IRoomTypeConfig';
import { GenericModalDoNotAskAgain } from '../components/GenericModal';
import { usePermission } from '../contexts/AuthorizationContext';
import { useSetModal } from '../contexts/ModalContext';
@ -14,6 +14,7 @@ import { useToastMessageDispatch } from '../contexts/ToastMessagesContext';
import { useTranslation } from '../contexts/TranslationContext';
import { useUserSubscription } from '../contexts/UserContext';
import { useDontAskAgain } from '../hooks/useDontAskAgain';
import { roomCoordinator } from '../lib/rooms/roomCoordinator';
import WarningModal from '../views/admin/apps/WarningModal';
const fields = {
@ -72,7 +73,7 @@ const RoomMenu = ({ rid, unread, threadUnread, alert, roomOpen, type, cl, name =
closeModal();
};
const warnText = roomTypes.getConfig(type).getUiText(UiTextContext.LEAVE_WARNING);
const warnText = roomCoordinator.getRoomDirectives(type)?.getUiText(UiTextContext.LEAVE_WARNING);
setModal(
<WarningModal
@ -96,7 +97,7 @@ const RoomMenu = ({ rid, unread, threadUnread, alert, roomOpen, type, cl, name =
closeModal();
};
const warnText = roomTypes.getConfig(type).getUiText(UiTextContext.HIDE_WARNING);
const warnText = roomCoordinator.getRoomDirectives(type)?.getUiText(UiTextContext.HIDE_WARNING);
if (dontAskHideRoom) {
return hide();

@ -1,8 +1,8 @@
import { Sidebar } from '@rocket.chat/fuselage';
import React, { memo } from 'react';
import { roomTypes } from '../../../app/utils/client';
import { ReactiveUserStatus } from '../../components/UserStatus';
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
const UserItem = ({ item, id, style, t, SideBarItemTemplate, AvatarTemplate, useRealName }) => {
const title = useRealName ? item.fname || item.name : item.name || item.fname;
@ -11,7 +11,7 @@ const UserItem = ({ item, id, style, t, SideBarItemTemplate, AvatarTemplate, use
<ReactiveUserStatus uid={item._id} />
</Sidebar.Item.Icon>
);
const href = roomTypes.getRouteLink(item.t, item);
const href = roomCoordinator.getRouteLink(item.t, item);
return (
<SideBarItemTemplate

@ -31,3 +31,4 @@ import './userRoles';
import './userSetUtcOffset';
import './usersObserve';
import './userStatusManuallySet';
import '../lib/rooms/roomTypes';

@ -2,7 +2,7 @@ import { Box, Button, ButtonGroup, TextInput, Field, ToggleSwitch, Icon, Callout
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import React, { useState, useMemo } from 'react';
import { roomTypes, RoomSettingsEnum } from '../../../../app/utils/client';
import { RoomSettingsEnum } from '../../../../definition/IRoomTypeConfig';
import GenericModal from '../../../components/GenericModal';
import VerticalBar from '../../../components/VerticalBar';
import RoomAvatarEditor from '../../../components/avatar/RoomAvatarEditor';
@ -12,9 +12,10 @@ import { useMethod } from '../../../contexts/ServerContext';
import { useTranslation } from '../../../contexts/TranslationContext';
import { useEndpointActionExperimental } from '../../../hooks/useEndpointActionExperimental';
import { useForm } from '../../../hooks/useForm';
import { roomCoordinator } from '../../../lib/rooms/roomCoordinator';
const getInitialValues = (room) => ({
roomName: room.t === 'd' ? room.usernames.join(' x ') : roomTypes.getRoomName(room.t, { type: room.t, ...room }),
roomName: room.t === 'd' ? room.usernames.join(' x ') : roomCoordinator.getRoomName(room.t, { type: room.t, ...room }),
roomType: room.t,
readOnly: !!room.ro,
archived: !!room.archived,
@ -38,7 +39,7 @@ function EditRoom({ room, onChange }) {
const [canViewName, canViewTopic, canViewAnnouncement, canViewArchived, canViewDescription, canViewType, canViewReadOnly] =
useMemo(() => {
const isAllowed = roomTypes.getConfig(room.t).allowRoomSettingChange;
const isAllowed = roomCoordinator.getRoomDirectives(room.t)?.allowRoomSettingChange;
return [
isAllowed(room, RoomSettingsEnum.NAME),
isAllowed(room, RoomSettingsEnum.TOPIC),

@ -2,13 +2,13 @@ import { Box, Table, Icon } from '@rocket.chat/fuselage';
import { useMediaQuery, useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import React, { useMemo, useCallback, useState } from 'react';
import { roomTypes } from '../../../../app/utils/client';
import GenericTable from '../../../components/GenericTable';
import RoomAvatar from '../../../components/avatar/RoomAvatar';
import { useRoute } from '../../../contexts/RouterContext';
import { useTranslation } from '../../../contexts/TranslationContext';
import { useEndpointData } from '../../../hooks/useEndpointData';
import { AsyncStatePhase } from '../../../lib/asyncState';
import { roomCoordinator } from '../../../lib/rooms/roomCoordinator';
import FilterByTypeAndText from './FilterByTypeAndText';
const style = { whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' };
@ -42,7 +42,7 @@ const useQuery = ({ text, types, itemsPerPage, current }, [column, direction]) =
[text, types, itemsPerPage, current, column, direction],
);
const getRoomDisplayName = (room) => (room.t === 'd' ? room.usernames.join(' x ') : roomTypes.getRoomName(room.t, room));
const getRoomDisplayName = (room) => (room.t === 'd' ? room.usernames.join(' x ') : roomCoordinator.getRoomName(room.t, room));
const useDisplayData = (asyncState, sort) =>
useMemo(() => {
@ -178,7 +178,7 @@ function RoomsTable() {
const renderRow = useCallback(
(room) => {
const { _id, name, t: type, usersCount, msgs, default: isDefault, featured, usernames, ...args } = room;
const icon = roomTypes.getIcon(room);
const icon = roomCoordinator.getIcon(room);
const roomName = getRoomDisplayName(room);
return (

@ -2,7 +2,6 @@ import { Box, Table, Avatar, Icon } from '@rocket.chat/fuselage';
import { useMediaQuery, useAutoFocus } from '@rocket.chat/fuselage-hooks';
import React, { useMemo, useState, useCallback } from 'react';
import { roomTypes } from '../../../app/utils/client';
import FilterByText from '../../components/FilterByText';
import GenericTable from '../../components/GenericTable';
import MarkdownText from '../../components/MarkdownText';
@ -10,6 +9,7 @@ import { useRoute } from '../../contexts/RouterContext';
import { useTranslation } from '../../contexts/TranslationContext';
import { useEndpointData } from '../../hooks/useEndpointData';
import { useFormatDate } from '../../hooks/useFormatDate';
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
import RoomTags from './RoomTags';
import { useQuery } from './hooks';
@ -109,7 +109,7 @@ function ChannelsTable() {
const renderRow = useCallback(
(room) => {
const { _id, ts, t, name, fname, usersCount, lastMessage, topic, belongsTo } = room;
const avatarUrl = roomTypes.getConfig(t).getAvatarPath(room);
const avatarUrl = roomCoordinator.getRoomDirectives(t)?.getAvatarPath(room);
return (
<Table.Row key={_id} onKeyDown={onClick(name, t)} onClick={onClick(name, t)} tabIndex={0} role='link' action>
@ -120,7 +120,7 @@ function ChannelsTable() {
</Box>
<Box grow={1} mi='x8' style={style}>
<Box display='flex' alignItems='center'>
<Icon name={roomTypes.getIcon(room)} color='hint' />{' '}
<Icon name={roomCoordinator.getIcon(room)} color='hint' />{' '}
<Box fontScale='p2m' mi='x4'>
{fname || name}
</Box>

@ -2,7 +2,6 @@ import { Box, Table, Avatar, Icon } from '@rocket.chat/fuselage';
import { useAutoFocus, useMediaQuery } from '@rocket.chat/fuselage-hooks';
import React, { useMemo, useState, useCallback } from 'react';
import { roomTypes } from '../../../app/utils/client';
import FilterByText from '../../components/FilterByText';
import GenericTable from '../../components/GenericTable';
import MarkdownText from '../../components/MarkdownText';
@ -10,6 +9,7 @@ import { useRoute } from '../../contexts/RouterContext';
import { useTranslation } from '../../contexts/TranslationContext';
import { useEndpointData } from '../../hooks/useEndpointData';
import { useFormatDate } from '../../hooks/useFormatDate';
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
import RoomTags from './RoomTags';
import { useQuery } from './hooks';
@ -85,7 +85,7 @@ function TeamsTable() {
const renderRow = useCallback(
(team) => {
const { _id, ts, t, name, fname, topic, roomsCount } = team;
const avatarUrl = roomTypes.getConfig(t).getAvatarPath(team);
const avatarUrl = roomCoordinator.getRoomDirectives(t)?.getAvatarPath(team);
return (
<Table.Row key={_id} onKeyDown={onClick(name, t)} onClick={onClick(name, t)} tabIndex={0} role='link' action>
@ -96,7 +96,7 @@ function TeamsTable() {
</Box>
<Box grow={1} mi='x8' style={style}>
<Box display='flex' alignItems='center'>
<Icon name={roomTypes.getIcon(team)} color='hint' />{' '}
<Icon name={roomCoordinator.getIcon(team)} color='hint' />{' '}
<Box fontScale='p2m' mi='x4'>
{fname || name}
</Box>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save