From d2c6ed97d835c777778cd72d06b578107b0a2ce8 Mon Sep 17 00:00:00 2001 From: Debdut Chakraborty Date: Sun, 5 Jun 2022 13:45:44 +0530 Subject: [PATCH 1/8] Chore: Messages raw model rewrite to ts (#25761) * Messages raw model rewrtite to ts Co-authored-by: Pierre Lehnen --- .gitignore | 3 +- apps/meteor/app/api/server/v1/im.ts | 4 +- .../server/raw/{Messages.js => Messages.ts} | 128 +++++++++++++----- .../modules/watchers/watchers.module.ts | 2 +- 4 files changed, 96 insertions(+), 41 deletions(-) rename apps/meteor/app/models/server/raw/{Messages.js => Messages.ts} (50%) diff --git a/.gitignore b/.gitignore index 816c05071f59..310be7ef939d 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ yarn-error.log* !.yarn/sdks !.yarn/versions -.nvmrc \ No newline at end of file +.nvmrc +.idea/ diff --git a/apps/meteor/app/api/server/v1/im.ts b/apps/meteor/app/api/server/v1/im.ts index 5795f9601ac2..0236d350b212 100644 --- a/apps/meteor/app/api/server/v1/im.ts +++ b/apps/meteor/app/api/server/v1/im.ts @@ -358,7 +358,7 @@ API.v1.addRoute( sort: sortObj, skip: offset, limit: count, - ...(fields && { fields }), + ...(fields && { projection: fields }), }).toArray(); return API.v1.success({ @@ -410,7 +410,7 @@ API.v1.addRoute( sort: sort || { ts: -1 }, skip: offset, limit: count, - fields, + projection: fields, }).toArray(); if (!msgs) { diff --git a/apps/meteor/app/models/server/raw/Messages.js b/apps/meteor/app/models/server/raw/Messages.ts similarity index 50% rename from apps/meteor/app/models/server/raw/Messages.js rename to apps/meteor/app/models/server/raw/Messages.ts index 7f84cac25420..ee81c791180a 100644 --- a/apps/meteor/app/models/server/raw/Messages.js +++ b/apps/meteor/app/models/server/raw/Messages.ts @@ -1,10 +1,25 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; +import type { IMessage, IRoom, IUser, MessageTypesValues, ILivechatDepartment } from '@rocket.chat/core-typings'; +import type { PaginatedRequest } from '@rocket.chat/rest-typings'; +import type { + AggregationCursor, + Cursor, + FilterQuery, + FindOneOptions, + WithoutProjection, + Collection, + CollectionAggregationOptions, +} from 'mongodb'; import { BaseRaw } from './BaseRaw'; -export class MessagesRaw extends BaseRaw { - findVisibleByMentionAndRoomId(username, rid, options) { - const query = { +export class MessagesRaw extends BaseRaw { + findVisibleByMentionAndRoomId( + username: IUser['username'], + rid: IRoom['_id'], + options: WithoutProjection>, + ): Cursor { + const query: FilterQuery = { '_hidden': { $ne: true }, 'mentions.username': username, rid, @@ -13,8 +28,12 @@ export class MessagesRaw extends BaseRaw { return this.find(query, options); } - findStarredByUserAtRoom(userId, roomId, options) { - const query = { + findStarredByUserAtRoom( + userId: IUser['_id'], + roomId: IRoom['_id'], + options: WithoutProjection>, + ): Cursor { + const query: FilterQuery = { '_hidden': { $ne: true }, 'starred._id': userId, 'rid': roomId, @@ -23,21 +42,21 @@ export class MessagesRaw extends BaseRaw { return this.find(query, options); } - findByRoomIdAndType(roomId, type, options) { - const query = { + findByRoomIdAndType( + roomId: IRoom['_id'], + type: IMessage['t'], + options: WithoutProjection> = {}, + ): Cursor { + const query: FilterQuery = { rid: roomId, t: type, }; - if (options == null) { - options = {}; - } - return this.find(query, options); } - findSnippetedByRoom(roomId, options) { - const query = { + findSnippetedByRoom(roomId: IRoom['_id'], options: WithoutProjection>): Cursor { + const query: FilterQuery = { _hidden: { $ne: true }, snippeted: true, rid: roomId, @@ -46,14 +65,15 @@ export class MessagesRaw extends BaseRaw { return this.find(query, options); } - findDiscussionsByRoom(rid, options) { - const query = { rid, drid: { $exists: true } }; + // TODO: do we need this? currently not used anywhere + findDiscussionsByRoom(rid: IRoom['_id'], options: WithoutProjection>): Cursor { + const query: FilterQuery = { rid, drid: { $exists: true } }; return this.find(query, options); } - findDiscussionsByRoomAndText(rid, text, options) { - const query = { + findDiscussionsByRoomAndText(rid: IRoom['_id'], text: string, options: WithoutProjection>): Cursor { + const query: FilterQuery = { rid, drid: { $exists: true }, msg: new RegExp(escapeRegExp(text), 'i'), @@ -62,7 +82,20 @@ export class MessagesRaw extends BaseRaw { return this.find(query, options); } - findAllNumberOfTransferredRooms({ start, end, departmentId, onlyCount = false, options = {} }) { + findAllNumberOfTransferredRooms({ + start, + end, + departmentId, + onlyCount = false, + options = {}, + }: { + start: string; + end: string; + departmentId: ILivechatDepartment['_id']; + onlyCount: boolean; + options: PaginatedRequest; + }): AggregationCursor { + // FIXME: aggregation type definitions const match = { $match: { t: 'livechat_transfer_history', @@ -98,7 +131,7 @@ export class MessagesRaw extends BaseRaw { numberOfTransferredRooms: 1, }, }; - const firstParams = [match, lookup, unwind]; + const firstParams: Exclude['aggregate']>[0], undefined> = [match, lookup, unwind]; if (departmentId) { firstParams.push({ $match: { @@ -121,8 +154,8 @@ export class MessagesRaw extends BaseRaw { return this.col.aggregate(params, { allowDiskUse: true }); } - getTotalOfMessagesSentByDate({ start, end, options = {} }) { - const params = [ + getTotalOfMessagesSentByDate({ start, end, options = {} }: { start: Date; end: Date; options?: PaginatedRequest }): Promise { + const params: Exclude['aggregate']>[0], undefined> = [ { $match: { t: { $exists: false }, ts: { $gte: start, $lte: end } } }, { $lookup: { @@ -179,7 +212,7 @@ export class MessagesRaw extends BaseRaw { return this.col.aggregate(params).toArray(); } - findLivechatClosedMessages(rid, options) { + findLivechatClosedMessages(rid: IRoom['_id'], options: WithoutProjection>): Cursor { return this.find( { rid, @@ -189,32 +222,53 @@ export class MessagesRaw extends BaseRaw { ); } - async countRoomsWithStarredMessages(options) { - const [queryResult] = await this.col - .aggregate( - [{ $match: { 'starred._id': { $exists: true } } }, { $group: { _id: '$rid' } }, { $group: { _id: null, total: { $sum: 1 } } }], + async countRoomsWithStarredMessages(options: CollectionAggregationOptions): Promise { + const queryResult = await this.col + .aggregate<{ _id: null; total: number }>( + [ + { $match: { 'starred._id': { $exists: true } } }, + { $group: { _id: '$rid' } }, + { + $group: { + _id: null, + total: { $sum: 1 }, + }, + }, + ], options, ) - .toArray(); + .next(); return queryResult?.total || 0; } - async countRoomsWithPinnedMessages(options) { - const [queryResult] = await this.col - .aggregate([{ $match: { pinned: true } }, { $group: { _id: '$rid' } }, { $group: { _id: null, total: { $sum: 1 } } }], options) - .toArray(); + async countRoomsWithPinnedMessages(options: CollectionAggregationOptions): Promise { + const queryResult = await this.col + .aggregate<{ _id: null; total: number }>( + [ + { $match: { pinned: true } }, + { $group: { _id: '$rid' } }, + { + $group: { + _id: null, + total: { $sum: 1 }, + }, + }, + ], + options, + ) + .next(); return queryResult?.total || 0; } - async countE2EEMessages(options) { + async countE2EEMessages(options: WithoutProjection>): Promise { return this.find({ t: 'e2e' }, options).count(); } - findPinned(options) { - const query = { - t: { $ne: 'rm' }, + findPinned(options: WithoutProjection>): Cursor { + const query: FilterQuery = { + t: { $ne: 'rm' as MessageTypesValues }, _hidden: { $ne: true }, pinned: true, }; @@ -222,8 +276,8 @@ export class MessagesRaw extends BaseRaw { return this.find(query, options); } - findStarred(options) { - const query = { + findStarred(options: WithoutProjection>): Cursor { + const query: FilterQuery = { '_hidden': { $ne: true }, 'starred._id': { $exists: true }, }; diff --git a/apps/meteor/server/modules/watchers/watchers.module.ts b/apps/meteor/server/modules/watchers/watchers.module.ts index b4914d742181..f3567981dc14 100644 --- a/apps/meteor/server/modules/watchers/watchers.module.ts +++ b/apps/meteor/server/modules/watchers/watchers.module.ts @@ -122,7 +122,7 @@ export function initWatchers(models: IModelsParam, broadcast: BroadcastCallback, switch (clientAction) { case 'inserted': case 'updated': - const message: IMessage | undefined = data ?? (await Messages.findOne({ _id: id })); + const message: IMessage | null = data ?? (await Messages.findOne({ _id: id })); if (!message) { return; } From 881e7627526f52ba6cabc84eded2916cf37cf064 Mon Sep 17 00:00:00 2001 From: Kunal Verma <67605729+Kunalvrm555@users.noreply.github.com> Date: Sun, 5 Jun 2022 23:06:34 +0530 Subject: [PATCH 2/8] [FIX] user status Offline misnamed as Invisible in Custom Status edit dropdown menu (#24796) * Invisible to Offline * accomodate #25265 (Convert to tsx)+ * Rename UserStatusMenu.js to UserStatusMenu.tsx * Use correct file path Co-authored-by: Debdut Chakraborty Co-authored-by: dougfabris --- apps/meteor/client/components/UserStatusMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/components/UserStatusMenu.tsx b/apps/meteor/client/components/UserStatusMenu.tsx index c435f26cc8e1..5df7dd7c33cc 100644 --- a/apps/meteor/client/components/UserStatusMenu.tsx +++ b/apps/meteor/client/components/UserStatusMenu.tsx @@ -42,7 +42,7 @@ const UserStatusMenu = ({ ]; if (allowInvisibleStatus) { - statuses.push([UserStatusType.OFFLINE, renderOption(UserStatusType.OFFLINE, t('Invisible'))]); + statuses.push([UserStatusType.OFFLINE, renderOption(UserStatusType.OFFLINE, t('Offline'))]); } return statuses; From e682030213afd15a635a3c73b07815cb37af2126 Mon Sep 17 00:00:00 2001 From: Marcos Spessatto Defendi Date: Mon, 6 Jun 2022 11:20:35 -0300 Subject: [PATCH 3/8] [IMPROVE] Refactor + unit tests for federation-v2 (#25680) * refactor: refactor matrix bridge to support testing + unit tests * fix: fix conflict * fix: types on test * fix: fix tests * chore: rename bridged to federated * chore: remove duplicated config * fix: fix yarn.lock * chore: add migration for the renamed field * chore: fix lint * fix: type --- .../server/application/RoomServiceReceiver.ts | 217 +++++++++ .../server/application/RoomServiceSender.ts | 128 +++++ .../application/input/RoomReceiverDto.ts | 63 +++ .../server/application/input/RoomSenderDto.ts | 21 + .../meteor/app/federation-v2/server/bridge.ts | 85 ---- .../meteor/app/federation-v2/server/config.ts | 43 -- .../server/data-interface/index.ts | 9 - .../server/data-interface/message.ts | 17 - .../server/data-interface/room.ts | 8 - .../server/data-interface/user.ts | 8 - .../server/domain/FederatedRoom.ts | 78 ++++ .../server/domain/FederatedUser.ts | 38 ++ .../server/domain/IFederationBridge.ts | 25 + .../app/federation-v2/server/eventHandler.ts | 50 -- .../federation-v2/server/events/createRoom.ts | 187 -------- .../app/federation-v2/server/events/index.ts | 6 - .../server/events/roomMembership.ts | 86 ---- .../server/events/sendMessage.ts | 28 -- .../server/events/setRoomJoinRules.ts | 56 --- .../server/events/setRoomName.ts | 42 -- .../server/events/setRoomTopic.ts | 22 - apps/meteor/app/federation-v2/server/index.ts | 37 +- .../server/infrastructure/Factory.ts | 90 ++++ .../server/infrastructure/matrix/Bridge.ts | 177 +++++++ .../matrix/converters/RoomReceiver.ts | 152 ++++++ .../matrix}/definitions/IMatrixEvent.ts | 2 +- .../IMatrixEventContentAddMemberToRoom.ts | 0 .../IMatrixEventContentCreateRoom.ts | 2 +- .../IMatrixEventContentSendMessage.ts | 0 .../IMatrixEventContentSetRoomJoinRules.ts | 4 +- .../IMatrixEventContentSetRoomName.ts | 0 .../IMatrixEventContentSetRoomTopic.ts | 0 .../definitions/IMatrixEventContent/index.ts | 12 +- .../matrix}/definitions/MatrixEventType.ts | 12 +- .../matrix/handlers/BaseEvent.ts | 16 + .../infrastructure/matrix/handlers/Room.ts | 65 +++ .../infrastructure/matrix/handlers/index.ts | 16 + .../infrastructure/queue/InMemoryQueue.ts | 16 + .../rocket-chat/adapters/Message.ts | 9 + .../rocket-chat/adapters/Notification.ts | 14 + .../rocket-chat/adapters/Room.ts | 103 +++++ .../rocket-chat/adapters/Settings.ts | 192 ++++++++ .../rocket-chat/adapters/User.ts | 85 ++++ .../rocket-chat/adapters}/logger.ts | 2 +- .../rocket-chat/converters/RoomSender.ts | 34 ++ .../server/matrix-client/index.ts | 9 - .../server/matrix-client/message.ts | 25 - .../server/matrix-client/room.ts | 68 --- .../server/matrix-client/user.ts | 174 ------- .../server/methods/checkBridgedRoomExists.ts | 7 - apps/meteor/app/federation-v2/server/queue.ts | 16 - .../app/federation-v2/server/settings.ts | 136 ------ .../app/federation-v2/server/startup.ts | 37 -- .../app/lib/client/methods/sendMessage.js | 6 +- .../app/lib/server/methods/sendMessage.js | 13 +- apps/meteor/app/models/server/raw/Rooms.js | 4 +- .../app/slashcommands-bridge/server/index.ts | 70 +-- apps/meteor/app/ui-sidenav/client/roomList.js | 10 +- apps/meteor/package.json | 2 + .../server/modules/watchers/publishFields.ts | 2 +- .../meteor/server/startup/migrations/index.ts | 1 + apps/meteor/server/startup/migrations/v266.ts | 19 + .../application/RoomServiceReceiver.spec.ts | 436 ++++++++++++++++++ .../application/RoomServiceSender.spec.ts | 311 +++++++++++++ .../unit/domain/FederatedRoom.spec.ts | 169 +++++++ .../unit/domain/FederatedUser.spec.ts | 43 ++ .../matrix/converters/RoomReceiver.spec.ts | 328 +++++++++++++ .../matrix/handlers/BaseEvent.spec.ts | 45 ++ .../handlers/MatrixEventsHandler.spec.ts | 25 + .../queue/InMemoryQueue.spec.ts | 28 ++ .../rocket-chat/adapters/Room.spec.ts | 3 + .../rocket-chat/adapters/Settings.spec.ts | 3 + .../rocket-chat/adapters/User.spec.ts | 5 + .../rocket-chat/converters/RoomSender.spec.ts | 72 +++ packages/core-typings/src/IRoom.ts | 1 + yarn.lock | 101 +++- 76 files changed, 3234 insertions(+), 1192 deletions(-) create mode 100644 apps/meteor/app/federation-v2/server/application/RoomServiceReceiver.ts create mode 100644 apps/meteor/app/federation-v2/server/application/RoomServiceSender.ts create mode 100644 apps/meteor/app/federation-v2/server/application/input/RoomReceiverDto.ts create mode 100644 apps/meteor/app/federation-v2/server/application/input/RoomSenderDto.ts delete mode 100644 apps/meteor/app/federation-v2/server/bridge.ts delete mode 100644 apps/meteor/app/federation-v2/server/config.ts delete mode 100644 apps/meteor/app/federation-v2/server/data-interface/index.ts delete mode 100644 apps/meteor/app/federation-v2/server/data-interface/message.ts delete mode 100644 apps/meteor/app/federation-v2/server/data-interface/room.ts delete mode 100644 apps/meteor/app/federation-v2/server/data-interface/user.ts create mode 100644 apps/meteor/app/federation-v2/server/domain/FederatedRoom.ts create mode 100644 apps/meteor/app/federation-v2/server/domain/FederatedUser.ts create mode 100644 apps/meteor/app/federation-v2/server/domain/IFederationBridge.ts delete mode 100644 apps/meteor/app/federation-v2/server/eventHandler.ts delete mode 100644 apps/meteor/app/federation-v2/server/events/createRoom.ts delete mode 100644 apps/meteor/app/federation-v2/server/events/index.ts delete mode 100644 apps/meteor/app/federation-v2/server/events/roomMembership.ts delete mode 100644 apps/meteor/app/federation-v2/server/events/sendMessage.ts delete mode 100644 apps/meteor/app/federation-v2/server/events/setRoomJoinRules.ts delete mode 100644 apps/meteor/app/federation-v2/server/events/setRoomName.ts delete mode 100644 apps/meteor/app/federation-v2/server/events/setRoomTopic.ts create mode 100644 apps/meteor/app/federation-v2/server/infrastructure/Factory.ts create mode 100644 apps/meteor/app/federation-v2/server/infrastructure/matrix/Bridge.ts create mode 100644 apps/meteor/app/federation-v2/server/infrastructure/matrix/converters/RoomReceiver.ts rename apps/meteor/app/federation-v2/server/{ => infrastructure/matrix}/definitions/IMatrixEvent.ts (84%) rename apps/meteor/app/federation-v2/server/{ => infrastructure/matrix}/definitions/IMatrixEventContent/IMatrixEventContentAddMemberToRoom.ts (100%) rename apps/meteor/app/federation-v2/server/{ => infrastructure/matrix}/definitions/IMatrixEventContent/IMatrixEventContentCreateRoom.ts (64%) rename apps/meteor/app/federation-v2/server/{ => infrastructure/matrix}/definitions/IMatrixEventContent/IMatrixEventContentSendMessage.ts (100%) rename apps/meteor/app/federation-v2/server/{ => infrastructure/matrix}/definitions/IMatrixEventContent/IMatrixEventContentSetRoomJoinRules.ts (61%) rename apps/meteor/app/federation-v2/server/{ => infrastructure/matrix}/definitions/IMatrixEventContent/IMatrixEventContentSetRoomName.ts (100%) rename apps/meteor/app/federation-v2/server/{ => infrastructure/matrix}/definitions/IMatrixEventContent/IMatrixEventContentSetRoomTopic.ts (100%) rename apps/meteor/app/federation-v2/server/{ => infrastructure/matrix}/definitions/IMatrixEventContent/index.ts (57%) rename apps/meteor/app/federation-v2/server/{ => infrastructure/matrix}/definitions/MatrixEventType.ts (51%) create mode 100644 apps/meteor/app/federation-v2/server/infrastructure/matrix/handlers/BaseEvent.ts create mode 100644 apps/meteor/app/federation-v2/server/infrastructure/matrix/handlers/Room.ts create mode 100644 apps/meteor/app/federation-v2/server/infrastructure/matrix/handlers/index.ts create mode 100644 apps/meteor/app/federation-v2/server/infrastructure/queue/InMemoryQueue.ts create mode 100644 apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Message.ts create mode 100644 apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Notification.ts create mode 100644 apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Room.ts create mode 100644 apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Settings.ts create mode 100644 apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/User.ts rename apps/meteor/app/federation-v2/server/{ => infrastructure/rocket-chat/adapters}/logger.ts (73%) create mode 100644 apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/converters/RoomSender.ts delete mode 100644 apps/meteor/app/federation-v2/server/matrix-client/index.ts delete mode 100644 apps/meteor/app/federation-v2/server/matrix-client/message.ts delete mode 100644 apps/meteor/app/federation-v2/server/matrix-client/room.ts delete mode 100644 apps/meteor/app/federation-v2/server/matrix-client/user.ts delete mode 100644 apps/meteor/app/federation-v2/server/methods/checkBridgedRoomExists.ts delete mode 100644 apps/meteor/app/federation-v2/server/queue.ts delete mode 100644 apps/meteor/app/federation-v2/server/settings.ts delete mode 100644 apps/meteor/app/federation-v2/server/startup.ts create mode 100644 apps/meteor/server/startup/migrations/v266.ts create mode 100644 apps/meteor/tests/unit/app/federation-v2/unit/application/RoomServiceReceiver.spec.ts create mode 100644 apps/meteor/tests/unit/app/federation-v2/unit/application/RoomServiceSender.spec.ts create mode 100644 apps/meteor/tests/unit/app/federation-v2/unit/domain/FederatedRoom.spec.ts create mode 100644 apps/meteor/tests/unit/app/federation-v2/unit/domain/FederatedUser.spec.ts create mode 100644 apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/matrix/converters/RoomReceiver.spec.ts create mode 100644 apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/matrix/handlers/BaseEvent.spec.ts create mode 100644 apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/matrix/handlers/MatrixEventsHandler.spec.ts create mode 100644 apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/queue/InMemoryQueue.spec.ts create mode 100644 apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/adapters/Room.spec.ts create mode 100644 apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/adapters/Settings.spec.ts create mode 100644 apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/adapters/User.spec.ts create mode 100644 apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/converters/RoomSender.spec.ts diff --git a/apps/meteor/app/federation-v2/server/application/RoomServiceReceiver.ts b/apps/meteor/app/federation-v2/server/application/RoomServiceReceiver.ts new file mode 100644 index 000000000000..49898c6ec2a7 --- /dev/null +++ b/apps/meteor/app/federation-v2/server/application/RoomServiceReceiver.ts @@ -0,0 +1,217 @@ +import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; + +import { FederatedRoom } from '../domain/FederatedRoom'; +import { FederatedUser } from '../domain/FederatedUser'; +import { EVENT_ORIGIN, IFederationBridge } from '../domain/IFederationBridge'; +import { RocketChatMessageAdapter } from '../infrastructure/rocket-chat/adapters/Message'; +import { RocketChatRoomAdapter } from '../infrastructure/rocket-chat/adapters/Room'; +import { RocketChatSettingsAdapter } from '../infrastructure/rocket-chat/adapters/Settings'; +import { RocketChatUserAdapter } from '../infrastructure/rocket-chat/adapters/User'; +import { + FederationRoomCreateInputDto, + FederationRoomChangeMembershipDto, + FederationRoomSendInternalMessageDto, + FederationRoomChangeJoinRulesDto, + FederationRoomChangeNameDto, + FederationRoomChangeTopicDto, +} from './input/RoomReceiverDto'; + +export class FederationRoomServiceReceiver { + constructor( + private rocketRoomAdapter: RocketChatRoomAdapter, + private rocketUserAdapter: RocketChatUserAdapter, + private rocketMessageAdapter: RocketChatMessageAdapter, + private rocketSettingsAdapter: RocketChatSettingsAdapter, + private bridge: IFederationBridge, + ) {} // eslint-disable-line no-empty-function + + public async createRoom(roomCreateInput: FederationRoomCreateInputDto): Promise { + const { + externalRoomId, + externalInviterId, + normalizedInviterId, + externalRoomName, + normalizedRoomId, + roomType, + wasInternallyProgramaticallyCreated = false, + } = roomCreateInput; + + if ((await this.rocketRoomAdapter.getFederatedRoomByExternalId(externalRoomId)) || wasInternallyProgramaticallyCreated) { + return; + } + + if (!(await this.rocketUserAdapter.getFederatedUserByExternalId(externalInviterId))) { + const externalUserProfileInformation = await this.bridge.getUserProfileInformation(externalInviterId); + const name = externalUserProfileInformation?.displayname || normalizedInviterId; + const federatedCreatorUser = FederatedUser.createInstance(externalInviterId, { + name, + username: normalizedInviterId, + existsOnlyOnProxyServer: false, + }); + + await this.rocketUserAdapter.createFederatedUser(federatedCreatorUser); + } + const creator = await this.rocketUserAdapter.getFederatedUserByExternalId(externalInviterId); + const newFederatedRoom = FederatedRoom.createInstance( + externalRoomId, + normalizedRoomId, + creator as FederatedUser, + roomType || RoomType.CHANNEL, + externalRoomName, + ); + await this.rocketRoomAdapter.createFederatedRoom(newFederatedRoom); + } + + public async changeRoomMembership(roomChangeMembershipInput: FederationRoomChangeMembershipDto): Promise { + const { + externalRoomId, + normalizedInviteeId, + normalizedRoomId, + normalizedInviterId, + externalRoomName, + externalInviteeId, + externalInviterId, + inviteeUsernameOnly, + inviterUsernameOnly, + eventOrigin, + roomType, + leave, + } = roomChangeMembershipInput; + const affectedFederatedRoom = await this.rocketRoomAdapter.getFederatedRoomByExternalId(externalRoomId); + if (!affectedFederatedRoom && eventOrigin === EVENT_ORIGIN.LOCAL) { + throw new Error(`Could not find room with external room id: ${externalRoomId}`); + } + const isInviterFromTheSameHomeServer = await this.bridge.isUserIdFromTheSameHomeserver( + externalInviterId, + this.rocketSettingsAdapter.getHomeServerDomain(), + ); + const isInviteeFromTheSameHomeServer = await this.bridge.isUserIdFromTheSameHomeserver( + externalInviteeId, + this.rocketSettingsAdapter.getHomeServerDomain(), + ); + + if (!(await this.rocketUserAdapter.getFederatedUserByExternalId(externalInviterId))) { + const externalUserProfileInformation = await this.bridge.getUserProfileInformation(externalInviterId); + const name = externalUserProfileInformation.displayname || normalizedInviterId; + const username = isInviterFromTheSameHomeServer ? inviterUsernameOnly : normalizedInviterId; + const federatedInviterUser = FederatedUser.createInstance(externalInviterId, { + name, + username, + existsOnlyOnProxyServer: isInviterFromTheSameHomeServer, + }); + + await this.rocketUserAdapter.createFederatedUser(federatedInviterUser); + } + + if (!(await this.rocketUserAdapter.getFederatedUserByExternalId(externalInviteeId))) { + const externalUserProfileInformation = await this.bridge.getUserProfileInformation(externalInviteeId); + const name = externalUserProfileInformation.displayname || normalizedInviteeId; + const username = isInviteeFromTheSameHomeServer ? inviteeUsernameOnly : normalizedInviteeId; + const federatedInviteeUser = FederatedUser.createInstance(externalInviteeId, { + name, + username, + existsOnlyOnProxyServer: isInviteeFromTheSameHomeServer, + }); + + await this.rocketUserAdapter.createFederatedUser(federatedInviteeUser); + } + + const federatedInviteeUser = await this.rocketUserAdapter.getFederatedUserByExternalId(externalInviteeId); + const federatedInviterUser = await this.rocketUserAdapter.getFederatedUserByExternalId(externalInviterId); + + if (!affectedFederatedRoom && eventOrigin === EVENT_ORIGIN.REMOTE) { + const members = [federatedInviterUser, federatedInviteeUser] as any[]; + const newFederatedRoom = FederatedRoom.createInstance( + externalRoomId, + normalizedRoomId, + federatedInviterUser as FederatedUser, + roomType, + externalRoomName, + members, + ); + + await this.rocketRoomAdapter.createFederatedRoom(newFederatedRoom); + await this.bridge.joinRoom(externalRoomId, externalInviteeId); + } + const federatedRoom = affectedFederatedRoom || (await this.rocketRoomAdapter.getFederatedRoomByExternalId(externalRoomId)); + + if (leave) { + return this.rocketRoomAdapter.removeUserFromRoom( + federatedRoom as FederatedRoom, + federatedInviteeUser as FederatedUser, + federatedInviterUser as FederatedUser, + ); + } + await this.rocketRoomAdapter.addUserToRoom( + federatedRoom as FederatedRoom, + federatedInviteeUser as FederatedUser, + federatedInviterUser as FederatedUser, + ); + } + + public async receiveExternalMessage(roomSendInternalMessageInput: FederationRoomSendInternalMessageDto): Promise { + const { externalRoomId, externalSenderId, text } = roomSendInternalMessageInput; + + const federatedRoom = await this.rocketRoomAdapter.getFederatedRoomByExternalId(externalRoomId); + if (!federatedRoom) { + return; + } + + const senderUser = await this.rocketUserAdapter.getFederatedUserByExternalId(externalSenderId); + if (!senderUser) { + return; + } + + await this.rocketMessageAdapter.sendMessage(senderUser, text, federatedRoom); + } + + public async changeJoinRules(roomJoinRulesChangeInput: FederationRoomChangeJoinRulesDto): Promise { + const { externalRoomId, roomType } = roomJoinRulesChangeInput; + + const federatedRoom = await this.rocketRoomAdapter.getFederatedRoomByExternalId(externalRoomId); + if (!federatedRoom) { + return; + } + + if (federatedRoom.isDirectMessage()) { + return; + } + + federatedRoom.setRoomType(roomType); + await this.rocketRoomAdapter.updateRoomType(federatedRoom); + } + + public async changeRoomName(roomChangeNameInput: FederationRoomChangeNameDto): Promise { + const { externalRoomId, normalizedRoomName } = roomChangeNameInput; + + const federatedRoom = await this.rocketRoomAdapter.getFederatedRoomByExternalId(externalRoomId); + if (!federatedRoom) { + return; + } + + if (federatedRoom.isDirectMessage()) { + return; + } + + federatedRoom.changeRoomName(normalizedRoomName); + + await this.rocketRoomAdapter.updateRoomName(federatedRoom); + } + + public async changeRoomTopic(roomChangeTopicInput: FederationRoomChangeTopicDto): Promise { + const { externalRoomId, roomTopic } = roomChangeTopicInput; + + const federatedRoom = await this.rocketRoomAdapter.getFederatedRoomByExternalId(externalRoomId); + if (!federatedRoom) { + return; + } + + if (federatedRoom.isDirectMessage()) { + return; + } + + federatedRoom.changeRoomTopic(roomTopic); + + await this.rocketRoomAdapter.updateRoomTopic(federatedRoom); + } +} diff --git a/apps/meteor/app/federation-v2/server/application/RoomServiceSender.ts b/apps/meteor/app/federation-v2/server/application/RoomServiceSender.ts new file mode 100644 index 000000000000..2be2283ddd86 --- /dev/null +++ b/apps/meteor/app/federation-v2/server/application/RoomServiceSender.ts @@ -0,0 +1,128 @@ +import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; +import { IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; + +import { FederatedRoom } from '../domain/FederatedRoom'; +import { FederatedUser } from '../domain/FederatedUser'; +import { IFederationBridge } from '../domain/IFederationBridge'; +import { RocketChatNotificationAdapter } from '../infrastructure/rocket-chat/adapters/Notification'; +import { RocketChatRoomAdapter } from '../infrastructure/rocket-chat/adapters/Room'; +import { RocketChatSettingsAdapter } from '../infrastructure/rocket-chat/adapters/Settings'; +import { RocketChatUserAdapter } from '../infrastructure/rocket-chat/adapters/User'; +import { FederationRoomInviteUserDto, FederationRoomSendExternalMessageDto } from './input/RoomSenderDto'; + +export class FederationRoomServiceSender { + constructor( + private rocketRoomAdapter: RocketChatRoomAdapter, + private rocketUserAdapter: RocketChatUserAdapter, + private rocketSettingsAdapter: RocketChatSettingsAdapter, + private rocketNotificationAdapter: RocketChatNotificationAdapter, + private bridge: IFederationBridge, + ) {} // eslint-disable-line no-empty-function + + public async inviteUserToAFederatedRoom(roomInviteUserInput: FederationRoomInviteUserDto): Promise { + const { normalizedInviteeId, rawInviteeId, internalInviterId, inviteeUsernameOnly, internalRoomId } = roomInviteUserInput; + + if (!(await this.rocketUserAdapter.getFederatedUserByInternalId(internalInviterId))) { + const internalUser = (await this.rocketUserAdapter.getInternalUserById(internalInviterId)) as IUser; + const externalInviterId = await this.bridge.createUser( + internalUser.username as string, + internalUser.name as string, + this.rocketSettingsAdapter.getHomeServerDomain(), + ); + const federatedInviterUser = FederatedUser.createInstance(externalInviterId, { + name: internalUser.name as string, + username: internalUser.username as string, + existsOnlyOnProxyServer: true, + }); + await this.rocketUserAdapter.createFederatedUser(federatedInviterUser); + } + + if (!(await this.rocketUserAdapter.getFederatedUserByInternalUsername(normalizedInviteeId))) { + const externalUserProfileInformation = await this.bridge.getUserProfileInformation(rawInviteeId); + const name = externalUserProfileInformation?.displayname || normalizedInviteeId; + const federatedInviteeUser = FederatedUser.createInstance(rawInviteeId, { + name, + username: normalizedInviteeId, + existsOnlyOnProxyServer: false, + }); + + await this.rocketUserAdapter.createFederatedUser(federatedInviteeUser); + } + + const federatedInviterUser = (await this.rocketUserAdapter.getFederatedUserByInternalId(internalInviterId)) as FederatedUser; + const federatedInviteeUser = (await this.rocketUserAdapter.getFederatedUserByInternalUsername(normalizedInviteeId)) as FederatedUser; + const isInviteeFromTheSameHomeServer = await this.bridge.isUserIdFromTheSameHomeserver( + rawInviteeId, + this.rocketSettingsAdapter.getHomeServerDomain(), + ); + + if (!(await this.rocketRoomAdapter.getFederatedRoomByInternalId(internalRoomId))) { + const internalRoom = (await this.rocketRoomAdapter.getInternalRoomById(internalRoomId)) as IRoom; + const roomName = (internalRoom.fname || internalRoom.name) as string; + const externalRoomId = await this.bridge.createRoom( + federatedInviterUser.externalId, + federatedInviteeUser.externalId, + internalRoom.t as RoomType, + roomName, + internalRoom.topic, + ); + const newFederatedRoom = FederatedRoom.createInstance( + externalRoomId, + externalRoomId, + federatedInviterUser, + internalRoom.t as RoomType, + roomName, + ); + await this.rocketRoomAdapter.updateFederatedRoomByInternalRoomId(internalRoom._id, newFederatedRoom); + } + + const federatedRoom = (await this.rocketRoomAdapter.getFederatedRoomByInternalId(internalRoomId)) as FederatedRoom; + const wasInvitedWhenTheRoomWasCreated = federatedRoom.isDirectMessage(); + if (isInviteeFromTheSameHomeServer) { + await this.bridge.createUser( + inviteeUsernameOnly, + federatedInviteeUser.internalReference.name as string, + this.rocketSettingsAdapter.getHomeServerDomain(), + ); + await this.bridge.inviteToRoom(federatedRoom.externalId, federatedInviterUser.externalId, federatedInviteeUser.externalId); + await this.bridge.joinRoom(federatedRoom.externalId, federatedInviteeUser.externalId); + } else if (!wasInvitedWhenTheRoomWasCreated) { + this.bridge.inviteToRoom(federatedRoom.externalId, federatedInviterUser.externalId, federatedInviteeUser.externalId).catch(() => { + this.rocketNotificationAdapter.notifyWithEphemeralMessage( + 'Federation_Matrix_only_owners_can_invite_users', + federatedInviterUser?.internalReference?._id, + internalRoomId, + federatedInviterUser?.internalReference?.language, + ); + }); + } + await this.rocketRoomAdapter.addUserToRoom(federatedRoom, federatedInviteeUser, federatedInviterUser); + } + + public async sendMessageFromRocketChat(roomSendExternalMessageInput: FederationRoomSendExternalMessageDto): Promise { + const { internalRoomId, internalSenderId, message } = roomSendExternalMessageInput; + + const federatedSender = await this.rocketUserAdapter.getFederatedUserByInternalId(internalSenderId); + const federatedRoom = await this.rocketRoomAdapter.getFederatedRoomByInternalId(internalRoomId); + + if (!federatedSender) { + throw new Error(`Could not find user id for ${internalSenderId}`); + } + if (!federatedRoom) { + throw new Error(`Could not find room id for ${internalRoomId}`); + } + + await this.bridge.sendMessage(federatedRoom.externalId, federatedSender.externalId, message.msg); + + return message; + } + + public async isAFederatedRoom(internalRoomId: string): Promise { + if (!internalRoomId) { + return false; + } + const federatedRoom = await this.rocketRoomAdapter.getFederatedRoomByInternalId(internalRoomId); + + return Boolean(federatedRoom?.isFederated()); + } +} diff --git a/apps/meteor/app/federation-v2/server/application/input/RoomReceiverDto.ts b/apps/meteor/app/federation-v2/server/application/input/RoomReceiverDto.ts new file mode 100644 index 000000000000..ed85b194d67d --- /dev/null +++ b/apps/meteor/app/federation-v2/server/application/input/RoomReceiverDto.ts @@ -0,0 +1,63 @@ +import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; + +import { EVENT_ORIGIN } from '../../domain/IFederationBridge'; + +class BaseRoom { + externalRoomId: string; + + normalizedRoomId: string; +} + +export class FederationRoomCreateInputDto extends BaseRoom { + externalInviterId: string; + + normalizedInviterId: string; + + wasInternallyProgramaticallyCreated?: boolean; + + externalRoomName?: string; + + roomType?: RoomType; +} + +export class FederationRoomChangeMembershipDto extends BaseRoom { + externalInviterId: string; + + normalizedInviterId: string; + + inviterUsernameOnly: string; + + externalInviteeId: string; + + normalizedInviteeId: string; + + inviteeUsernameOnly: string; + + roomType: RoomType; + + eventOrigin: EVENT_ORIGIN; + + leave?: boolean; + + externalRoomName?: string; +} + +export class FederationRoomSendInternalMessageDto extends BaseRoom { + externalSenderId: string; + + normalizedSenderId: string; + + text: string; +} + +export class FederationRoomChangeJoinRulesDto extends BaseRoom { + roomType: RoomType; +} + +export class FederationRoomChangeNameDto extends BaseRoom { + normalizedRoomName: string; +} + +export class FederationRoomChangeTopicDto extends BaseRoom { + roomTopic: string; +} diff --git a/apps/meteor/app/federation-v2/server/application/input/RoomSenderDto.ts b/apps/meteor/app/federation-v2/server/application/input/RoomSenderDto.ts new file mode 100644 index 000000000000..700216105866 --- /dev/null +++ b/apps/meteor/app/federation-v2/server/application/input/RoomSenderDto.ts @@ -0,0 +1,21 @@ +import { IMessage } from '@rocket.chat/core-typings'; + +export class FederationRoomInviteUserDto { + internalInviterId: string; + + internalRoomId: string; + + rawInviteeId: string; + + normalizedInviteeId: string; + + inviteeUsernameOnly: string; +} + +export class FederationRoomSendExternalMessageDto { + internalRoomId: string; + + internalSenderId: string; + + message: IMessage; +} diff --git a/apps/meteor/app/federation-v2/server/bridge.ts b/apps/meteor/app/federation-v2/server/bridge.ts deleted file mode 100644 index 895e472de021..000000000000 --- a/apps/meteor/app/federation-v2/server/bridge.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { Bridge as MatrixBridge } from '@rocket.chat/forked-matrix-appservice-bridge'; - -import { settings } from '../../settings/server'; -import { Settings } from '../../models/server/raw'; -import type { IMatrixEvent } from './definitions/IMatrixEvent'; -import type { MatrixEventType } from './definitions/MatrixEventType'; -import { addToQueue } from './queue'; -import { getRegistrationInfo } from './config'; -import { bridgeLogger } from './logger'; - -class Bridge { - private bridgeInstance: MatrixBridge; - - private isRunning = false; - - public async start(): Promise { - try { - await this.stop(); - await this.createInstance(); - - if (!this.isRunning) { - await this.bridgeInstance.run(this.getBridgePort()); - this.isRunning = true; - } - } catch (e) { - bridgeLogger.error('Failed to initialize the matrix-appservice-bridge.', e); - - bridgeLogger.error('Disabling Matrix Bridge. Please resolve error and try again'); - Settings.updateValueById('Federation_Matrix_enabled', false); - } - } - - public async stop(): Promise { - if (!this.isRunning) { - return; - } - // the http server can take some minutes to shutdown and this promise to be resolved - await this.bridgeInstance?.close(); - this.isRunning = false; - } - - public async getRoomStateByRoomId(userId: string, roomId: string): Promise[]> { - return Array.from(((await this.getInstance().getIntent(userId).roomState(roomId)) as IMatrixEvent[]) || []); - } - - public getInstance(): MatrixBridge { - return this.bridgeInstance; - } - - private async createInstance(): Promise { - bridgeLogger.info('Performing Dynamic Import of matrix-appservice-bridge'); - - // Dynamic import to prevent Rocket.Chat from loading the module until needed and then handle if that fails - const { Bridge: MatrixBridge, AppServiceRegistration } = await import('@rocket.chat/forked-matrix-appservice-bridge'); - - this.bridgeInstance = new MatrixBridge({ - homeserverUrl: settings.get('Federation_Matrix_homeserver_url'), - domain: settings.get('Federation_Matrix_homeserver_domain'), - registration: AppServiceRegistration.fromObject(getRegistrationInfo()), - disableStores: true, - controller: { - onAliasQuery: (alias, matrixRoomId): void => { - console.log('onAliasQuery', alias, matrixRoomId); - }, - onEvent: async (request /* , context*/): Promise => { - // Get the event - const event = request.getData() as unknown as IMatrixEvent; - - addToQueue(event); - }, - onLog: async (line, isError): Promise => { - console.log(line, isError); - }, - }, - }); - } - - private getBridgePort(): number { - const [, , port] = settings.get('Federation_Matrix_bridge_url').split(':'); - - return parseInt(port); - } -} - -export const matrixBridge = new Bridge(); diff --git a/apps/meteor/app/federation-v2/server/config.ts b/apps/meteor/app/federation-v2/server/config.ts deleted file mode 100644 index d1bad2455d80..000000000000 --- a/apps/meteor/app/federation-v2/server/config.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { AppServiceOutput } from '@rocket.chat/forked-matrix-appservice-bridge'; - -import { settings } from '../../settings/server'; - -export type bridgeUrlTuple = [string, string, number]; - -export function getRegistrationInfo(): AppServiceOutput { - /* eslint-disable @typescript-eslint/camelcase */ - return { - id: settings.get('Federation_Matrix_id'), - hs_token: settings.get('Federation_Matrix_hs_token'), - as_token: settings.get('Federation_Matrix_as_token'), - url: settings.get('Federation_Matrix_bridge_url'), - sender_localpart: settings.get('Federation_Matrix_bridge_localpart'), - namespaces: { - users: [ - { - exclusive: false, - // Reserve these MXID's (usernames) - regex: `.*`, - }, - ], - aliases: [ - { - exclusive: false, - // Reserve these room aliases - regex: `.*`, - }, - ], - rooms: [ - { - exclusive: false, - // This regex is used to define which rooms we listen to with the bridge. - // This does not reserve the rooms like the other namespaces. - regex: '.*', - }, - ], - }, - rate_limited: false, - protocols: null, - }; - /* eslint-enable @typescript-eslint/camelcase */ -} diff --git a/apps/meteor/app/federation-v2/server/data-interface/index.ts b/apps/meteor/app/federation-v2/server/data-interface/index.ts deleted file mode 100644 index 18e2fbf7020f..000000000000 --- a/apps/meteor/app/federation-v2/server/data-interface/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as message from './message'; -import * as room from './room'; -import * as user from './user'; - -export const dataInterface = { - message: message.normalize, - room: room.normalize, - user: user.normalize, -}; diff --git a/apps/meteor/app/federation-v2/server/data-interface/message.ts b/apps/meteor/app/federation-v2/server/data-interface/message.ts deleted file mode 100644 index 7d27732f93e6..000000000000 --- a/apps/meteor/app/federation-v2/server/data-interface/message.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { IMessage, IUser } from '@rocket.chat/core-typings'; - -import { dataInterface } from '.'; - -interface INormalizedMessage extends IMessage { - u: Required>; -} - -export const normalize = async (message: IMessage): Promise => { - // TODO: normalize the entire payload (if needed) - const normalizedMessage: INormalizedMessage = message as INormalizedMessage; - - // Normalize the user - normalizedMessage.u = (await dataInterface.user(message.u._id)) as Required>; - - return normalizedMessage; -}; diff --git a/apps/meteor/app/federation-v2/server/data-interface/room.ts b/apps/meteor/app/federation-v2/server/data-interface/room.ts deleted file mode 100644 index df1d2163badf..000000000000 --- a/apps/meteor/app/federation-v2/server/data-interface/room.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IRoom } from '@rocket.chat/core-typings'; - -import { Rooms } from '../../../models/server'; - -export const normalize = async (roomId: string): Promise => { - // Normalize the user - return Rooms.findOneById(roomId); -}; diff --git a/apps/meteor/app/federation-v2/server/data-interface/user.ts b/apps/meteor/app/federation-v2/server/data-interface/user.ts deleted file mode 100644 index 15fb48843428..000000000000 --- a/apps/meteor/app/federation-v2/server/data-interface/user.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IUser } from '@rocket.chat/core-typings'; - -import { Users } from '../../../models/server'; - -export const normalize = async (userId: string): Promise => { - // Normalize the user - return Users.findOneById(userId); -}; diff --git a/apps/meteor/app/federation-v2/server/domain/FederatedRoom.ts b/apps/meteor/app/federation-v2/server/domain/FederatedRoom.ts new file mode 100644 index 000000000000..c82b078c49e7 --- /dev/null +++ b/apps/meteor/app/federation-v2/server/domain/FederatedRoom.ts @@ -0,0 +1,78 @@ +import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; +import { IRoom, IUser } from '@rocket.chat/core-typings'; + +import { FederatedUser } from './FederatedUser'; + +export class FederatedRoom { + public externalId: string; + + public members?: FederatedUser[]; + + public internalReference: IRoom; + + // eslint-disable-next-line + private constructor() {} + + private static generateTemporaryName(normalizedExternalId: string): string { + return `Federation-${normalizedExternalId}`; + } + + public static createInstance( + externalId: string, + normalizedExternalId: string, + creator: FederatedUser, + type: RoomType, + name?: string, + members?: IUser[], + ): FederatedRoom { + const roomName = name || FederatedRoom.generateTemporaryName(normalizedExternalId); + return Object.assign(new FederatedRoom(), { + externalId, + ...(type === RoomType.DIRECT_MESSAGE ? { members } : {}), + internalReference: { + t: type, + name: roomName, + fname: roomName, + u: creator.internalReference, + }, + }); + } + + public static build(): FederatedRoom { + return new FederatedRoom(); + } + + public isDirectMessage(): boolean { + return this.internalReference?.t === RoomType.DIRECT_MESSAGE; + } + + public setRoomType(type: RoomType): void { + if (this.isDirectMessage()) { + throw new Error('Its not possible to change a direct message type'); + } + this.internalReference.t = type; + } + + public changeRoomName(name: string): void { + if (this.isDirectMessage()) { + throw new Error('Its not possible to change a direct message name'); + } + this.internalReference.name = name; + this.internalReference.fname = name; + } + + public changeRoomTopic(topic: string): void { + if (this.isDirectMessage()) { + throw new Error('Its not possible to change a direct message topic'); + } + this.internalReference.description = topic; + } + + public getMembers(): IUser[] { + return this.isDirectMessage() && this.members && this.members.length > 0 ? this.members.map((user) => user.internalReference) : []; + } + + public isFederated(): boolean { + return this.internalReference?.federated === true; + } +} diff --git a/apps/meteor/app/federation-v2/server/domain/FederatedUser.ts b/apps/meteor/app/federation-v2/server/domain/FederatedUser.ts new file mode 100644 index 000000000000..225b0fabc003 --- /dev/null +++ b/apps/meteor/app/federation-v2/server/domain/FederatedUser.ts @@ -0,0 +1,38 @@ +import { IUser } from '@rocket.chat/core-typings'; + +export interface IFederatedUserCreationParams { + name: string; + username: string; + existsOnlyOnProxyServer: boolean; +} + +export class FederatedUser { + public externalId: string; + + public internalReference: IUser; + + public existsOnlyOnProxyServer: boolean; + + // eslint-disable-next-line + private constructor() {} + + public static createInstance(externalId: string, params: IFederatedUserCreationParams): FederatedUser { + return Object.assign(new FederatedUser(), { + externalId, + existsOnlyOnProxyServer: params.existsOnlyOnProxyServer, + internalReference: { + username: params.username, + name: params.name, + type: 'user', + status: 'online', + active: true, + roles: ['user'], + requirePasswordChange: false, + }, + }); + } + + public static build(): FederatedUser { + return new FederatedUser(); + } +} diff --git a/apps/meteor/app/federation-v2/server/domain/IFederationBridge.ts b/apps/meteor/app/federation-v2/server/domain/IFederationBridge.ts new file mode 100644 index 000000000000..86310ec30f17 --- /dev/null +++ b/apps/meteor/app/federation-v2/server/domain/IFederationBridge.ts @@ -0,0 +1,25 @@ +import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; + +export interface IFederationBridge { + start(): Promise; + stop(): Promise; + onFederationAvailabilityChanged(enabled: boolean): Promise; + getUserProfileInformation(externalUserId: string): Promise; + joinRoom(externalRoomId: string, externalUserId: string): Promise; + createRoom( + externalCreatorId: string, + externalInviteeId: string, + roomType: RoomType, + roomName: string, + roomTopic?: string, + ): Promise; + inviteToRoom(externalRoomId: string, externalInviterId: string, externalInviteeId: string): Promise; + sendMessage(externalRoomId: string, externaSenderId: string, text: string): Promise; + createUser(username: string, name: string, domain: string): Promise; + isUserIdFromTheSameHomeserver(externalUserId: string, domain: string): boolean; +} + +export enum EVENT_ORIGIN { + LOCAL = 'LOCAL', + REMOTE = 'REMOTE', +} diff --git a/apps/meteor/app/federation-v2/server/eventHandler.ts b/apps/meteor/app/federation-v2/server/eventHandler.ts deleted file mode 100644 index 166ed1199adc..000000000000 --- a/apps/meteor/app/federation-v2/server/eventHandler.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { IMatrixEvent } from './definitions/IMatrixEvent'; -import { MatrixEventType } from './definitions/MatrixEventType'; -import { handleRoomMembership, handleCreateRoom, handleSendMessage, setRoomJoinRules, setRoomName, setRoomTopic } from './events'; - -export const eventHandler = async (event: IMatrixEvent): Promise => { - console.log(`Processing ${event.type}...`, JSON.stringify(event, null, 2)); - - switch (event.type) { - case MatrixEventType.CREATE_ROOM: { - await handleCreateRoom(event as IMatrixEvent); - - break; - } - case MatrixEventType.ROOM_MEMBERSHIP: { - await handleRoomMembership(event as IMatrixEvent); - - break; - } - case MatrixEventType.SET_ROOM_JOIN_RULES: { - await setRoomJoinRules(event as IMatrixEvent); - - break; - } - case MatrixEventType.SET_ROOM_NAME: { - await setRoomName(event as IMatrixEvent); - - break; - } - case MatrixEventType.SET_ROOM_TOPIC: { - await setRoomTopic(event as IMatrixEvent); - - break; - } - case MatrixEventType.SEND_MESSAGE: { - await handleSendMessage(event as IMatrixEvent); - - break; - } - // case MatrixEventType.SET_ROOM_POWER_LEVELS: - // case MatrixEventType.SET_ROOM_CANONICAL_ALIAS: - // case MatrixEventType.SET_ROOM_HISTORY_VISIBILITY: - // case MatrixEventType.SET_ROOM_GUEST_ACCESS: { - // console.log(`Ignoring ${event.type}`); - // - // break; - // } - default: - console.log(`Could not find handler for ${event.type}`, event); - } -}; diff --git a/apps/meteor/app/federation-v2/server/events/createRoom.ts b/apps/meteor/app/federation-v2/server/events/createRoom.ts deleted file mode 100644 index 5cca3032c455..000000000000 --- a/apps/meteor/app/federation-v2/server/events/createRoom.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { IRoom, RoomType } from '@rocket.chat/apps-engine/definition/rooms'; -import { ICreatedRoom } from '@rocket.chat/core-typings'; -import { IUser } from '@rocket.chat/apps-engine/definition/users'; - -import { MatrixBridgedRoom, MatrixBridgedUser, Users } from '../../../models/server'; -import { Rooms } from '../../../models/server/raw'; -import { createRoom } from '../../../lib/server'; -import { IMatrixEvent } from '../definitions/IMatrixEvent'; -import { MatrixEventType } from '../definitions/MatrixEventType'; -import { checkBridgedRoomExists } from '../methods/checkBridgedRoomExists'; -import { matrixClient } from '../matrix-client'; -import { SetRoomJoinRules } from '../definitions/IMatrixEventContent/IMatrixEventContentSetRoomJoinRules'; -import { matrixBridge } from '../bridge'; -import { setRoomJoinRules } from './setRoomJoinRules'; -import { setRoomName } from './setRoomName'; -import { handleRoomMembership } from './roomMembership'; - -const removeUselessCharacterFromMatrixRoomId = (matrixRoomId: string): string => { - const prefixedRoomIdOnly = matrixRoomId.split(':')[0]; - const prefix = '!'; - - return prefixedRoomIdOnly?.replace(prefix, ''); -}; - -const generateRoomNameForLocalServer = (matrixRoomId: string, matrixRoomName?: string): string => { - return matrixRoomName || `Federation-${removeUselessCharacterFromMatrixRoomId(matrixRoomId)}`; -}; - -const createLocalRoomAsync = async (roomType: RoomType, roomName: string, creator: IUser, members: IUser[] = []): Promise => { - return new Promise((resolve) => resolve(createRoom(roomType, roomName, creator.username, members as any[]) as ICreatedRoom)); -}; - -const createBridgedRecordRoom = async (roomId: IRoom['id'], matrixRoomId: string): Promise => - new Promise((resolve) => resolve(MatrixBridgedRoom.insert({ rid: roomId, mri: matrixRoomId }))); - -const createLocalUserIfNecessary = async (matrixUserId: string): Promise => { - const { uid } = await matrixClient.user.createLocal(matrixUserId); - - return uid; -}; - -const applyRoomStateIfNecessary = async (matrixRoomId: string, roomState?: IMatrixEvent[]): Promise => { - // TODO: this should be better - /* eslint-disable no-await-in-loop */ - for (const state of roomState || []) { - switch (state.type) { - case 'm.room.create': - continue; - case 'm.room.join_rules': { - // @ts-ignore - // eslint-disable-next-line @typescript-eslint/camelcase - await setRoomJoinRules({ room_id: matrixRoomId, ...state }); - - break; - } - case 'm.room.name': { - // @ts-ignore - // eslint-disable-next-line @typescript-eslint/camelcase - await setRoomName({ room_id: matrixRoomId, ...state }); - - break; - } - case 'm.room.member': { - // @ts-ignore - if (state.content.membership === 'join') { - // @ts-ignore - // eslint-disable-next-line @typescript-eslint/camelcase,@typescript-eslint/no-use-before-define - await handleRoomMembership({ room_id: matrixRoomId, ...state }); - } - - break; - } - } - } - /* eslint-enable no-await-in-loop */ -}; - -const mapLocalAndExternal = async (roomId: string, matrixRoomId: string): Promise => { - await createBridgedRecordRoom(roomId, matrixRoomId); - await Rooms.setAsBridged(roomId); -}; - -const tryToGetDataFromExternalRoom = async ( - senderMatrixUserId: string, - matrixRoomId: string, - roomState: IMatrixEvent[] = [], -): Promise> => { - const finalRoomState = - roomState && roomState?.length > 0 ? roomState : await matrixBridge.getRoomStateByRoomId(senderMatrixUserId, matrixRoomId); - const externalRoomName = finalRoomState.find((stateEvent: Record) => stateEvent.type === MatrixEventType.SET_ROOM_NAME) - ?.content?.name; - const externalRoomJoinRule = finalRoomState.find( - (stateEvent: Record) => stateEvent.type === MatrixEventType.SET_ROOM_JOIN_RULES, - )?.content?.join_rule; - - return { - externalRoomName, - externalRoomJoinRule, - }; -}; - -export const createLocalDirectMessageRoom = async (matrixRoomId: string, creator: IUser, affectedUser: IUser): Promise => { - const { _id: roomId } = await createLocalRoomAsync(RoomType.DIRECT_MESSAGE, generateRoomNameForLocalServer(matrixRoomId), creator, [ - creator, - affectedUser, - ]); - await mapLocalAndExternal(roomId, matrixRoomId); - - return roomId; -}; - -export const getLocalRoomType = (matrixJoinRule = '', matrixRoomIsDirect = false): RoomType => { - const mapping: Record = { - [SetRoomJoinRules.JOIN]: RoomType.CHANNEL, - [SetRoomJoinRules.INVITE]: RoomType.PRIVATE_GROUP, - }; - const roomType = mapping[matrixJoinRule] || RoomType.CHANNEL; - - return roomType === RoomType.PRIVATE_GROUP && matrixRoomIsDirect ? RoomType.DIRECT_MESSAGE : roomType; -}; - -export const createLocalChannelsRoom = async ( - matrixRoomId: string, - senderMatrixUserId: string, - creator: IUser, - roomState?: IMatrixEvent[], -): Promise => { - let roomName = ''; - let joinRule; - - try { - const { externalRoomName, externalRoomJoinRule } = await tryToGetDataFromExternalRoom(senderMatrixUserId, matrixRoomId, roomState); - roomName = externalRoomName; - joinRule = externalRoomJoinRule; - } catch (err) { - // no-op - } - const { rid: roomId } = await createLocalRoomAsync( - getLocalRoomType(joinRule), - generateRoomNameForLocalServer(matrixRoomId, roomName), - creator, - ); - await mapLocalAndExternal(roomId, matrixRoomId); - - return roomId; -}; - -export const processFirstAccessFromExternalServer = async ( - matrixRoomId: string, - senderMatrixUserId: string, - affectedMatrixUserId: string, - senderUser: IUser, - affectedUser: IUser, - isDirect = false, - roomState: IMatrixEvent[], -): Promise => { - let roomId; - if (isDirect) { - roomId = await createLocalDirectMessageRoom(matrixRoomId, senderUser, affectedUser); - } else { - roomId = await createLocalChannelsRoom(matrixRoomId, senderMatrixUserId, senderUser, roomState); - } - - await applyRoomStateIfNecessary(matrixRoomId, roomState); - await matrixBridge.getInstance().getIntent(affectedMatrixUserId).join(matrixRoomId); - - return roomId; -}; - -export const handleCreateRoom = async (event: IMatrixEvent): Promise => { - const { - room_id: matrixRoomId, - sender, - content: { was_programatically_created: wasProgramaticallyCreated = false }, - } = event; - - // Check if the room already exists and if so, ignore - const roomExists = await checkBridgedRoomExists(matrixRoomId); - if (roomExists || wasProgramaticallyCreated) { - return; - } - - const bridgedUserId = await MatrixBridgedUser.getId(sender); - const creator = await Users.findOneById(bridgedUserId || (await createLocalUserIfNecessary(sender))); - - await createLocalChannelsRoom(matrixRoomId, sender, creator); -}; diff --git a/apps/meteor/app/federation-v2/server/events/index.ts b/apps/meteor/app/federation-v2/server/events/index.ts deleted file mode 100644 index ef403e8e78cd..000000000000 --- a/apps/meteor/app/federation-v2/server/events/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './createRoom'; -export * from './roomMembership'; -export * from './sendMessage'; -export * from './setRoomJoinRules'; -export * from './setRoomName'; -export * from './setRoomTopic'; diff --git a/apps/meteor/app/federation-v2/server/events/roomMembership.ts b/apps/meteor/app/federation-v2/server/events/roomMembership.ts deleted file mode 100644 index d51233c1f14e..000000000000 --- a/apps/meteor/app/federation-v2/server/events/roomMembership.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { IUser } from '@rocket.chat/apps-engine/definition/users'; - -import { MatrixBridgedUser, MatrixBridgedRoom, Users } from '../../../models/server'; -import { addUserToRoom, removeUserFromRoom } from '../../../lib/server'; -import { IMatrixEvent } from '../definitions/IMatrixEvent'; -import { MatrixEventType } from '../definitions/MatrixEventType'; -import { AddMemberToRoomMembership } from '../definitions/IMatrixEventContent/IMatrixEventContentAddMemberToRoom'; -import { matrixClient } from '../matrix-client'; -import { processFirstAccessFromExternalServer } from './createRoom'; - -const extractServerNameFromMatrixUserId = (matrixRoomId = ''): string => matrixRoomId.split(':')[1]; - -const addUserToRoomAsync = async (roomId: string, affectedUser: IUser, senderUser?: IUser): Promise => { - new Promise((resolve) => resolve(addUserToRoom(roomId, affectedUser as any, senderUser as any))); -}; - -export const handleRoomMembership = async (event: IMatrixEvent): Promise => { - const { - room_id: matrixRoomId, - sender: senderMatrixUserId, - state_key: affectedMatrixUserId, - content: { membership, is_direct: isDirect = false }, - invite_room_state: roomState, - } = event; - - // Find the bridged room id - let roomId = await MatrixBridgedRoom.getId(matrixRoomId); - const fromADifferentServer = - extractServerNameFromMatrixUserId(senderMatrixUserId) !== extractServerNameFromMatrixUserId(affectedMatrixUserId); - - // If there is no room id, throw error - if (!roomId && !fromADifferentServer) { - throw new Error(`Could not find room with matrixRoomId: ${matrixRoomId}`); - } - - // Find the sender user - const senderUserId = await MatrixBridgedUser.getId(senderMatrixUserId); - let senderUser = await Users.findOneById(senderUserId); - // If the sender user does not exist, it means we need to create it - if (!senderUser) { - const { uid } = await matrixClient.user.createLocal(senderMatrixUserId); - - senderUser = Users.findOneById(uid); - } - - // Find the affected user - const affectedUserId = await MatrixBridgedUser.getId(affectedMatrixUserId); - let affectedUser = await Users.findOneById(affectedUserId); - // If the affected user does not exist, it means we need to create it - if (!affectedUser) { - const { uid } = await matrixClient.user.createLocal(affectedMatrixUserId); - - affectedUser = Users.findOneById(uid); - } - - if (!roomId && fromADifferentServer) { - roomId = await processFirstAccessFromExternalServer( - matrixRoomId, - senderMatrixUserId, - affectedMatrixUserId, - senderUser, - affectedUser, - isDirect, - roomState as IMatrixEvent[], - ); - } - - if (!roomId) { - return; - } - - switch (membership) { - case AddMemberToRoomMembership.JOIN: - await addUserToRoomAsync(roomId, affectedUser); - break; - case AddMemberToRoomMembership.INVITE: - // TODO: this should be a local invite - await addUserToRoomAsync(roomId, affectedUser, senderUser); - break; - case AddMemberToRoomMembership.LEAVE: - await removeUserFromRoom(roomId, affectedUser, { - byUser: senderUser, - }); - break; - } -}; diff --git a/apps/meteor/app/federation-v2/server/events/sendMessage.ts b/apps/meteor/app/federation-v2/server/events/sendMessage.ts deleted file mode 100644 index c70577d1e2af..000000000000 --- a/apps/meteor/app/federation-v2/server/events/sendMessage.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { MatrixBridgedRoom, MatrixBridgedUser, Users } from '../../../models/server'; -import { IMatrixEvent } from '../definitions/IMatrixEvent'; -import { MatrixEventType } from '../definitions/MatrixEventType'; -import { sendMessage } from '../../../lib/server'; -import { Rooms } from '../../../models/server/raw'; - -export const sendMessageAsync = async (user: any, msg: any, room: any): Promise => - new Promise((resolve) => resolve(sendMessage(user, msg, room))); - -export const handleSendMessage = async (event: IMatrixEvent): Promise => { - const { room_id: matrixRoomId, sender } = event; - - // Find the bridged room id - const roomId = await MatrixBridgedRoom.getId(matrixRoomId); - if (!roomId) { - return; - } - - // Find the bridged user id - const userId = await MatrixBridgedUser.getId(sender); - - // Find the user - const user = await Users.findOneById(userId); - - const room = await Rooms.findOneById(roomId); - - await sendMessageAsync(user, { msg: event.content.body }, room); -}; diff --git a/apps/meteor/app/federation-v2/server/events/setRoomJoinRules.ts b/apps/meteor/app/federation-v2/server/events/setRoomJoinRules.ts deleted file mode 100644 index e95bf691bf43..000000000000 --- a/apps/meteor/app/federation-v2/server/events/setRoomJoinRules.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; - -import { Rooms, Subscriptions } from '../../../models/server/raw'; -import { MatrixBridgedRoom } from '../../../models/server'; -import { IMatrixEvent } from '../definitions/IMatrixEvent'; -import { MatrixEventType } from '../definitions/MatrixEventType'; -import { SetRoomJoinRules } from '../definitions/IMatrixEventContent/IMatrixEventContentSetRoomJoinRules'; - -export const setRoomJoinRules = async (event: IMatrixEvent): Promise => { - const { - room_id: matrixRoomId, - content: { join_rule: joinRule }, - } = event; - - // Find the bridged room id - const roomId = await MatrixBridgedRoom.getId(matrixRoomId); - if (!roomId) { - return; - } - - const localRoom = await Rooms.findOneById(roomId); - - if (!localRoom || localRoom?.t === RoomType.DIRECT_MESSAGE) { - return; - } - - let type; - - switch (joinRule) { - case SetRoomJoinRules.INVITE: - type = RoomType.PRIVATE_GROUP; - break; - case SetRoomJoinRules.JOIN: - default: - type = RoomType.CHANNEL; - } - - await Rooms.update( - { _id: roomId }, - { - $set: { - t: type, - }, - }, - ); - - await Subscriptions.update( - { rid: roomId }, - { - $set: { - t: type, - }, - }, - { multi: true }, - ); -}; diff --git a/apps/meteor/app/federation-v2/server/events/setRoomName.ts b/apps/meteor/app/federation-v2/server/events/setRoomName.ts deleted file mode 100644 index 243791841d70..000000000000 --- a/apps/meteor/app/federation-v2/server/events/setRoomName.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Rooms, Subscriptions } from '../../../models/server/raw'; -import { MatrixBridgedRoom } from '../../../models/server'; -import { IMatrixEvent } from '../definitions/IMatrixEvent'; -import { MatrixEventType } from '../definitions/MatrixEventType'; - -export const setRoomName = async (event: IMatrixEvent): Promise => { - const { - room_id: matrixRoomId, - content: { name }, - } = event; - - // Normalize room name - const normalizedName = name.replace('@', ''); - - // Find the bridged room id - const roomId = await MatrixBridgedRoom.getId(matrixRoomId); - - if (!roomId) { - return; - } - - await Rooms.update( - { _id: roomId }, - { - $set: { - name: normalizedName, - fname: normalizedName, - }, - }, - ); - - await Subscriptions.update( - { rid: roomId }, - { - $set: { - name: normalizedName, - fname: normalizedName, - }, - }, - { multi: true }, - ); -}; diff --git a/apps/meteor/app/federation-v2/server/events/setRoomTopic.ts b/apps/meteor/app/federation-v2/server/events/setRoomTopic.ts deleted file mode 100644 index d75d38df651e..000000000000 --- a/apps/meteor/app/federation-v2/server/events/setRoomTopic.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { MatrixBridgedRoom, Rooms } from '../../../models/server'; -import { IMatrixEvent } from '../definitions/IMatrixEvent'; -import { MatrixEventType } from '../definitions/MatrixEventType'; - -export const setRoomTopic = async (event: IMatrixEvent): Promise => { - const { - room_id: matrixRoomId, - content: { topic }, - } = event; - - // Find the bridged room id - const roomId = await MatrixBridgedRoom.getId(matrixRoomId); - - Rooms.update( - { _id: roomId }, - { - $set: { - description: topic, - }, - }, - ); -}; diff --git a/apps/meteor/app/federation-v2/server/index.ts b/apps/meteor/app/federation-v2/server/index.ts index e331badfd006..d2d90966d4d0 100644 --- a/apps/meteor/app/federation-v2/server/index.ts +++ b/apps/meteor/app/federation-v2/server/index.ts @@ -1,4 +1,35 @@ -import './settings'; -import { startBridge } from './startup'; +import { FederationFactory } from './infrastructure/Factory'; -startBridge(); +const PROCESSING_CONCURRENCY = 1; + +const rocketSettingsAdapter = FederationFactory.buildRocketSettingsAdapter(); +rocketSettingsAdapter.initialize(); +const queueInstance = FederationFactory.buildQueue(); +const federation = FederationFactory.buildBridge(rocketSettingsAdapter, queueInstance); +const rocketRoomAdapter = FederationFactory.buildRocketRoomAdapter(); +const rocketUserAdapter = FederationFactory.buildRocketUserAdapter(); +const rocketMessageAdapter = FederationFactory.buildRocketMessageAdapter(); +const rocketNotificationAdapter = FederationFactory.buildRocketNotificationdapter(); + +const federationRoomServiceReceiver = FederationFactory.buildRoomServiceReceiver( + rocketRoomAdapter, + rocketUserAdapter, + rocketMessageAdapter, + rocketSettingsAdapter, + federation, +); +const federationEventsHandler = FederationFactory.buildEventHandlers(federationRoomServiceReceiver); + +export const federationRoomServiceSender = FederationFactory.buildRoomServiceSender( + rocketRoomAdapter, + rocketUserAdapter, + rocketSettingsAdapter, + rocketNotificationAdapter, + federation, +); + +(async (): Promise => { + queueInstance.setHandler(federationEventsHandler.handleEvent.bind(federationEventsHandler), PROCESSING_CONCURRENCY); + await federation.start(); + await rocketSettingsAdapter.onFederationEnabledStatusChanged(federation.onFederationAvailabilityChanged.bind(federation)); +})(); diff --git a/apps/meteor/app/federation-v2/server/infrastructure/Factory.ts b/apps/meteor/app/federation-v2/server/infrastructure/Factory.ts new file mode 100644 index 000000000000..36a20f827c10 --- /dev/null +++ b/apps/meteor/app/federation-v2/server/infrastructure/Factory.ts @@ -0,0 +1,90 @@ +import { FederationRoomServiceReceiver } from '../application/RoomServiceReceiver'; +import { FederationRoomServiceSender } from '../application/RoomServiceSender'; +import { MatrixBridge } from './matrix/Bridge'; +import { MatrixEventsHandler } from './matrix/handlers'; +import { + MatrixRoomCreatedHandler, + MatrixRoomJoinRulesChangedHandler, + MatrixRoomMembershipChangedHandler, + MatrixRoomMessageSentHandler, + MatrixRoomNameChangedHandler, + MatrixRoomTopicChangedHandler, +} from './matrix/handlers/Room'; +import { InMemoryQueue } from './queue/InMemoryQueue'; +import { RocketChatMessageAdapter } from './rocket-chat/adapters/Message'; +import { RocketChatRoomAdapter } from './rocket-chat/adapters/Room'; +import { RocketChatSettingsAdapter } from './rocket-chat/adapters/Settings'; +import { RocketChatUserAdapter } from './rocket-chat/adapters/User'; +import { IFederationBridge } from '../domain/IFederationBridge'; +import { RocketChatNotificationAdapter } from './rocket-chat/adapters/Notification'; + +export class FederationFactory { + public static buildRocketSettingsAdapter(): RocketChatSettingsAdapter { + return new RocketChatSettingsAdapter(); + } + + public static buildRocketRoomAdapter(): RocketChatRoomAdapter { + return new RocketChatRoomAdapter(); + } + + public static buildRocketUserAdapter(): RocketChatUserAdapter { + return new RocketChatUserAdapter(); + } + + public static buildRocketMessageAdapter(): RocketChatMessageAdapter { + return new RocketChatMessageAdapter(); + } + + public static buildRocketNotificationdapter(): RocketChatNotificationAdapter { + return new RocketChatNotificationAdapter(); + } + + public static buildQueue(): InMemoryQueue { + return new InMemoryQueue(); + } + + public static buildRoomServiceReceiver( + rocketRoomAdapter: RocketChatRoomAdapter, + rocketUserAdapter: RocketChatUserAdapter, + rocketMessageAdapter: RocketChatMessageAdapter, + rocketSettingsAdapter: RocketChatSettingsAdapter, + bridge: IFederationBridge, + ): FederationRoomServiceReceiver { + return new FederationRoomServiceReceiver(rocketRoomAdapter, rocketUserAdapter, rocketMessageAdapter, rocketSettingsAdapter, bridge); + } + + public static buildRoomServiceSender( + rocketRoomAdapter: RocketChatRoomAdapter, + rocketUserAdapter: RocketChatUserAdapter, + rocketSettingsAdapter: RocketChatSettingsAdapter, + rocketNotificationAdapter: RocketChatNotificationAdapter, + bridge: IFederationBridge, + ): FederationRoomServiceSender { + return new FederationRoomServiceSender(rocketRoomAdapter, rocketUserAdapter, rocketSettingsAdapter, rocketNotificationAdapter, bridge); + } + + public static buildBridge(rocketSettingsAdapter: RocketChatSettingsAdapter, queue: InMemoryQueue): IFederationBridge { + return new MatrixBridge( + rocketSettingsAdapter.getApplicationServiceId(), + rocketSettingsAdapter.getHomeServerUrl(), + rocketSettingsAdapter.getHomeServerDomain(), + rocketSettingsAdapter.getBridgeUrl(), + rocketSettingsAdapter.getBridgePort(), + rocketSettingsAdapter.generateRegistrationFileObject(), + queue.addToQueue.bind(queue), + ); + } + + public static buildEventHandlers(roomServiceReceive: FederationRoomServiceReceiver): MatrixEventsHandler { + const EVENT_HANDLERS = [ + new MatrixRoomCreatedHandler(roomServiceReceive), + new MatrixRoomMembershipChangedHandler(roomServiceReceive), + new MatrixRoomJoinRulesChangedHandler(roomServiceReceive), + new MatrixRoomNameChangedHandler(roomServiceReceive), + new MatrixRoomTopicChangedHandler(roomServiceReceive), + new MatrixRoomMessageSentHandler(roomServiceReceive), + ]; + + return new MatrixEventsHandler(EVENT_HANDLERS); + } +} diff --git a/apps/meteor/app/federation-v2/server/infrastructure/matrix/Bridge.ts b/apps/meteor/app/federation-v2/server/infrastructure/matrix/Bridge.ts new file mode 100644 index 000000000000..acb2824f6903 --- /dev/null +++ b/apps/meteor/app/federation-v2/server/infrastructure/matrix/Bridge.ts @@ -0,0 +1,177 @@ +import { AppServiceOutput, Bridge } from '@rocket.chat/forked-matrix-appservice-bridge'; +import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; + +import { IFederationBridge } from '../../domain/IFederationBridge'; +import { bridgeLogger } from '../rocket-chat/adapters/logger'; +import { IMatrixEvent } from './definitions/IMatrixEvent'; +import { MatrixEventType } from './definitions/MatrixEventType'; + +export class MatrixBridge implements IFederationBridge { + private bridgeInstance: Bridge; + + private isRunning = false; + + constructor( + private appServiceId: string, + private homeServerUrl: string, + private homeServerDomain: string, + private bridgeUrl: string, + private bridgePort: number, + private homeServerRegistrationFile: Record, + private eventHandler: Function, + ) { + this.logInfo(); + } + + public async onFederationAvailabilityChanged(enabled: boolean): Promise { + if (!enabled) { + await this.stop(); + return; + } + await this.start(); + } + + public async start(): Promise { + try { + await this.stop(); + await this.createInstance(); + + if (!this.isRunning) { + await this.bridgeInstance.run(this.bridgePort); + this.isRunning = true; + } + } catch (e) { + bridgeLogger.error('Failed to initialize the matrix-appservice-bridge.', e); + bridgeLogger.error('Disabling Matrix Bridge. Please resolve error and try again'); + + // await this.settingsAdapter.disableFederation(); + } + } + + public async stop(): Promise { + if (!this.isRunning) { + return; + } + // the http server might take some minutes to shutdown, and this promise can take some time to be resolved + await this.bridgeInstance?.close(); + this.isRunning = false; + } + + public async getUserProfileInformation(externalUserId: string): Promise { + try { + return this.bridgeInstance.getIntent(externalUserId).getProfileInfo(externalUserId); + } catch (err) { + // no-op + } + } + + public async joinRoom(externalRoomId: string, externalUserId: string): Promise { + await this.bridgeInstance.getIntent(externalUserId).join(externalRoomId); + } + + public async inviteToRoom(externalRoomId: string, externalInviterId: string, externalInviteeId: string): Promise { + await this.bridgeInstance.getIntent(externalInviterId).invite(externalRoomId, externalInviteeId); + } + + public async createUser(username: string, name: string, domain: string): Promise { + const matrixUserId = `@${username?.toLowerCase()}:${domain}`; + const intent = this.bridgeInstance.getIntent(matrixUserId); + + await intent.ensureProfile(name); + await intent.setDisplayName(`${username} (${name})`); + + return matrixUserId; + } + + public async createRoom( + externalCreatorId: string, + externalInviteeId: string, + roomType: RoomType, + roomName: string, + roomTopic?: string, + ): Promise { + const intent = this.bridgeInstance.getIntent(externalCreatorId); + + const visibility = roomType === 'p' || roomType === 'd' ? 'invite' : 'public'; + const preset = roomType === 'p' || roomType === 'd' ? 'private_chat' : 'public_chat'; + + // Create the matrix room + const matrixRoom = await intent.createRoom({ + createAsClient: true, + options: { + name: roomName, + topic: roomTopic, + visibility, + preset, + ...this.parametersForDirectMessagesIfNecessary(roomType, externalInviteeId), + // eslint-disable-next-line @typescript-eslint/camelcase + creation_content: { + // eslint-disable-next-line @typescript-eslint/camelcase + was_internally_programatically_created: true, + }, + }, + }); + + return matrixRoom.room_id; + } + + public async sendMessage(externalRoomId: string, externaSenderId: string, text: string): Promise { + await this.bridgeInstance.getIntent(externaSenderId).sendText(externalRoomId, text); + } + + public isUserIdFromTheSameHomeserver(externalUserId: string, domain: string): boolean { + const userDomain = externalUserId.includes(':') ? externalUserId.split(':').pop() : ''; + + return userDomain === domain; + } + + public getInstance(): IFederationBridge { + return this; + } + + private parametersForDirectMessagesIfNecessary = (roomType: RoomType, invitedUserId: string): Record => { + return roomType === RoomType.DIRECT_MESSAGE + ? { + // eslint-disable-next-line @typescript-eslint/camelcase + is_direct: true, + invite: [invitedUserId], + } + : {}; + }; + + private logInfo(): void { + bridgeLogger.info(`Running Federation V2: + id: ${this.appServiceId} + bridgeUrl: ${this.bridgeUrl} + homeserverURL: ${this.homeServerUrl} + homeserverDomain: ${this.homeServerDomain} + `); + } + + private async createInstance(): Promise { + bridgeLogger.info('Performing Dynamic Import of matrix-appservice-bridge'); + + // Dynamic import to prevent Rocket.Chat from loading the module until needed and then handle if that fails + const { Bridge, AppServiceRegistration } = await import('@rocket.chat/forked-matrix-appservice-bridge'); + + this.bridgeInstance = new Bridge({ + homeserverUrl: this.homeServerUrl, + domain: this.homeServerDomain, + registration: AppServiceRegistration.fromObject(this.homeServerRegistrationFile as AppServiceOutput), + disableStores: true, + controller: { + onAliasQuery: (alias, matrixRoomId): void => { + console.log('onAliasQuery', alias, matrixRoomId); + }, + onEvent: async (request /* , context*/): Promise => { + // Get the event + const event = request.getData() as unknown as IMatrixEvent; + this.eventHandler(event); + }, + onLog: async (line, isError): Promise => { + console.log(line, isError); + }, + }, + }); + } +} diff --git a/apps/meteor/app/federation-v2/server/infrastructure/matrix/converters/RoomReceiver.ts b/apps/meteor/app/federation-v2/server/infrastructure/matrix/converters/RoomReceiver.ts new file mode 100644 index 000000000000..9d845516cd75 --- /dev/null +++ b/apps/meteor/app/federation-v2/server/infrastructure/matrix/converters/RoomReceiver.ts @@ -0,0 +1,152 @@ +import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; + +import { + FederationRoomChangeJoinRulesDto, + FederationRoomChangeMembershipDto, + FederationRoomChangeNameDto, + FederationRoomChangeTopicDto, + FederationRoomCreateInputDto, + FederationRoomSendInternalMessageDto, +} from '../../../application/input/RoomReceiverDto'; +import { EVENT_ORIGIN } from '../../../domain/IFederationBridge'; +import { IMatrixEvent } from '../definitions/IMatrixEvent'; +import { AddMemberToRoomMembership } from '../definitions/IMatrixEventContent/IMatrixEventContentAddMemberToRoom'; +import { RoomJoinRules } from '../definitions/IMatrixEventContent/IMatrixEventContentSetRoomJoinRules'; +import { MatrixEventType } from '../definitions/MatrixEventType'; + +export class MatrixRoomReceiverConverter { + public static toRoomCreateDto(externalEvent: IMatrixEvent): FederationRoomCreateInputDto { + return Object.assign(new FederationRoomCreateInputDto(), { + ...MatrixRoomReceiverConverter.getBasicRoomsFields(externalEvent.room_id), + ...MatrixRoomReceiverConverter.tryToGetExternalInfoFromTheRoomState( + externalEvent.invite_room_state || externalEvent.unsigned?.invite_room_state, + ), + externalInviterId: externalEvent.sender, + normalizedInviterId: MatrixRoomReceiverConverter.convertMatrixUserIdFormatToRCFormat(externalEvent.sender), + wasInternallyProgramaticallyCreated: externalEvent.content?.was_internally_programatically_created || false, + }); + } + + public static toChangeRoomMembershipDto( + externalEvent: IMatrixEvent, + ): FederationRoomChangeMembershipDto { + return Object.assign(new FederationRoomChangeMembershipDto(), { + ...MatrixRoomReceiverConverter.getBasicRoomsFields(externalEvent.room_id), + ...MatrixRoomReceiverConverter.tryToGetExternalInfoFromTheRoomState( + externalEvent.invite_room_state || externalEvent.unsigned?.invite_room_state, + externalEvent.content?.is_direct, + ), + externalInviterId: externalEvent.sender, + normalizedInviterId: MatrixRoomReceiverConverter.convertMatrixUserIdFormatToRCFormat(externalEvent.sender), + externalInviteeId: externalEvent.state_key, + normalizedInviteeId: MatrixRoomReceiverConverter.convertMatrixUserIdFormatToRCFormat(externalEvent.state_key), + inviteeUsernameOnly: MatrixRoomReceiverConverter.formatMatrixUserIdToRCUsernameFormat(externalEvent.state_key), + inviterUsernameOnly: MatrixRoomReceiverConverter.formatMatrixUserIdToRCUsernameFormat(externalEvent.sender), + eventOrigin: MatrixRoomReceiverConverter.getEventOrigin(externalEvent.sender, externalEvent.state_key), + leave: externalEvent.content?.membership === AddMemberToRoomMembership.LEAVE, + }); + } + + public static toSendRoomMessageDto(externalEvent: IMatrixEvent): FederationRoomSendInternalMessageDto { + return Object.assign(new FederationRoomSendInternalMessageDto(), { + ...MatrixRoomReceiverConverter.getBasicRoomsFields(externalEvent.room_id), + externalSenderId: externalEvent.sender, + normalizedSenderId: MatrixRoomReceiverConverter.convertMatrixUserIdFormatToRCFormat(externalEvent.sender), + text: externalEvent.content?.body, + }); + } + + public static toRoomChangeJoinRulesDto( + externalEvent: IMatrixEvent, + ): FederationRoomChangeJoinRulesDto { + return Object.assign(new FederationRoomChangeJoinRulesDto(), { + ...MatrixRoomReceiverConverter.getBasicRoomsFields(externalEvent.room_id), + roomType: MatrixRoomReceiverConverter.convertMatrixJoinRuleToRCRoomType(externalEvent.content?.join_rule), + }); + } + + public static toRoomChangeNameDto(externalEvent: IMatrixEvent): FederationRoomChangeNameDto { + return Object.assign(new FederationRoomChangeNameDto(), { + ...MatrixRoomReceiverConverter.getBasicRoomsFields(externalEvent.room_id), + normalizedRoomName: MatrixRoomReceiverConverter.normalizeRoomNameToRCFormat(externalEvent.content?.name), + }); + } + + public static toRoomChangeTopicDto(externalEvent: IMatrixEvent): FederationRoomChangeTopicDto { + return Object.assign(new FederationRoomChangeTopicDto(), { + ...MatrixRoomReceiverConverter.getBasicRoomsFields(externalEvent.room_id), + roomTopic: externalEvent.content?.topic, + }); + } + + private static convertMatrixUserIdFormatToRCFormat(matrixUserId = ''): string { + return matrixUserId.replace('@', ''); + } + + private static convertMatrixRoomIdFormatToRCFormat(matrixRoomId = ''): string { + const prefixedRoomIdOnly = matrixRoomId.split(':')[0]; + const prefix = '!'; + + return prefixedRoomIdOnly?.replace(prefix, ''); + } + + private static normalizeRoomNameToRCFormat(matrixRoomName = ''): string { + return matrixRoomName.replace('@', ''); + } + + private static formatMatrixUserIdToRCUsernameFormat(matrixUserId = ''): string { + return matrixUserId.split(':')[0]?.replace('@', ''); + } + + private static getEventOrigin(inviterId = '', inviteeId = ''): EVENT_ORIGIN { + const fromADifferentServer = + MatrixRoomReceiverConverter.extractServerNameFromMatrixUserId(inviterId) !== + MatrixRoomReceiverConverter.extractServerNameFromMatrixUserId(inviteeId); + + return fromADifferentServer ? EVENT_ORIGIN.REMOTE : EVENT_ORIGIN.LOCAL; + } + + private static extractServerNameFromMatrixUserId(matrixUserId = ''): string { + const splitted = matrixUserId.split(':'); + + return splitted.length > 1 ? splitted[1] : ''; + } + + private static getBasicRoomsFields(externalRoomId: string): Record { + return { + externalRoomId, + normalizedRoomId: MatrixRoomReceiverConverter.convertMatrixRoomIdFormatToRCFormat(externalRoomId), + }; + } + + private static convertMatrixJoinRuleToRCRoomType(matrixJoinRule: RoomJoinRules, matrixRoomIsDirect = false): RoomType { + const mapping: Record = { + [RoomJoinRules.JOIN]: RoomType.CHANNEL, + [RoomJoinRules.INVITE]: RoomType.PRIVATE_GROUP, + }; + const roomType = mapping[matrixJoinRule] || RoomType.CHANNEL; + + return roomType === RoomType.PRIVATE_GROUP && matrixRoomIsDirect ? RoomType.DIRECT_MESSAGE : roomType; + } + + private static tryToGetExternalInfoFromTheRoomState( + roomState: Record[] = [], + matrixRoomIsDirect = false, + ): Record { + if (roomState.length === 0) { + return {}; + } + const externalRoomName = roomState.find((stateEvent: Record) => stateEvent.type === MatrixEventType.ROOM_NAME_CHANGED) + ?.content?.name; + const externalRoomJoinRule = roomState.find( + (stateEvent: Record) => stateEvent.type === MatrixEventType.ROOM_JOIN_RULES_CHANGED, + )?.content?.join_rule; + + return { + ...(externalRoomName ? { externalRoomName } : {}), + ...(externalRoomJoinRule + ? { roomType: MatrixRoomReceiverConverter.convertMatrixJoinRuleToRCRoomType(externalRoomJoinRule, matrixRoomIsDirect) } + : {}), + }; + } +} diff --git a/apps/meteor/app/federation-v2/server/definitions/IMatrixEvent.ts b/apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEvent.ts similarity index 84% rename from apps/meteor/app/federation-v2/server/definitions/IMatrixEvent.ts rename to apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEvent.ts index 7111057ec55e..3470ab6481bc 100644 --- a/apps/meteor/app/federation-v2/server/definitions/IMatrixEvent.ts +++ b/apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEvent.ts @@ -11,6 +11,6 @@ export interface IMatrixEvent { sender: string; state_key: string; type: T; - unsigned: { age: number }; + unsigned: { age: number; invite_room_state: Record[] }; user_id: string; } diff --git a/apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/IMatrixEventContentAddMemberToRoom.ts b/apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentAddMemberToRoom.ts similarity index 100% rename from apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/IMatrixEventContentAddMemberToRoom.ts rename to apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentAddMemberToRoom.ts diff --git a/apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/IMatrixEventContentCreateRoom.ts b/apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentCreateRoom.ts similarity index 64% rename from apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/IMatrixEventContentCreateRoom.ts rename to apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentCreateRoom.ts index 6180c0356200..f9e80f615808 100644 --- a/apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/IMatrixEventContentCreateRoom.ts +++ b/apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentCreateRoom.ts @@ -1,5 +1,5 @@ export interface IMatrixEventContentCreateRoom { creator: string; room_version: string; - was_programatically_created?: boolean; + was_internally_programatically_created?: boolean; } diff --git a/apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/IMatrixEventContentSendMessage.ts b/apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentSendMessage.ts similarity index 100% rename from apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/IMatrixEventContentSendMessage.ts rename to apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentSendMessage.ts diff --git a/apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/IMatrixEventContentSetRoomJoinRules.ts b/apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentSetRoomJoinRules.ts similarity index 61% rename from apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/IMatrixEventContentSetRoomJoinRules.ts rename to apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentSetRoomJoinRules.ts index 920f9bb53777..f97fa09d8aa7 100644 --- a/apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/IMatrixEventContentSetRoomJoinRules.ts +++ b/apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentSetRoomJoinRules.ts @@ -1,8 +1,8 @@ -export enum SetRoomJoinRules { +export enum RoomJoinRules { JOIN = 'public', INVITE = 'invite', } export interface IMatrixEventContentSetRoomJoinRules { - join_rule: SetRoomJoinRules; + join_rule: RoomJoinRules; } diff --git a/apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/IMatrixEventContentSetRoomName.ts b/apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentSetRoomName.ts similarity index 100% rename from apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/IMatrixEventContentSetRoomName.ts rename to apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentSetRoomName.ts diff --git a/apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/IMatrixEventContentSetRoomTopic.ts b/apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentSetRoomTopic.ts similarity index 100% rename from apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/IMatrixEventContentSetRoomTopic.ts rename to apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentSetRoomTopic.ts diff --git a/apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/index.ts b/apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/index.ts similarity index 57% rename from apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/index.ts rename to apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/index.ts index 7615779282e1..3c1d5b52f076 100644 --- a/apps/meteor/app/federation-v2/server/definitions/IMatrixEventContent/index.ts +++ b/apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/index.ts @@ -7,10 +7,10 @@ import { IMatrixEventContentSetRoomName } from './IMatrixEventContentSetRoomName import { IMatrixEventContentSetRoomTopic } from './IMatrixEventContentSetRoomTopic'; export type EventContent = { - [MatrixEventType.CREATE_ROOM]: IMatrixEventContentCreateRoom; - [MatrixEventType.ROOM_MEMBERSHIP]: IMatrixEventContentAddMemberToRoom; - [MatrixEventType.SET_ROOM_JOIN_RULES]: IMatrixEventContentSetRoomJoinRules; - [MatrixEventType.SET_ROOM_NAME]: IMatrixEventContentSetRoomName; - [MatrixEventType.SET_ROOM_TOPIC]: IMatrixEventContentSetRoomTopic; - [MatrixEventType.SEND_MESSAGE]: IMatrixEventContentSendMessage; + [MatrixEventType.ROOM_CREATED]: IMatrixEventContentCreateRoom; + [MatrixEventType.ROOM_MEMBERSHIP_CHANGED]: IMatrixEventContentAddMemberToRoom; + [MatrixEventType.ROOM_JOIN_RULES_CHANGED]: IMatrixEventContentSetRoomJoinRules; + [MatrixEventType.ROOM_NAME_CHANGED]: IMatrixEventContentSetRoomName; + [MatrixEventType.ROOM_TOPIC_CHANGED]: IMatrixEventContentSetRoomTopic; + [MatrixEventType.ROOM_MESSAGE_SENT]: IMatrixEventContentSendMessage; }; diff --git a/apps/meteor/app/federation-v2/server/definitions/MatrixEventType.ts b/apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/MatrixEventType.ts similarity index 51% rename from apps/meteor/app/federation-v2/server/definitions/MatrixEventType.ts rename to apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/MatrixEventType.ts index 14d4f0bb0ecb..bb58a0d71825 100644 --- a/apps/meteor/app/federation-v2/server/definitions/MatrixEventType.ts +++ b/apps/meteor/app/federation-v2/server/infrastructure/matrix/definitions/MatrixEventType.ts @@ -1,12 +1,12 @@ export enum MatrixEventType { - CREATE_ROOM = 'm.room.create', - ROOM_MEMBERSHIP = 'm.room.member', + ROOM_CREATED = 'm.room.create', + ROOM_MEMBERSHIP_CHANGED = 'm.room.member', // SET_ROOM_POWER_LEVELS = 'm.room.power_levels', // SET_ROOM_CANONICAL_ALIAS = 'm.room.canonical_alias', - SET_ROOM_JOIN_RULES = 'm.room.join_rules', + ROOM_JOIN_RULES_CHANGED = 'm.room.join_rules', // SET_ROOM_HISTORY_VISIBILITY = 'm.room.history_visibility', // SET_ROOM_GUEST_ACCESS = 'm.room.guest_access', - SET_ROOM_NAME = 'm.room.name', - SET_ROOM_TOPIC = 'm.room.topic', - SEND_MESSAGE = 'm.room.message', + ROOM_NAME_CHANGED = 'm.room.name', + ROOM_TOPIC_CHANGED = 'm.room.topic', + ROOM_MESSAGE_SENT = 'm.room.message', } diff --git a/apps/meteor/app/federation-v2/server/infrastructure/matrix/handlers/BaseEvent.ts b/apps/meteor/app/federation-v2/server/infrastructure/matrix/handlers/BaseEvent.ts new file mode 100644 index 000000000000..25e179bfae7e --- /dev/null +++ b/apps/meteor/app/federation-v2/server/infrastructure/matrix/handlers/BaseEvent.ts @@ -0,0 +1,16 @@ +import { IMatrixEvent } from '../definitions/IMatrixEvent'; +import { MatrixEventType } from '../definitions/MatrixEventType'; + +export abstract class MatrixBaseEventHandler { + private type: T; + + public abstract handle(externalEvent: IMatrixEvent): Promise; + + protected constructor(type: T) { + this.type = type; + } + + public equals(type: MatrixEventType): boolean { + return this.type === type; + } +} diff --git a/apps/meteor/app/federation-v2/server/infrastructure/matrix/handlers/Room.ts b/apps/meteor/app/federation-v2/server/infrastructure/matrix/handlers/Room.ts new file mode 100644 index 000000000000..47d10be8fb73 --- /dev/null +++ b/apps/meteor/app/federation-v2/server/infrastructure/matrix/handlers/Room.ts @@ -0,0 +1,65 @@ +import { FederationRoomServiceReceiver } from '../../../application/RoomServiceReceiver'; +import { MatrixRoomReceiverConverter } from '../converters/RoomReceiver'; +import { IMatrixEvent } from '../definitions/IMatrixEvent'; +import { MatrixEventType } from '../definitions/MatrixEventType'; +import { MatrixBaseEventHandler } from './BaseEvent'; + +export class MatrixRoomCreatedHandler extends MatrixBaseEventHandler { + constructor(private roomService: FederationRoomServiceReceiver) { + super(MatrixEventType.ROOM_CREATED); + } + + public async handle(externalEvent: IMatrixEvent): Promise { + await this.roomService.createRoom(MatrixRoomReceiverConverter.toRoomCreateDto(externalEvent)); + } +} + +export class MatrixRoomMembershipChangedHandler extends MatrixBaseEventHandler { + constructor(private roomService: FederationRoomServiceReceiver) { + super(MatrixEventType.ROOM_MEMBERSHIP_CHANGED); + } + + public async handle(externalEvent: IMatrixEvent): Promise { + await this.roomService.changeRoomMembership(MatrixRoomReceiverConverter.toChangeRoomMembershipDto(externalEvent)); + } +} + +export class MatrixRoomJoinRulesChangedHandler extends MatrixBaseEventHandler { + constructor(private roomService: FederationRoomServiceReceiver) { + super(MatrixEventType.ROOM_JOIN_RULES_CHANGED); + } + + public async handle(externalEvent: IMatrixEvent): Promise { + await this.roomService.changeJoinRules(MatrixRoomReceiverConverter.toRoomChangeJoinRulesDto(externalEvent)); + } +} + +export class MatrixRoomNameChangedHandler extends MatrixBaseEventHandler { + constructor(private roomService: FederationRoomServiceReceiver) { + super(MatrixEventType.ROOM_NAME_CHANGED); + } + + public async handle(externalEvent: IMatrixEvent): Promise { + await this.roomService.changeRoomName(MatrixRoomReceiverConverter.toRoomChangeNameDto(externalEvent)); + } +} + +export class MatrixRoomTopicChangedHandler extends MatrixBaseEventHandler { + constructor(private roomService: FederationRoomServiceReceiver) { + super(MatrixEventType.ROOM_TOPIC_CHANGED); + } + + public async handle(externalEvent: IMatrixEvent): Promise { + await this.roomService.changeRoomTopic(MatrixRoomReceiverConverter.toRoomChangeTopicDto(externalEvent)); + } +} + +export class MatrixRoomMessageSentHandler extends MatrixBaseEventHandler { + constructor(private roomService: FederationRoomServiceReceiver) { + super(MatrixEventType.ROOM_MESSAGE_SENT); + } + + public async handle(externalEvent: IMatrixEvent): Promise { + await this.roomService.receiveExternalMessage(MatrixRoomReceiverConverter.toSendRoomMessageDto(externalEvent)); + } +} diff --git a/apps/meteor/app/federation-v2/server/infrastructure/matrix/handlers/index.ts b/apps/meteor/app/federation-v2/server/infrastructure/matrix/handlers/index.ts new file mode 100644 index 000000000000..67b361681492 --- /dev/null +++ b/apps/meteor/app/federation-v2/server/infrastructure/matrix/handlers/index.ts @@ -0,0 +1,16 @@ +import { IMatrixEvent } from '../definitions/IMatrixEvent'; +import { MatrixEventType } from '../definitions/MatrixEventType'; +import { MatrixBaseEventHandler } from './BaseEvent'; + +export class MatrixEventsHandler { + // eslint-disable-next-line no-empty-function + constructor(private handlers: MatrixBaseEventHandler[]) {} + + public async handleEvent(event: IMatrixEvent): Promise { + const handler = this.handlers.find((handler) => handler.equals(event.type)); + if (!handler) { + return console.log(`Could not find handler for ${event.type}`, event); + } + return handler?.handle(event); + } +} diff --git a/apps/meteor/app/federation-v2/server/infrastructure/queue/InMemoryQueue.ts b/apps/meteor/app/federation-v2/server/infrastructure/queue/InMemoryQueue.ts new file mode 100644 index 000000000000..fc4ea1106d26 --- /dev/null +++ b/apps/meteor/app/federation-v2/server/infrastructure/queue/InMemoryQueue.ts @@ -0,0 +1,16 @@ +import * as fastq from 'fastq'; + +export class InMemoryQueue { + private instance: any; + + public setHandler(handler: Function, concurrency: number): void { + this.instance = fastq.promise(handler as any, concurrency); + } + + public addToQueue(task: Record): void { + if (!this.instance) { + throw new Error('You need to set the handler first'); + } + this.instance.push(task).catch(console.error); + } +} diff --git a/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Message.ts b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Message.ts new file mode 100644 index 000000000000..3f82f77a6a19 --- /dev/null +++ b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Message.ts @@ -0,0 +1,9 @@ +import { sendMessage } from '../../../../../lib/server'; +import { FederatedRoom } from '../../../domain/FederatedRoom'; +import { FederatedUser } from '../../../domain/FederatedUser'; + +export class RocketChatMessageAdapter { + public async sendMessage(user: FederatedUser, text: string, room: FederatedRoom): Promise { + new Promise((resolve) => resolve(sendMessage(user.internalReference, { msg: text }, room.internalReference))); + } +} diff --git a/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Notification.ts b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Notification.ts new file mode 100644 index 000000000000..8e07d94cf12a --- /dev/null +++ b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Notification.ts @@ -0,0 +1,14 @@ +import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; + +import { api } from '../../../../../../server/sdk/api'; + +export class RocketChatNotificationAdapter { + public notifyWithEphemeralMessage(i18nMessageKey: string, userId: string, roomId: string, language = 'en'): void { + api.broadcast('notify.ephemeralMessage', userId, roomId, { + msg: TAPi18n.__(i18nMessageKey, { + postProcess: 'sprintf', + lng: language, + }), + }); + } +} diff --git a/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Room.ts b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Room.ts new file mode 100644 index 000000000000..44ec3ee5e5ad --- /dev/null +++ b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Room.ts @@ -0,0 +1,103 @@ +import { ICreatedRoom, IRoom } from '@rocket.chat/core-typings'; + +import { MatrixBridgedRoom } from '../../../../../models/server'; +import { FederatedRoom } from '../../../domain/FederatedRoom'; +import { createRoom, addUserToRoom, removeUserFromRoom } from '../../../../../lib/server'; +import { Rooms, Subscriptions } from '../../../../../models/server/raw'; +import { FederatedUser } from '../../../domain/FederatedUser'; + +export class RocketChatRoomAdapter { + public async getFederatedRoomByExternalId(externalRoomId: string): Promise { + const internalBridgedRoomId = MatrixBridgedRoom.getId(externalRoomId); + if (!internalBridgedRoomId) { + return; + } + const room = await Rooms.findOneById(internalBridgedRoomId); + + return this.createFederatedRoomInstance(externalRoomId, room); + } + + public async getFederatedRoomByInternalId(internalRoomId: string): Promise { + const externalRoomId = MatrixBridgedRoom.getMatrixId(internalRoomId); + if (!externalRoomId) { + return; + } + const room = await Rooms.findOneById(internalRoomId); + + return this.createFederatedRoomInstance(externalRoomId, room); + } + + public async getInternalRoomById(internalRoomId: string): Promise { + return Rooms.findOneById(internalRoomId); + } + + public async createFederatedRoom(federatedRoom: FederatedRoom): Promise { + const members = federatedRoom.getMembers(); + const { rid, _id } = createRoom( + federatedRoom.internalReference.t, + federatedRoom.internalReference.name, + federatedRoom.internalReference.u.username as string, + members, + ) as ICreatedRoom; + const roomId = rid || _id; + MatrixBridgedRoom.insert({ rid: roomId, mri: federatedRoom.externalId }); + await Rooms.setAsFederated(roomId); + } + + public async updateFederatedRoomByInternalRoomId(internalRoomId: string, federatedRoom: FederatedRoom): Promise { + MatrixBridgedRoom.upsert({ rid: internalRoomId }, { rid: internalRoomId, mri: federatedRoom.externalId }); + await Rooms.setAsFederated(internalRoomId); + } + + public async addUserToRoom(federatedRoom: FederatedRoom, inviteeUser: FederatedUser, inviterUser?: FederatedUser): Promise { + return new Promise((resolve) => + resolve(addUserToRoom(federatedRoom.internalReference._id, inviteeUser.internalReference, inviterUser?.internalReference) as any), + ); + } + + public async removeUserFromRoom(federatedRoom: FederatedRoom, affectedUser: FederatedUser, byUser: FederatedUser): Promise { + return new Promise((resolve) => + resolve( + removeUserFromRoom(federatedRoom.internalReference._id, affectedUser.internalReference, { + byUser: byUser.internalReference, + }) as any, + ), + ); + } + + public async updateRoomType(federatedRoom: FederatedRoom): Promise { + await Rooms.update({ _id: federatedRoom.internalReference._id }, { $set: { t: federatedRoom.internalReference.t } }); + await Subscriptions.update( + { rid: federatedRoom.internalReference._id }, + { $set: { t: federatedRoom.internalReference.t } }, + { multi: true }, + ); + } + + public async updateRoomName(federatedRoom: FederatedRoom): Promise { + await Rooms.update( + { _id: federatedRoom.internalReference._id }, + { $set: { name: federatedRoom.internalReference.name, fname: federatedRoom.internalReference.fname } }, + ); + await Subscriptions.update( + { rid: federatedRoom.internalReference._id }, + { $set: { name: federatedRoom.internalReference.name, fname: federatedRoom.internalReference.fname } }, + { multi: true }, + ); + } + + public async updateRoomTopic(federatedRoom: FederatedRoom): Promise { + await Rooms.update( + { _id: federatedRoom.internalReference._id }, + { $set: { description: federatedRoom.internalReference.description } }, + ); + } + + private createFederatedRoomInstance(externalRoomId: string, room: IRoom): FederatedRoom { + const federatedRoom = FederatedRoom.build(); + federatedRoom.externalId = externalRoomId; + federatedRoom.internalReference = room; + + return federatedRoom; + } +} diff --git a/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Settings.ts b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Settings.ts new file mode 100644 index 000000000000..71ff9af73461 --- /dev/null +++ b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/Settings.ts @@ -0,0 +1,192 @@ +import yaml from 'js-yaml'; +import { SHA256 } from 'meteor/sha'; + +import { Settings } from '../../../../../models/server/raw'; +import { settings, settingsRegistry } from '../../../../../settings/server'; + +const EVERYTHING_REGEX = '.*'; +const LISTEN_RULES = EVERYTHING_REGEX; + +export class RocketChatSettingsAdapter { + public initialize(): void { + this.addFederationSettings(); + this.watchChangesAndUpdateRegistrationFile(); + } + + public getApplicationServiceId(): string { + return settings.get('Federation_Matrix_id'); + } + + public getApplicationHomeServerToken(): string { + return settings.get('Federation_Matrix_hs_token'); + } + + public getApplicationApplicationServiceToken(): string { + return settings.get('Federation_Matrix_as_token'); + } + + public getBridgeUrl(): string { + return settings.get('Federation_Matrix_bridge_url'); + } + + public getBridgePort(): number { + const [, , port] = this.getBridgeUrl().split(':'); + + return parseInt(port); + } + + public getHomeServerUrl(): string { + return settings.get('Federation_Matrix_homeserver_url'); + } + + public getHomeServerDomain(): string { + return settings.get('Federation_Matrix_homeserver_domain'); + } + + public getBridgeBotUsername(): string { + return settings.get('Federation_Matrix_bridge_localpart'); + } + + public async disableFederation(): Promise { + await Settings.updateValueById('Federation_Matrix_enabled', false); + } + + public onFederationEnabledStatusChanged(callback: Function): void { + settings.watchMultiple( + [ + 'Federation_Matrix_enabled', + 'Federation_Matrix_id', + 'Federation_Matrix_hs_token', + 'Federation_Matrix_as_token', + 'Federation_Matrix_homeserver_url', + 'Federation_Matrix_homeserver_domain', + 'Federation_Matrix_bridge_url', + 'Federation_Matrix_bridge_localpart', + ], + ([enabled]) => callback(enabled), + ); + } + + public generateRegistrationFileObject(): Record { + /* eslint-disable @typescript-eslint/camelcase */ + return { + id: this.getApplicationServiceId(), + hs_token: this.getApplicationHomeServerToken(), + as_token: this.getApplicationApplicationServiceToken(), + url: this.getBridgeUrl(), + sender_localpart: this.getBridgeBotUsername(), + namespaces: { + users: [ + { + exclusive: false, + regex: LISTEN_RULES, + }, + ], + rooms: [ + { + exclusive: false, + regex: LISTEN_RULES, + }, + ], + aliases: [ + { + exclusive: false, + regex: LISTEN_RULES, + }, + ], + }, + }; + /* eslint-enable @typescript-eslint/camelcase */ + } + + private async updateRegistrationFile(): Promise { + await Settings.updateValueById('Federation_Matrix_registration_file', yaml.dump(this.generateRegistrationFileObject())); + } + + private watchChangesAndUpdateRegistrationFile(): void { + settings.watchMultiple( + [ + 'Federation_Matrix_id', + 'Federation_Matrix_hs_token', + 'Federation_Matrix_as_token', + 'Federation_Matrix_homeserver_url', + 'Federation_Matrix_homeserver_domain', + 'Federation_Matrix_bridge_url', + 'Federation_Matrix_bridge_localpart', + ], + this.updateRegistrationFile.bind(this), + ); + } + + private addFederationSettings(): void { + settingsRegistry.addGroup('Federation', function () { + this.section('Matrix Bridge', function () { + this.add('Federation_Matrix_enabled', false, { + readonly: false, + type: 'boolean', + i18nLabel: 'Federation_Matrix_enabled', + i18nDescription: 'Federation_Matrix_enabled_desc', + alert: 'Federation_Matrix_Enabled_Alert', + }); + + const uniqueId = settings.get('uniqueID'); + const hsToken = SHA256(`hs_${uniqueId}`); + const asToken = SHA256(`as_${uniqueId}`); + + this.add('Federation_Matrix_id', `rocketchat_${uniqueId}`, { + readonly: true, + type: 'string', + i18nLabel: 'Federation_Matrix_id', + i18nDescription: 'Federation_Matrix_id_desc', + }); + + this.add('Federation_Matrix_hs_token', hsToken, { + readonly: true, + type: 'string', + i18nLabel: 'Federation_Matrix_hs_token', + i18nDescription: 'Federation_Matrix_hs_token_desc', + }); + + this.add('Federation_Matrix_as_token', asToken, { + readonly: true, + type: 'string', + i18nLabel: 'Federation_Matrix_as_token', + i18nDescription: 'Federation_Matrix_as_token_desc', + }); + + this.add('Federation_Matrix_homeserver_url', 'http://localhost:8008', { + type: 'string', + i18nLabel: 'Federation_Matrix_homeserver_url', + i18nDescription: 'Federation_Matrix_homeserver_url_desc', + alert: 'Federation_Matrix_homeserver_url_alert', + }); + + this.add('Federation_Matrix_homeserver_domain', 'local.rocket.chat', { + type: 'string', + i18nLabel: 'Federation_Matrix_homeserver_domain', + i18nDescription: 'Federation_Matrix_homeserver_domain_desc', + alert: 'Federation_Matrix_homeserver_domain_alert', + }); + + this.add('Federation_Matrix_bridge_url', 'http://host.docker.internal:3300', { + type: 'string', + i18nLabel: 'Federation_Matrix_bridge_url', + i18nDescription: 'Federation_Matrix_bridge_url_desc', + }); + + this.add('Federation_Matrix_bridge_localpart', 'rocket.cat', { + type: 'string', + i18nLabel: 'Federation_Matrix_bridge_localpart', + i18nDescription: 'Federation_Matrix_bridge_localpart_desc', + }); + + this.add('Federation_Matrix_registration_file', '', { + readonly: true, + type: 'code', + i18nLabel: 'Federation_Matrix_registration_file', + i18nDescription: 'Federation_Matrix_registration_file_desc', + }); + }); + }); + } +} diff --git a/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/User.ts b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/User.ts new file mode 100644 index 000000000000..a2f2c26f8e6c --- /dev/null +++ b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/User.ts @@ -0,0 +1,85 @@ +import { IUser } from '@rocket.chat/core-typings'; + +import { MatrixBridgedUser, Users } from '../../../../../models/server'; +import { FederatedUser } from '../../../domain/FederatedUser'; + +export class RocketChatUserAdapter { + public async getFederatedUserByExternalId(externalUserId: string): Promise { + const internalBridgedUserId = MatrixBridgedUser.getId(externalUserId); + if (!internalBridgedUserId) { + return; + } + + const user = await Users.findOneById(internalBridgedUserId); + + return this.createFederatedUserInstance(externalUserId, user); + } + + public async getFederatedUserByInternalId(internalUserId: string): Promise { + const internalBridgedUserId = MatrixBridgedUser.getById(internalUserId); + if (!internalBridgedUserId) { + return; + } + const { uid: userId, mui: externalUserId } = internalBridgedUserId; + const user = await Users.findOneById(userId); + + return this.createFederatedUserInstance(externalUserId, user); + } + + public async getFederatedUserByInternalUsername(username: string): Promise { + const user = await Users.findOneByUsername(username); + if (!user) { + return; + } + const internalBridgedUserId = MatrixBridgedUser.getById(user._id); + if (!internalBridgedUserId) { + return; + } + const { mui: externalUserId } = internalBridgedUserId; + + return this.createFederatedUserInstance(externalUserId, user); + } + + public async getInternalUserById(userId: string): Promise { + return Users.findOneById(userId); + } + + public async createFederatedUser(federatedUser: FederatedUser): Promise { + const existingLocalUser = await Users.findOneByUsername(federatedUser.internalReference.username); + if (existingLocalUser) { + return MatrixBridgedUser.upsert( + { uid: existingLocalUser._id }, + { + uid: existingLocalUser._id, + mui: federatedUser.externalId, + remote: !federatedUser.existsOnlyOnProxyServer, + }, + ); + } + const newLocalUserId = await Users.create({ + username: federatedUser.internalReference.username, + type: federatedUser.internalReference.type, + status: federatedUser.internalReference.status, + active: federatedUser.internalReference.active, + roles: federatedUser.internalReference.roles, + name: federatedUser.internalReference.name, + requirePasswordChange: federatedUser.internalReference.requirePasswordChange, + }); + MatrixBridgedUser.upsert( + { uid: newLocalUserId }, + { + uid: newLocalUserId, + mui: federatedUser.externalId, + remote: !federatedUser.existsOnlyOnProxyServer, + }, + ); + } + + private createFederatedUserInstance(externalUserId: string, user: IUser): FederatedUser { + const federatedUser = FederatedUser.build(); + federatedUser.externalId = externalUserId; + federatedUser.internalReference = user; + + return federatedUser; + } +} diff --git a/apps/meteor/app/federation-v2/server/logger.ts b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/logger.ts similarity index 73% rename from apps/meteor/app/federation-v2/server/logger.ts rename to apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/logger.ts index 0b88f48bfde6..331ed9f5f4b4 100644 --- a/apps/meteor/app/federation-v2/server/logger.ts +++ b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/adapters/logger.ts @@ -1,4 +1,4 @@ -import { Logger } from '../../logger/server'; +import { Logger } from '../../../../../logger/server'; const logger = new Logger('Federation_Matrix'); diff --git a/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/converters/RoomSender.ts b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/converters/RoomSender.ts new file mode 100644 index 000000000000..76d6937f206b --- /dev/null +++ b/apps/meteor/app/federation-v2/server/infrastructure/rocket-chat/converters/RoomSender.ts @@ -0,0 +1,34 @@ +import { IMessage } from '@rocket.chat/core-typings'; + +import { FederationRoomInviteUserDto, FederationRoomSendExternalMessageDto } from '../../../application/input/RoomSenderDto'; + +export class FederationRoomSenderConverter { + public static toRoomInviteUserDto( + internalInviterId: string, + internalRoomId: string, + externalInviteeId: string, + ): FederationRoomInviteUserDto { + const normalizedInviteeId = externalInviteeId.replace('@', ''); + const inviteeUsernameOnly = externalInviteeId.split(':')[0]?.replace('@', ''); + + return Object.assign(new FederationRoomInviteUserDto(), { + internalInviterId, + internalRoomId, + rawInviteeId: externalInviteeId, + normalizedInviteeId, + inviteeUsernameOnly, + }); + } + + public static toSendExternalMessageDto( + internalSenderId: string, + internalRoomId: string, + message: IMessage, + ): FederationRoomSendExternalMessageDto { + return Object.assign(new FederationRoomSendExternalMessageDto(), { + internalRoomId, + internalSenderId, + message, + }); + } +} diff --git a/apps/meteor/app/federation-v2/server/matrix-client/index.ts b/apps/meteor/app/federation-v2/server/matrix-client/index.ts deleted file mode 100644 index 68664d6e9cdf..000000000000 --- a/apps/meteor/app/federation-v2/server/matrix-client/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as message from './message'; -import * as room from './room'; -import * as user from './user'; - -export const matrixClient = { - message, - room, - user, -}; diff --git a/apps/meteor/app/federation-v2/server/matrix-client/message.ts b/apps/meteor/app/federation-v2/server/matrix-client/message.ts deleted file mode 100644 index a6a9d8626632..000000000000 --- a/apps/meteor/app/federation-v2/server/matrix-client/message.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { IMessage } from '@rocket.chat/core-typings'; - -import { MatrixBridgedRoom, MatrixBridgedUser } from '../../../models/server'; -import { matrixBridge } from '../bridge'; - -export const send = async (message: IMessage): Promise => { - // Retrieve the matrix user - const userMatrixId = MatrixBridgedUser.getMatrixId(message.u._id); - - // Retrieve the matrix room - const roomMatrixId = MatrixBridgedRoom.getMatrixId(message.rid); - - if (!userMatrixId) { - throw new Error(`Could not find user matrix id for ${message.u._id}`); - } - - if (!roomMatrixId) { - throw new Error(`Could not find room matrix id for ${message.rid}`); - } - - const intent = matrixBridge.getInstance().getIntent(userMatrixId); - await intent.sendText(roomMatrixId, message.msg || '...not-supported...'); - - return message; -}; diff --git a/apps/meteor/app/federation-v2/server/matrix-client/room.ts b/apps/meteor/app/federation-v2/server/matrix-client/room.ts deleted file mode 100644 index e031de4e4b2b..000000000000 --- a/apps/meteor/app/federation-v2/server/matrix-client/room.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; -import { IRoom, IUser } from '@rocket.chat/core-typings'; - -import { MatrixBridgedRoom, MatrixBridgedUser } from '../../../models/server'; -import { matrixBridge } from '../bridge'; -import { Rooms } from '../../../models/server/raw'; - -interface ICreateRoomResult { - rid: string; - mri: string; -} - -const parametersForDirectMessagesIfNecessary = (room: IRoom, invitedUserId: string): Record => { - return room.t === RoomType.DIRECT_MESSAGE - ? { - // eslint-disable-next-line @typescript-eslint/camelcase - is_direct: true, - invite: [invitedUserId], - } - : {}; -}; - -export const create = async (inviterUser: IUser, room: IRoom, invitedUserId: string): Promise => { - // Check if this room already exists (created by another method) - // and if so, ignore the callback - const roomMatrixId = MatrixBridgedRoom.getMatrixId(room._id); - if (roomMatrixId) { - return { rid: room._id, mri: roomMatrixId }; - } - - // Retrieve the matrix user - const userMatrixId = MatrixBridgedUser.getMatrixId(inviterUser._id); - - if (!userMatrixId) { - throw new Error(`Could not find user matrix id for ${inviterUser._id}`); - } - - const intent = matrixBridge.getInstance().getIntent(userMatrixId); - - const visibility = room.t === 'p' || room.t === 'd' ? 'invite' : 'public'; - const preset = room.t === 'p' || room.t === 'd' ? 'private_chat' : 'public_chat'; - - // Create the matrix room - const matrixRoom = await intent.createRoom({ - createAsClient: true, - options: { - name: room.fname || room.name, - topic: room.topic, - visibility, - preset, - ...parametersForDirectMessagesIfNecessary(room, invitedUserId), - // eslint-disable-next-line @typescript-eslint/camelcase - creation_content: { - // eslint-disable-next-line @typescript-eslint/camelcase - was_programatically_created: true, - }, - }, - }); - // Add to the map - MatrixBridgedRoom.insert({ rid: room._id, mri: matrixRoom.room_id }); - - await Rooms.setAsBridged(room._id); - - // Add our user TODO: Doing this I think is un-needed since our user is the creator of the room. With it in.. there were errors - // await intent.invite(matrixRoom.room_id, userMatrixId); - - return { rid: room._id, mri: matrixRoom.room_id }; -}; diff --git a/apps/meteor/app/federation-v2/server/matrix-client/user.ts b/apps/meteor/app/federation-v2/server/matrix-client/user.ts deleted file mode 100644 index 9e6ba092e9b4..000000000000 --- a/apps/meteor/app/federation-v2/server/matrix-client/user.ts +++ /dev/null @@ -1,174 +0,0 @@ -import type { MatrixProfileInfo } from '@rocket.chat/forked-matrix-bot-sdk'; -import { IUser } from '@rocket.chat/core-typings'; -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; - -import { matrixBridge } from '../bridge'; -import { MatrixBridgedUser, MatrixBridgedRoom, Users } from '../../../models/server'; -import { addUserToRoom } from '../../../lib/server/functions'; -import { matrixClient } from '.'; -import { dataInterface } from '../data-interface'; -import { settings } from '../../../settings/server'; -import { api } from '../../../../server/sdk/api'; - -interface ICreateUserResult { - uid: string; - mui: string; - remote: boolean; -} - -const removeUselessCharsFromMatrixId = (matrixUserId = ''): string => matrixUserId.replace('@', ''); -const formatUserIdAsRCUsername = (userId = ''): string => removeUselessCharsFromMatrixId(userId.split(':')[0]); - -export const invite = async (inviterId: string, roomId: string, invitedId: string): Promise => { - console.log(`[${inviterId}-${invitedId}-${roomId}] Inviting user ${invitedId} to ${roomId}...`); - - // Find the inviter user - let bridgedInviterUser = MatrixBridgedUser.getById(inviterId); - // Get the user - const inviterUser = await dataInterface.user(inviterId); - - // Determine if the user is local or remote - let invitedUserMatrixId = invitedId; - const invitedUserDomain = invitedId.includes(':') ? invitedId.split(':').pop() : ''; - const invitedUserIsRemote = invitedUserDomain && invitedUserDomain !== settings.get('Federation_Matrix_homeserver_domain'); - - // Find the invited user in Rocket.Chats users - // TODO: this should be refactored asap, since these variable value changes lead us to confusion - let invitedUser = Users.findOneByUsername(removeUselessCharsFromMatrixId(invitedId)); - - if (!invitedUser) { - // Create the invited user - const { uid } = await matrixClient.user.createLocal(invitedUserMatrixId); - invitedUser = Users.findOneById(uid); - } - - // The inviters user doesn't yet exist in matrix - if (!bridgedInviterUser) { - console.log(`[${inviterId}-${invitedId}-${roomId}] Creating remote inviter user...`); - - // Create the missing user - bridgedInviterUser = await matrixClient.user.createRemote(inviterUser); - - console.log(`[${inviterId}-${invitedId}-${roomId}] Inviter user created as ${bridgedInviterUser.mui}...`); - } - - // Find the bridged room id - let matrixRoomId = await MatrixBridgedRoom.getMatrixId(roomId); - - // Get the room - const room = await dataInterface.room(roomId); - - if (!matrixRoomId) { - console.log(`[${inviterId}-${invitedId}-${roomId}] Creating remote room...`); - - // Create the missing room - const { mri } = await matrixClient.room.create({ _id: inviterId } as IUser, room, invitedId); - - matrixRoomId = mri; - - console.log(`[${inviterId}-${invitedId}-${roomId}] Remote room created as ${matrixRoomId}...`); - } - - // If the invited user is not remote, let's ensure it exists remotely - if (!invitedUserIsRemote) { - console.log(`[${inviterId}-${invitedId}-${roomId}] Creating remote invited user...`); - - // Check if we already have a matrix id for that user - const existingMatrixId = MatrixBridgedUser.getMatrixId(invitedUser._id); - - if (!existingMatrixId) { - const { mui } = await matrixClient.user.createRemote(invitedUser); - - invitedUserMatrixId = mui; - } else { - invitedUserMatrixId = existingMatrixId; - } - - console.log(`[${inviterId}-${invitedId}-${roomId}] Invited user created as ${invitedUserMatrixId}...`); - } - - console.log(`[${inviterId}-${invitedId}-${roomId}] Inviting the user to the room...`); - // Invite && Auto-join if the user is Rocket.Chat controlled - if (!invitedUserIsRemote) { - // Invite the user to the room - await matrixBridge.getInstance().getIntent(bridgedInviterUser.mui).invite(matrixRoomId, invitedUserMatrixId); - - console.log(`[${inviterId}-${invitedId}-${roomId}] Auto-join room...`); - - await matrixBridge.getInstance().getIntent(invitedUserMatrixId).join(matrixRoomId); - } else if (room.t !== 'd') { - // Invite the user to the room but don't wait as this is dependent on the user accepting the invite because we don't control this user - matrixBridge - .getInstance() - .getIntent(bridgedInviterUser.mui) - .invite(matrixRoomId, invitedUserMatrixId) - .catch(() => { - api.broadcast('notify.ephemeralMessage', inviterId, roomId, { - msg: TAPi18n.__('Federation_Matrix_only_owners_can_invite_users', { - postProcess: 'sprintf', - lng: settings.get('Language') || 'en', - }), - }); - }); - } - - // Add the matrix user to the invited room - addUserToRoom(roomId, invitedUser, inviterUser, false); -}; - -export const createRemote = async (u: IUser): Promise => { - const matrixUserId = `@${u.username?.toLowerCase()}:${settings.get('Federation_Matrix_homeserver_domain')}`; - - console.log(`Creating remote user ${matrixUserId}...`); - - const intent = matrixBridge.getInstance().getIntent(matrixUserId); - - await intent.ensureProfile(u.name); - - await intent.setDisplayName(`${u.username} (${u.name})`); - - const payload = { uid: u._id, mui: matrixUserId, remote: true }; - - MatrixBridgedUser.upsert({ uid: u._id }, payload); - - return payload; -}; - -const createLocalUserIfNotExists = async (userId = '', profileInfo: MatrixProfileInfo = {}): Promise => { - const existingUser = await Users.findOneByUsername(formatUserIdAsRCUsername(userId)); - - if (existingUser) { - return existingUser._id; - } - - return Users.create({ - username: removeUselessCharsFromMatrixId(userId), - type: 'user', - status: 'online', - active: true, - roles: ['user'], - name: profileInfo.displayname, - requirePasswordChange: false, - }); -}; - -export const createLocal = async (matrixUserId: string): Promise => { - console.log(`Creating local user ${matrixUserId}...`); - - const intent = matrixBridge.getInstance().getIntent(matrixUserId); - - let currentProfile: MatrixProfileInfo = {}; - - try { - currentProfile = await intent.getProfileInfo(matrixUserId); - } catch (err) { - // no-op - } - - const uid = await createLocalUserIfNotExists(matrixUserId, currentProfile); - const payload = { uid, mui: matrixUserId, remote: false }; - - MatrixBridgedUser.upsert({ uid }, payload); - - return payload; -}; diff --git a/apps/meteor/app/federation-v2/server/methods/checkBridgedRoomExists.ts b/apps/meteor/app/federation-v2/server/methods/checkBridgedRoomExists.ts deleted file mode 100644 index 7db759e1cc38..000000000000 --- a/apps/meteor/app/federation-v2/server/methods/checkBridgedRoomExists.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { MatrixBridgedRoom } from '../../../models/server'; - -export const checkBridgedRoomExists = async (matrixRoomId: string): Promise => { - const existingRoomId = MatrixBridgedRoom.getId(matrixRoomId); - - return !!existingRoomId; -}; diff --git a/apps/meteor/app/federation-v2/server/queue.ts b/apps/meteor/app/federation-v2/server/queue.ts deleted file mode 100644 index f1f0ea02061e..000000000000 --- a/apps/meteor/app/federation-v2/server/queue.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Create the queue -import { queueAsPromised } from 'fastq'; -import * as fastq from 'fastq'; - -import { IMatrixEvent } from './definitions/IMatrixEvent'; -import { MatrixEventType } from './definitions/MatrixEventType'; -import { eventHandler } from './eventHandler'; - -export const matrixEventQueue: queueAsPromised> = fastq.promise(eventHandler, 1); - -export const addToQueue = (event: IMatrixEvent): void => { - console.log(`Queueing ${event.type}...`); - - // TODO: Handle error - matrixEventQueue.push(event).catch((err) => console.error(err)); -}; diff --git a/apps/meteor/app/federation-v2/server/settings.ts b/apps/meteor/app/federation-v2/server/settings.ts deleted file mode 100644 index f10264a3ea37..000000000000 --- a/apps/meteor/app/federation-v2/server/settings.ts +++ /dev/null @@ -1,136 +0,0 @@ -import yaml from 'js-yaml'; -import { SHA256 } from 'meteor/sha'; - -import { getRegistrationInfo } from './config'; -import { Settings } from '../../models/server/raw'; -import { settings, settingsRegistry } from '../../settings/server'; - -settingsRegistry.addGroup('Federation', function () { - this.section('Matrix Bridge', async function () { - this.add('Federation_Matrix_enabled', false, { - readonly: false, - type: 'boolean', - i18nLabel: 'Federation_Matrix_enabled', - i18nDescription: 'Federation_Matrix_enabled_desc', - alert: 'Federation_Matrix_Enabled_Alert', - }); - - const uniqueId = await settings.get('uniqueID'); - const hsToken = SHA256(`hs_${uniqueId}`); - const asToken = SHA256(`as_${uniqueId}`); - - this.add('Federation_Matrix_id', `rocketchat_${uniqueId}`, { - readonly: true, - type: 'string', - i18nLabel: 'Federation_Matrix_id', - i18nDescription: 'Federation_Matrix_id_desc', - }); - - this.add('Federation_Matrix_hs_token', hsToken, { - readonly: true, - type: 'string', - i18nLabel: 'Federation_Matrix_hs_token', - i18nDescription: 'Federation_Matrix_hs_token_desc', - }); - - this.add('Federation_Matrix_as_token', asToken, { - readonly: true, - type: 'string', - i18nLabel: 'Federation_Matrix_as_token', - i18nDescription: 'Federation_Matrix_as_token_desc', - }); - - this.add('Federation_Matrix_homeserver_url', 'http://localhost:8008', { - type: 'string', - i18nLabel: 'Federation_Matrix_homeserver_url', - i18nDescription: 'Federation_Matrix_homeserver_url_desc', - alert: 'Federation_Matrix_homeserver_url_alert', - }); - - this.add('Federation_Matrix_homeserver_domain', 'local.rocket.chat', { - type: 'string', - i18nLabel: 'Federation_Matrix_homeserver_domain', - i18nDescription: 'Federation_Matrix_homeserver_domain_desc', - alert: 'Federation_Matrix_homeserver_domain_alert', - }); - - this.add('Federation_Matrix_bridge_url', 'http://host.docker.internal:3300', { - type: 'string', - i18nLabel: 'Federation_Matrix_bridge_url', - i18nDescription: 'Federation_Matrix_bridge_url_desc', - }); - - this.add('Federation_Matrix_bridge_localpart', 'rocket.cat', { - type: 'string', - i18nLabel: 'Federation_Matrix_bridge_localpart', - i18nDescription: 'Federation_Matrix_bridge_localpart_desc', - }); - - this.add('Federation_Matrix_registration_file', '', { - readonly: true, - type: 'code', - i18nLabel: 'Federation_Matrix_registration_file', - i18nDescription: 'Federation_Matrix_registration_file_desc', - }); - }); -}); - -let registrationFile = {}; - -const updateRegistrationFile = async function (): Promise { - const registrationInfo = getRegistrationInfo(); - - // eslint-disable-next-line @typescript-eslint/camelcase - const { id, hs_token, as_token, sender_localpart } = registrationInfo; - let { url } = registrationInfo; - - if (!url || !url.includes(':')) { - url = `${url}:3300`; - } - - /* eslint-disable @typescript-eslint/camelcase */ - registrationFile = { - id, - hs_token, - as_token, - url, - sender_localpart, - namespaces: { - users: [ - { - exclusive: false, - regex: '.*', - }, - ], - rooms: [ - { - exclusive: false, - regex: '.*', - }, - ], - aliases: [ - { - exclusive: false, - regex: '.*', - }, - ], - }, - }; - /* eslint-enable @typescript-eslint/camelcase */ - - // Update the registration file - await Settings.updateValueById('Federation_Matrix_registration_file', yaml.dump(registrationFile)); -}; - -settings.watchMultiple( - [ - 'Federation_Matrix_id', - 'Federation_Matrix_hs_token', - 'Federation_Matrix_as_token', - 'Federation_Matrix_homeserver_url', - 'Federation_Matrix_homeserver_domain', - 'Federation_Matrix_bridge_url', - 'Federation_Matrix_bridge_localpart', - ], - updateRegistrationFile, -); diff --git a/apps/meteor/app/federation-v2/server/startup.ts b/apps/meteor/app/federation-v2/server/startup.ts deleted file mode 100644 index a1495878788c..000000000000 --- a/apps/meteor/app/federation-v2/server/startup.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { settings } from '../../settings/server'; -import { matrixBridge } from './bridge'; -import { bridgeLogger, setupLogger } from './logger'; - -const watchChanges = (): void => { - settings.watchMultiple( - [ - 'Federation_Matrix_enabled', - 'Federation_Matrix_id', - 'Federation_Matrix_hs_token', - 'Federation_Matrix_as_token', - 'Federation_Matrix_homeserver_url', - 'Federation_Matrix_homeserver_domain', - 'Federation_Matrix_bridge_url', - 'Federation_Matrix_bridge_localpart', - ], - async ([enabled]) => { - setupLogger.info(`Federation Matrix is ${enabled ? 'enabled' : 'disabled'}`); - if (!enabled) { - await matrixBridge.stop(); - return; - } - await matrixBridge.start(); - }, - ); -}; - -export const startBridge = (): void => { - watchChanges(); - - bridgeLogger.info(`Running Federation V2: - id: ${settings.get('Federation_Matrix_id')} - bridgeUrl: ${settings.get('Federation_Matrix_bridge_url')} - homeserverURL: ${settings.get('Federation_Matrix_homeserver_url')} - homeserverDomain: ${settings.get('Federation_Matrix_homeserver_domain')} - `); -}; diff --git a/apps/meteor/app/lib/client/methods/sendMessage.js b/apps/meteor/app/lib/client/methods/sendMessage.js index 8dfafeeb5f94..8d3d9bd9532e 100644 --- a/apps/meteor/app/lib/client/methods/sendMessage.js +++ b/apps/meteor/app/lib/client/methods/sendMessage.js @@ -32,9 +32,9 @@ Meteor.methods({ message.unread = true; } - // If the room is bridged, send the message to matrix only - const { bridged } = Rooms.findOne({ _id: message.rid }, { fields: { bridged: 1 } }); - if (bridged) { + // If the room is federated, send the message to matrix only + const { federated } = Rooms.findOne({ _id: message.rid }, { fields: { federated: 1 } }); + if (federated) { return; } diff --git a/apps/meteor/app/lib/server/methods/sendMessage.js b/apps/meteor/app/lib/server/methods/sendMessage.js index 91e223322b36..3cf002ba4e0b 100644 --- a/apps/meteor/app/lib/server/methods/sendMessage.js +++ b/apps/meteor/app/lib/server/methods/sendMessage.js @@ -7,13 +7,14 @@ import { hasPermission } from '../../../authorization'; import { metrics } from '../../../metrics'; import { settings } from '../../../settings'; import { messageProperties } from '../../../ui-utils'; -import { Users, Messages, Rooms } from '../../../models'; +import { Users, Messages } from '../../../models'; import { sendMessage } from '../functions'; import { RateLimiter } from '../lib'; import { canSendMessage } from '../../../authorization/server'; import { SystemLogger } from '../../../../server/lib/logger/system'; import { api } from '../../../../server/sdk/api'; -import { matrixClient } from '../../../federation-v2/server/matrix-client'; +import { federationRoomServiceSender } from '../../../federation-v2/server'; +import { FederationRoomSenderConverter } from '../../../federation-v2/server/infrastructure/rocket-chat/converters/RoomSender'; export function executeSendMessage(uid, message) { if (message.tshow && !message.tmid) { @@ -106,10 +107,10 @@ Meteor.methods({ } try { - // If the room is bridged, send the message to matrix only - const { bridged } = Rooms.findOne({ _id: message.rid }, { fields: { bridged: 1 } }); - if (bridged) { - return matrixClient.message.send({ ...message, u: { _id: uid } }); + if (Promise.await(federationRoomServiceSender.isAFederatedRoom(message.rid))) { + return federationRoomServiceSender.sendMessageFromRocketChat( + FederationRoomSenderConverter.toSendExternalMessageDto(uid, message.rid, message), + ); } return executeSendMessage(uid, message); diff --git a/apps/meteor/app/models/server/raw/Rooms.js b/apps/meteor/app/models/server/raw/Rooms.js index 0d86223b42a8..807b3d365aea 100644 --- a/apps/meteor/app/models/server/raw/Rooms.js +++ b/apps/meteor/app/models/server/raw/Rooms.js @@ -461,8 +461,8 @@ export class RoomsRaw extends BaseRaw { ]); } - setAsBridged(roomId) { - return this.updateOne({ _id: roomId }, { $set: { bridged: true } }); + setAsFederated(roomId) { + return this.updateOne({ _id: roomId }, { $set: { federated: true } }); } findByE2E(options) { diff --git a/apps/meteor/app/slashcommands-bridge/server/index.ts b/apps/meteor/app/slashcommands-bridge/server/index.ts index fa1c1e9d2f9f..b9396a596312 100644 --- a/apps/meteor/app/slashcommands-bridge/server/index.ts +++ b/apps/meteor/app/slashcommands-bridge/server/index.ts @@ -2,36 +2,40 @@ import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; import { slashCommands } from '../../utils/lib/slashCommand'; -import { matrixClient } from '../../federation-v2/server/matrix-client'; - -slashCommands.add( - 'bridge', - function Bridge(_command, stringParams, item): void { - if (_command !== 'bridge' || !Match.test(stringParams, String)) { - return; - } - - const [command, ...params] = stringParams.split(' '); - - const { rid: roomId } = item; - - switch (command) { - case 'invite': - // Invite a user - // Example: /bridge invite rc_helena:b.rc.allskar.com - const [userId] = params; - - const currentUserId = Meteor.userId(); - - if (currentUserId) { - Promise.await(matrixClient.user.invite(currentUserId, roomId, `@${userId.replace('@', '')}`)); - } - - break; - } - }, - { - description: 'Invites_an_user_to_a_bridged_room', - params: '#command #user', - }, -); +import { federationRoomServiceSender } from '../../federation-v2/server'; +import { FederationRoomSenderConverter } from '../../federation-v2/server/infrastructure/rocket-chat/converters/RoomSender'; + +function Bridge(_command: 'bridge', stringParams: string | undefined, item: Record): void { + if (_command !== 'bridge' || !Match.test(stringParams, String)) { + return; + } + + const [command, ...params] = stringParams.split(' '); + + const { rid: roomId } = item; + + switch (command) { + case 'invite': + // Invite a user + // Example: /bridge invite rc_helena:b.rc.allskar.com + const [userId] = params; + + const currentUserId = Meteor.userId(); + + if (currentUserId) { + const invitee = `@${userId.replace('@', '')}`; + Promise.await( + federationRoomServiceSender.inviteUserToAFederatedRoom( + FederationRoomSenderConverter.toRoomInviteUserDto(currentUserId, roomId, invitee), + ), + ); + } + + break; + } +} + +slashCommands.add('bridge', Bridge, { + description: 'Invites_an_user_to_a_bridged_room', + params: '#command #user', +}); diff --git a/apps/meteor/app/ui-sidenav/client/roomList.js b/apps/meteor/app/ui-sidenav/client/roomList.js index 902441e0b0fa..3dfdf1189230 100644 --- a/apps/meteor/app/ui-sidenav/client/roomList.js +++ b/apps/meteor/app/ui-sidenav/client/roomList.js @@ -172,7 +172,7 @@ const mergeSubRoom = (subscription) => { departmentId: 1, source: 1, queuedAt: 1, - bridged: 1, + federated: 1, }, }; @@ -214,7 +214,7 @@ const mergeSubRoom = (subscription) => { ts, source, queuedAt, - bridged, + federated, } = room; subscription.lm = subscription.lr ? new Date(Math.max(subscription.lr, lastRoomUpdate)) : lastRoomUpdate; @@ -253,7 +253,7 @@ const mergeSubRoom = (subscription) => { ts, source, queuedAt, - bridged, + federated, }); }; @@ -297,7 +297,7 @@ const mergeRoomSub = (room) => { ts, source, queuedAt, - bridged, + federated, } = room; Subscriptions.update( @@ -338,7 +338,7 @@ const mergeRoomSub = (room) => { ts, source, queuedAt, - bridged, + federated, ...getLowerCaseNames(room, sub.name, sub.fname), }, }, diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 976b71cf6f2d..ddbe4132fac6 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -121,6 +121,7 @@ "@types/rewire": "^2.5.28", "@types/semver": "^7.3.9", "@types/sharp": "^0.30.2", + "@types/sinon": "^10.0.11", "@types/string-strip-html": "^5.0.0", "@types/supertest": "^2.0.11", "@types/toastr": "^2.1.39", @@ -165,6 +166,7 @@ "postcss-url": "^10.1.3", "prettier": "2.6.2", "rewire": "^6.0.0", + "sinon": "^14.0.0", "source-map": "^0.7.3", "stylelint": "^13.13.1", "stylelint-order": "^4.1.0", diff --git a/apps/meteor/server/modules/watchers/publishFields.ts b/apps/meteor/server/modules/watchers/publishFields.ts index bede67ffd03f..6a0ff581ef50 100644 --- a/apps/meteor/server/modules/watchers/publishFields.ts +++ b/apps/meteor/server/modules/watchers/publishFields.ts @@ -105,7 +105,7 @@ export const roomFields = { queuedAt: 1, // Federation fields - bridged: 1, + federated: 1, // fields used by DMs usernames: 1, diff --git a/apps/meteor/server/startup/migrations/index.ts b/apps/meteor/server/startup/migrations/index.ts index 045c08a18f4e..f293f36a4640 100644 --- a/apps/meteor/server/startup/migrations/index.ts +++ b/apps/meteor/server/startup/migrations/index.ts @@ -89,4 +89,5 @@ import './v262'; import './v263'; import './v264'; import './v265'; +import './v266'; import './xrun'; diff --git a/apps/meteor/server/startup/migrations/v266.ts b/apps/meteor/server/startup/migrations/v266.ts new file mode 100644 index 000000000000..a2c016b1c571 --- /dev/null +++ b/apps/meteor/server/startup/migrations/v266.ts @@ -0,0 +1,19 @@ +import { addMigration } from '../../lib/migrations'; +import { Rooms } from '../../../app/models/server/raw'; + +addMigration({ + version: 266, + async up() { + await Rooms.updateMany( + { bridged: true }, + { + $set: { + federated: true, + }, + $unset: { + bridged: 1, + }, + }, + ); + }, +}); diff --git a/apps/meteor/tests/unit/app/federation-v2/unit/application/RoomServiceReceiver.spec.ts b/apps/meteor/tests/unit/app/federation-v2/unit/application/RoomServiceReceiver.spec.ts new file mode 100644 index 000000000000..11050f2681fb --- /dev/null +++ b/apps/meteor/tests/unit/app/federation-v2/unit/application/RoomServiceReceiver.spec.ts @@ -0,0 +1,436 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; + +import '../../../lib/server.mocks'; + +import { FederationRoomServiceReceiver } from '../../../../../../app/federation-v2/server/application/RoomServiceReceiver'; +import { FederatedUser } from '../../../../../../app/federation-v2/server/domain/FederatedUser'; +import { FederatedRoom } from '../../../../../../app/federation-v2/server/domain/FederatedRoom'; +import { EVENT_ORIGIN } from '../../../../../../app/federation-v2/server/domain/IFederationBridge'; + +describe('Federation - Application - FederationRoomServiceReceiver', () => { + let service: FederationRoomServiceReceiver; + const roomAdapter = { + getFederatedRoomByExternalId: sinon.stub(), + createFederatedRoom: sinon.stub(), + removeUserFromRoom: sinon.stub(), + addUserToRoom: sinon.stub(), + updateRoomType: sinon.stub(), + updateRoomName: sinon.stub(), + updateRoomTopic: sinon.stub(), + }; + const userAdapter = { + getFederatedUserByExternalId: sinon.stub(), + createFederatedUser: sinon.stub(), + }; + const messageAdapter = { + sendMessage: sinon.stub(), + }; + const settingsAdapter = { + getHomeServerDomain: sinon.stub(), + }; + const bridge = { + getUserProfileInformation: sinon.stub().resolves({}), + isUserIdFromTheSameHomeserver: sinon.stub(), + joinRoom: sinon.stub(), + }; + + beforeEach(() => { + service = new FederationRoomServiceReceiver( + roomAdapter as any, + userAdapter as any, + messageAdapter as any, + settingsAdapter as any, + bridge as any, + ); + }); + + afterEach(() => { + roomAdapter.getFederatedRoomByExternalId.reset(); + roomAdapter.createFederatedRoom.reset(); + roomAdapter.removeUserFromRoom.reset(); + roomAdapter.addUserToRoom.reset(); + roomAdapter.updateRoomType.reset(); + roomAdapter.updateRoomName.reset(); + roomAdapter.updateRoomTopic.reset(); + userAdapter.getFederatedUserByExternalId.reset(); + userAdapter.createFederatedUser.reset(); + messageAdapter.sendMessage.reset(); + settingsAdapter.getHomeServerDomain.reset(); + bridge.isUserIdFromTheSameHomeserver.reset(); + bridge.joinRoom.reset(); + }); + + describe('#createRoom()', () => { + it('should NOT create users nor room if the room already exists', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + userAdapter.getFederatedUserByExternalId.resolves({} as any); + await service.createRoom({} as any); + + expect(roomAdapter.createFederatedRoom.called).to.be.false; + }); + + it('should NOT create users nor room if the room was created internally and programatically', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + await service.createRoom({ wasInternallyProgramaticallyCreated: true } as any); + + expect(roomAdapter.createFederatedRoom.called).to.be.false; + }); + + it('should NOT create the creator if it already exists', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + userAdapter.getFederatedUserByExternalId.resolves({} as any); + await service.createRoom({} as any); + + expect(userAdapter.createFederatedUser.called).to.be.false; + }); + + it('should create the creator if it does not exists yet', async () => { + const creator = FederatedUser.createInstance('externalInviterId', { + name: 'normalizedInviterId', + username: 'normalizedInviterId', + existsOnlyOnProxyServer: false, + }); + roomAdapter.getFederatedRoomByExternalId.resolves(undefined); + userAdapter.getFederatedUserByExternalId.onCall(0).resolves(undefined); + userAdapter.getFederatedUserByExternalId.onCall(1).resolves(creator); + await service.createRoom({ externalInviterId: 'externalInviterId', normalizedInviterId: 'normalizedInviterId' } as any); + + expect(userAdapter.createFederatedUser.calledWith(creator)).to.be.true; + }); + + it('should create the room if it does not exists yet', async () => { + const creator = FederatedUser.createInstance('externalInviterId', { + name: 'normalizedInviterId', + username: 'normalizedInviterId', + existsOnlyOnProxyServer: false, + }); + roomAdapter.getFederatedRoomByExternalId.resolves(undefined); + userAdapter.getFederatedUserByExternalId.onCall(0).resolves(undefined); + userAdapter.getFederatedUserByExternalId.onCall(1).resolves(creator); + await service.createRoom({ + externalInviterId: 'externalInviterId', + normalizedInviterId: 'normalizedInviterId', + externalRoomId: 'externalRoomId', + normalizedRoomId: 'normalizedRoomId', + externalRoomName: 'externalRoomName', + } as any); + + const room = FederatedRoom.createInstance( + 'externalRoomId', + 'normalizedRoomId', + creator as FederatedUser, + RoomType.CHANNEL, + 'externalRoomName', + ); + expect(roomAdapter.createFederatedRoom.calledWith(room)).to.be.true; + }); + }); + + describe('#changeRoomMembership()', () => { + it('should throw an error if the room does not exists AND event origin is equal to LOCAL', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves(undefined); + try { + await service.changeRoomMembership({ externalRoomId: 'externalRoomId', eventOrigin: EVENT_ORIGIN.LOCAL } as any); + } catch (e: any) { + expect(e.message).to.be.equal('Could not find room with external room id: externalRoomId'); + } + }); + + it('should NOT throw an error if the room already exists AND event origin is equal to LOCAL', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + await service.changeRoomMembership({ externalRoomId: 'externalRoomId', eventOrigin: EVENT_ORIGIN.LOCAL } as any); + + expect(bridge.isUserIdFromTheSameHomeserver.called).to.be.true; + }); + + it('should NOT throw an error if the room already exists AND event origin is equal to REMOTE', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + await service.changeRoomMembership({ externalRoomId: 'externalRoomId', eventOrigin: EVENT_ORIGIN.REMOTE } as any); + + expect(bridge.isUserIdFromTheSameHomeserver.called).to.be.true; + }); + + it('should NOT create the inviter if it already exists', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + userAdapter.getFederatedUserByExternalId.onCall(0).resolves({} as any); + userAdapter.getFederatedUserByExternalId.onCall(1).resolves({} as any); + await service.changeRoomMembership({ externalRoomId: 'externalRoomId', eventOrigin: EVENT_ORIGIN.LOCAL } as any); + + expect(userAdapter.createFederatedUser.called).to.be.false; + }); + + it('should create the inviter if it does not exists', async () => { + const inviter = FederatedUser.createInstance('externalInviterId', { + name: 'normalizedInviterId', + username: 'normalizedInviterId', + existsOnlyOnProxyServer: false, + }); + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + userAdapter.getFederatedUserByExternalId.onCall(0).resolves(undefined); + bridge.isUserIdFromTheSameHomeserver.onCall(0).resolves(false); + await service.changeRoomMembership({ + externalRoomId: 'externalRoomId', + eventOrigin: EVENT_ORIGIN.LOCAL, + externalInviterId: 'externalInviterId', + normalizedInviterId: 'normalizedInviterId', + } as any); + + expect(userAdapter.createFederatedUser.calledWith(inviter)).to.be.true; + }); + + it('should NOT create the invitee if it already exists', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + userAdapter.getFederatedUserByExternalId.onCall(0).resolves({} as any); + userAdapter.getFederatedUserByExternalId.onCall(1).resolves(undefined); + await service.changeRoomMembership({ externalRoomId: 'externalRoomId', eventOrigin: EVENT_ORIGIN.LOCAL } as any); + + expect(userAdapter.createFederatedUser.calledOnce).to.be.true; + }); + + it('should create the invitee if it does not exists', async () => { + const invitee = FederatedUser.createInstance('externalInviteeId', { + name: 'normalizedInviteeId', + username: 'normalizedInviteeId', + existsOnlyOnProxyServer: false, + }); + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + userAdapter.getFederatedUserByExternalId.onCall(0).resolves({} as any); + userAdapter.getFederatedUserByExternalId.onCall(1).resolves(undefined); + bridge.isUserIdFromTheSameHomeserver.onCall(1).resolves(false); + await service.changeRoomMembership({ + externalRoomId: 'externalRoomId', + eventOrigin: EVENT_ORIGIN.LOCAL, + externalInviteeId: 'externalInviteeId', + normalizedInviteeId: 'normalizedInviteeId', + } as any); + + expect(userAdapter.createFederatedUser.calledWith(invitee)).to.be.true; + }); + + it('should create the room if it does not exists yet AND the event origin is REMOTE', async () => { + const inviter = FederatedUser.createInstance('externalInviterId', { + name: 'normalizedInviterId', + username: 'normalizedInviterId', + existsOnlyOnProxyServer: false, + }); + const invitee = inviter; + roomAdapter.getFederatedRoomByExternalId.resolves(undefined); + bridge.isUserIdFromTheSameHomeserver.onCall(1).resolves(false); + userAdapter.getFederatedUserByExternalId.onCall(0).resolves(undefined); + userAdapter.getFederatedUserByExternalId.onCall(1).resolves(undefined); + userAdapter.getFederatedUserByExternalId.onCall(2).resolves(inviter); + userAdapter.getFederatedUserByExternalId.onCall(3).resolves(invitee); + await service.changeRoomMembership({ + externalRoomId: 'externalRoomId', + normalizedRoomId: 'normalizedRoomId', + eventOrigin: EVENT_ORIGIN.REMOTE, + roomType: RoomType.CHANNEL, + externalInviteeId: 'externalInviteeId', + normalizedInviteeId: 'normalizedInviteeId', + } as any); + + const room = FederatedRoom.createInstance('externalRoomId', 'normalizedRoomId', inviter as FederatedUser, RoomType.CHANNEL); + expect(roomAdapter.createFederatedRoom.calledWith(room)).to.be.true; + expect(bridge.joinRoom.calledWith('externalRoomId', 'externalInviteeId')).to.be.true; + }); + + it('should NOT create the room if it already exists yet AND the event origin is REMOTE', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + await service.changeRoomMembership({ + externalRoomId: 'externalRoomId', + normalizedRoomId: 'normalizedRoomId', + eventOrigin: EVENT_ORIGIN.REMOTE, + roomType: RoomType.CHANNEL, + externalInviteeId: 'externalInviteeId', + normalizedInviteeId: 'normalizedInviteeId', + } as any); + + expect(roomAdapter.createFederatedRoom.called).to.be.false; + }); + + it('should NOT create the room if it already exists yet AND the event origin is REMOTE', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + await service.changeRoomMembership({ + externalRoomId: 'externalRoomId', + normalizedRoomId: 'normalizedRoomId', + eventOrigin: EVENT_ORIGIN.LOCAL, + roomType: RoomType.CHANNEL, + externalInviteeId: 'externalInviteeId', + normalizedInviteeId: 'normalizedInviteeId', + } as any); + + expect(roomAdapter.createFederatedRoom.called).to.be.false; + }); + + it('should remove the user from room if its a LEAVE event', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + await service.changeRoomMembership({ + externalRoomId: 'externalRoomId', + normalizedRoomId: 'normalizedRoomId', + eventOrigin: EVENT_ORIGIN.LOCAL, + roomType: RoomType.CHANNEL, + externalInviteeId: 'externalInviteeId', + leave: true, + normalizedInviteeId: 'normalizedInviteeId', + } as any); + + expect(roomAdapter.removeUserFromRoom.called).to.be.true; + expect(roomAdapter.addUserToRoom.called).to.be.false; + }); + + it('should add the user from room if its NOT a LEAVE event', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + await service.changeRoomMembership({ + externalRoomId: 'externalRoomId', + normalizedRoomId: 'normalizedRoomId', + eventOrigin: EVENT_ORIGIN.LOCAL, + roomType: RoomType.CHANNEL, + externalInviteeId: 'externalInviteeId', + leave: false, + normalizedInviteeId: 'normalizedInviteeId', + } as any); + + expect(roomAdapter.removeUserFromRoom.called).to.be.false; + expect(roomAdapter.addUserToRoom.called).to.be.true; + }); + }); + + describe('#receiveExternalMessage()', () => { + it('should NOT send a message if the room does not exists', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves(undefined); + await service.receiveExternalMessage({ + text: 'text', + } as any); + + expect(messageAdapter.sendMessage.called).to.be.false; + }); + + it('should NOT send a message if the sender does not exists', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + userAdapter.getFederatedUserByExternalId.resolves(undefined); + await service.receiveExternalMessage({ + text: 'text', + } as any); + + expect(messageAdapter.sendMessage.called).to.be.false; + }); + + it('should send a message if the room and the sender already exists', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves({} as any); + userAdapter.getFederatedUserByExternalId.resolves({} as any); + await service.receiveExternalMessage({ + text: 'text', + } as any); + + expect(messageAdapter.sendMessage.calledWith({}, 'text', {})).to.be.true; + }); + }); + + describe('#changeJoinRules()', () => { + it('should NOT change the room type if the room does not exists', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves(undefined); + await service.changeJoinRules({ + roomType: RoomType.CHANNEL, + } as any); + + expect(roomAdapter.updateRoomType.called).to.be.false; + }); + + it('should NOT change the room type if it exists and is a direct message', async () => { + const room = FederatedRoom.build(); + room.internalReference = {} as any; + room.internalReference.t = RoomType.DIRECT_MESSAGE; + roomAdapter.getFederatedRoomByExternalId.resolves(room); + await service.changeJoinRules({ + roomType: RoomType.CHANNEL, + } as any); + + expect(roomAdapter.updateRoomType.called).to.be.false; + }); + + it('should change the room type if it exists and is NOT a direct message', async () => { + const room = FederatedRoom.build(); + room.internalReference = {} as any; + room.internalReference.t = RoomType.PRIVATE_GROUP; + roomAdapter.getFederatedRoomByExternalId.resolves(room); + await service.changeJoinRules({ + roomType: RoomType.CHANNEL, + } as any); + room.internalReference.t = RoomType.CHANNEL; + expect(roomAdapter.updateRoomType.calledWith(room)).to.be.true; + }); + }); + + describe('#changeRoomName()', () => { + it('should NOT change the room name if the room does not exists', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves(undefined); + await service.changeRoomName({ + normalizedRoomName: 'normalizedRoomName', + } as any); + + expect(roomAdapter.updateRoomName.called).to.be.false; + }); + + it('should NOT change the room name if it exists and is a direct message', async () => { + const room = FederatedRoom.build(); + room.internalReference = {} as any; + room.internalReference.t = RoomType.DIRECT_MESSAGE; + roomAdapter.getFederatedRoomByExternalId.resolves(room); + await service.changeRoomName({ + normalizedRoomName: 'normalizedRoomName', + } as any); + + expect(roomAdapter.updateRoomName.called).to.be.false; + }); + + it('should change the room name if it exists and is NOT a direct message', async () => { + const room = FederatedRoom.build(); + room.internalReference = {} as any; + room.internalReference.t = RoomType.PRIVATE_GROUP; + roomAdapter.getFederatedRoomByExternalId.resolves(room); + await service.changeRoomName({ + roomnormalizedRoomNameType: 'normalizedRoomName', + } as any); + room.internalReference.name = 'normalizedRoomName'; + room.internalReference.fname = 'normalizedRoomName'; + expect(roomAdapter.updateRoomName.calledWith(room)).to.be.true; + }); + }); + + describe('#changeRoomTopic()', () => { + it('should NOT change the room topic if the room does not exists', async () => { + roomAdapter.getFederatedRoomByExternalId.resolves(undefined); + await service.changeRoomTopic({ + roomTopic: 'roomTopic', + } as any); + + expect(roomAdapter.updateRoomTopic.called).to.be.false; + }); + + it('should NOT change the room topic if it exists and is a direct message', async () => { + const room = FederatedRoom.build(); + room.internalReference = {} as any; + room.internalReference.t = RoomType.DIRECT_MESSAGE; + roomAdapter.getFederatedRoomByExternalId.resolves(room); + await service.changeRoomTopic({ + roomTopic: 'roomTopic', + } as any); + + expect(roomAdapter.updateRoomTopic.called).to.be.false; + }); + + it('should change the room topic if it exists and is NOT a direct message', async () => { + const room = FederatedRoom.build(); + room.internalReference = {} as any; + room.internalReference.t = RoomType.PRIVATE_GROUP; + roomAdapter.getFederatedRoomByExternalId.resolves(room); + await service.changeRoomTopic({ + roomTopic: 'roomTopic', + } as any); + room.internalReference.description = 'roomTopic'; + expect(roomAdapter.updateRoomTopic.calledWith(room)).to.be.true; + }); + }); +}); diff --git a/apps/meteor/tests/unit/app/federation-v2/unit/application/RoomServiceSender.spec.ts b/apps/meteor/tests/unit/app/federation-v2/unit/application/RoomServiceSender.spec.ts new file mode 100644 index 000000000000..8fa7719fb2ad --- /dev/null +++ b/apps/meteor/tests/unit/app/federation-v2/unit/application/RoomServiceSender.spec.ts @@ -0,0 +1,311 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; + +import { FederationRoomServiceSender } from '../../../../../../app/federation-v2/server/application/RoomServiceSender'; +import { FederatedUser } from '../../../../../../app/federation-v2/server/domain/FederatedUser'; +import { FederatedRoom } from '../../../../../../app/federation-v2/server/domain/FederatedRoom'; + +describe('Federation - Application - FederationRoomServiceSender', () => { + let service: FederationRoomServiceSender; + const roomAdapter = { + getFederatedRoomByExternalId: sinon.stub(), + getFederatedRoomByInternalId: sinon.stub(), + createFederatedRoom: sinon.stub(), + updateFederatedRoomByInternalRoomId: sinon.stub(), + removeUserFromRoom: sinon.stub(), + addUserToRoom: sinon.stub(), + getInternalRoomById: sinon.stub(), + }; + const userAdapter = { + getFederatedUserByExternalId: sinon.stub(), + getFederatedUserByInternalId: sinon.stub(), + createFederatedUser: sinon.stub(), + getInternalUserById: sinon.stub(), + getFederatedUserByInternalUsername: sinon.stub(), + }; + const settingsAdapter = { + getHomeServerDomain: sinon.stub(), + }; + const bridge = { + getUserProfileInformation: sinon.stub().resolves({}), + isUserIdFromTheSameHomeserver: sinon.stub(), + sendMessage: sinon.stub(), + createUser: sinon.stub(), + inviteToRoom: sinon.stub().returns(new Promise((resolve) => resolve({}))), + createRoom: sinon.stub(), + joinRoom: sinon.stub(), + }; + const notificationAdapter = {}; + const room = FederatedRoom.build(); + const user = FederatedRoom.build(); + + beforeEach(() => { + service = new FederationRoomServiceSender( + roomAdapter as any, + userAdapter as any, + settingsAdapter as any, + notificationAdapter as any, + bridge as any, + ); + }); + + afterEach(() => { + roomAdapter.getFederatedRoomByExternalId.reset(); + roomAdapter.getFederatedRoomByInternalId.reset(); + roomAdapter.createFederatedRoom.reset(); + roomAdapter.updateFederatedRoomByInternalRoomId.reset(); + roomAdapter.addUserToRoom.reset(); + roomAdapter.getInternalRoomById.reset(); + userAdapter.getFederatedUserByExternalId.reset(); + userAdapter.getFederatedUserByInternalId.reset(); + userAdapter.getInternalUserById.reset(); + userAdapter.createFederatedUser.reset(); + userAdapter.getFederatedUserByInternalUsername.reset(); + settingsAdapter.getHomeServerDomain.reset(); + bridge.isUserIdFromTheSameHomeserver.reset(); + bridge.sendMessage.reset(); + bridge.createUser.reset(); + bridge.createRoom.reset(); + bridge.inviteToRoom.reset(); + bridge.joinRoom.reset(); + }); + + describe('#inviteUserToAFederatedRoom()', () => { + it('should NOT create the inviter user if the user already exists', async () => { + userAdapter.getFederatedUserByInternalId.resolves(user); + userAdapter.getFederatedUserByInternalUsername.resolves(user); + roomAdapter.getFederatedRoomByInternalId.resolves(room); + bridge.inviteToRoom.returns(new Promise((resolve) => resolve({}))); + await service.inviteUserToAFederatedRoom({} as any); + + expect(userAdapter.createFederatedUser.called).to.be.false; + }); + + it('should create the inviter user both externally and internally if it does not exists', async () => { + userAdapter.getFederatedUserByInternalUsername.resolves(user); + userAdapter.getFederatedUserByInternalId.onCall(0).resolves(undefined); + userAdapter.getFederatedUserByInternalId.onCall(1).resolves(user); + userAdapter.getInternalUserById.resolves({ username: 'username', name: 'name' } as any); + settingsAdapter.getHomeServerDomain.returns('domain'); + roomAdapter.getFederatedRoomByInternalId.resolves(room); + bridge.inviteToRoom.returns(new Promise((resolve) => resolve({}))); + bridge.createUser.resolves('externalInviterId'); + await service.inviteUserToAFederatedRoom({ externalInviterId: 'externalInviterId' } as any); + const inviter = FederatedUser.createInstance('externalInviterId', { + name: 'name', + username: 'username', + existsOnlyOnProxyServer: true, + }); + expect(bridge.createUser.calledWith('username', 'name', 'domain')).to.be.true; + expect(userAdapter.createFederatedUser.calledWith(inviter)).to.be.true; + }); + + it('should NOT create the invitee user if the user already exists', async () => { + userAdapter.getFederatedUserByInternalId.resolves(user); + userAdapter.getFederatedUserByInternalUsername.resolves({} as any); + userAdapter.getInternalUserById.resolves({ username: 'username', name: 'name' } as any); + settingsAdapter.getHomeServerDomain.returns('domain'); + roomAdapter.getFederatedRoomByInternalId.resolves(room); + bridge.inviteToRoom.returns(new Promise((resolve) => resolve({}))); + await service.inviteUserToAFederatedRoom({ normalizedInviteeId: 'normalizedInviteeId', rawInviteeId: 'rawInviteeId' } as any); + + expect(userAdapter.createFederatedUser.called).to.be.false; + }); + + it('should create the invitee user internally if it does not exists', async () => { + userAdapter.getFederatedUserByInternalId.resolves(user); + userAdapter.getFederatedUserByInternalUsername.onCall(0).resolves(undefined); + userAdapter.getFederatedUserByInternalUsername.onCall(1).resolves({} as any); + userAdapter.getInternalUserById.resolves({ username: 'username', name: 'name' } as any); + bridge.inviteToRoom.returns(new Promise((resolve) => resolve({}))); + settingsAdapter.getHomeServerDomain.returns('domain'); + roomAdapter.getFederatedRoomByInternalId.resolves(room); + await service.inviteUserToAFederatedRoom({ normalizedInviteeId: 'normalizedInviteeId', rawInviteeId: 'rawInviteeId' } as any); + const invitee = FederatedUser.createInstance('rawInviteeId', { + name: 'normalizedInviteeId', + username: 'normalizedInviteeId', + existsOnlyOnProxyServer: false, + }); + + expect(userAdapter.createFederatedUser.calledWith(invitee)).to.be.true; + }); + + it('should NOT create the room if it already exists', async () => { + userAdapter.getFederatedUserByInternalId.resolves(user); + userAdapter.getFederatedUserByInternalUsername.resolves({} as any); + userAdapter.getInternalUserById.resolves({ username: 'username', name: 'name' } as any); + settingsAdapter.getHomeServerDomain.returns('domain'); + roomAdapter.getFederatedRoomByInternalId.resolves(room); + bridge.inviteToRoom.returns(new Promise((resolve) => resolve({}))); + await service.inviteUserToAFederatedRoom({ normalizedInviteeId: 'normalizedInviteeId', rawInviteeId: 'rawInviteeId' } as any); + + expect(roomAdapter.createFederatedRoom.called).to.be.false; + }); + + it('should create the room both externally and internally if it does not exists', async () => { + userAdapter.getFederatedUserByInternalId.resolves({ externalId: 'externalInviterId' } as any); + userAdapter.getFederatedUserByInternalUsername.resolves({ externalId: 'externalInviteeId' } as any); + roomAdapter.getInternalRoomById.resolves({ _id: 'internalRoomId', t: RoomType.CHANNEL, name: 'roomName', topic: 'topic' } as any); + userAdapter.getInternalUserById.resolves({ username: 'username', name: 'name' } as any); + settingsAdapter.getHomeServerDomain.returns('domain'); + bridge.createUser.resolves('externalInviterId'); + bridge.createRoom.resolves('externalRoomId'); + roomAdapter.getFederatedRoomByInternalId.onCall(0).resolves(undefined); + roomAdapter.getFederatedRoomByInternalId.onCall(1).resolves(room); + bridge.inviteToRoom.returns(new Promise((resolve) => resolve({}))); + await service.inviteUserToAFederatedRoom({ normalizedInviteeId: 'normalizedInviteeId', rawInviteeId: 'rawInviteeId' } as any); + const roomResult = FederatedRoom.createInstance('externalRoomId', 'externalRoomId', user as any, RoomType.CHANNEL, 'roomName'); + + expect(bridge.createRoom.calledWith('externalInviterId', 'externalInviteeId', RoomType.CHANNEL, 'roomName', 'topic')).to.be.true; + expect(roomAdapter.updateFederatedRoomByInternalRoomId.calledWith('internalRoomId', roomResult)).to.be.true; + }); + + it('should create, invite and join the user to the room in the proxy home server if the invitee is from the same homeserver', async () => { + userAdapter.getFederatedUserByInternalId.resolves({ externalId: 'externalInviterId' } as any); + userAdapter.getFederatedUserByInternalUsername.resolves({ + externalId: 'externalInviteeId', + internalReference: { name: 'usernameInvitee' }, + } as any); + roomAdapter.getInternalRoomById.resolves({ _id: 'internalRoomId', t: RoomType.CHANNEL, name: 'roomName', topic: 'topic' } as any); + userAdapter.getInternalUserById.resolves({ username: 'username', name: 'name' } as any); + room.externalId = 'externalRoomId'; + roomAdapter.getFederatedRoomByInternalId.resolves(room); + settingsAdapter.getHomeServerDomain.returns('domain'); + bridge.isUserIdFromTheSameHomeserver.resolves(true); + bridge.inviteToRoom.returns(new Promise((resolve) => resolve({}))); + await service.inviteUserToAFederatedRoom({ + normalizedInviteeId: 'normalizedInviteeId', + rawInviteeId: 'rawInviteeId', + inviteeUsernameOnly: 'inviteeUsernameOnly', + } as any); + + expect(bridge.createUser.calledWith('inviteeUsernameOnly', 'usernameInvitee', 'domain')).to.be.true; + expect(bridge.inviteToRoom.calledWith('externalRoomId', 'externalInviterId', 'externalInviteeId')).to.be.true; + expect(bridge.joinRoom.calledWith('externalRoomId', 'externalInviteeId')).to.be.true; + }); + + it('should invite the user to an external room if the room is NOT direct message(on DMs, they are invited during the creational process)', async () => { + userAdapter.getFederatedUserByInternalId.resolves({ externalId: 'externalInviterId' } as any); + userAdapter.getFederatedUserByInternalUsername.resolves({ + externalId: 'externalInviteeId', + internalReference: { name: 'usernameInvitee' }, + } as any); + roomAdapter.getInternalRoomById.resolves({ + _id: 'internalRoomId', + t: RoomType.DIRECT_MESSAGE, + name: 'roomName', + topic: 'topic', + } as any); + userAdapter.getInternalUserById.resolves({ username: 'username', name: 'name' } as any); + room.externalId = 'externalRoomId'; + roomAdapter.getFederatedRoomByInternalId.resolves(room); + bridge.isUserIdFromTheSameHomeserver.resolves(false); + bridge.inviteToRoom.returns(new Promise((resolve) => resolve({}))); + await service.inviteUserToAFederatedRoom({ + normalizedInviteeId: 'normalizedInviteeId', + rawInviteeId: 'rawInviteeId', + inviteeUsernameOnly: 'inviteeUsernameOnly', + } as any); + + expect(bridge.inviteToRoom.calledWith('externalRoomId', 'externalInviterId', 'externalInviteeId')).to.be.true; + }); + + it('should NOT invite any user externally if the user is not from the same home server AND it was already invited when creating the room', async () => { + userAdapter.getFederatedUserByInternalId.resolves({ externalId: 'externalInviterId' } as any); + userAdapter.getFederatedUserByInternalUsername.resolves({ + externalId: 'externalInviteeId', + internalReference: { name: 'usernameInvitee' }, + } as any); + room.internalReference = {} as any; + room.internalReference.t = RoomType.DIRECT_MESSAGE; + roomAdapter.getFederatedRoomByInternalId.resolves(room); + bridge.isUserIdFromTheSameHomeserver.resolves(false); + bridge.inviteToRoom.returns(new Promise((resolve) => resolve({}))); + await service.inviteUserToAFederatedRoom({ + normalizedInviteeId: 'normalizedInviteeId', + rawInviteeId: 'rawInviteeId', + inviteeUsernameOnly: 'inviteeUsernameOnly', + } as any); + + expect(bridge.inviteToRoom.called).to.be.false; + expect(bridge.createUser.called).to.be.false; + expect(bridge.joinRoom.called).to.be.false; + }); + + it('should always add the user to the internal room', async () => { + userAdapter.getFederatedUserByInternalId.resolves({ externalId: 'externalInviterId' } as any); + userAdapter.getFederatedUserByInternalUsername.resolves({ + externalId: 'externalInviteeId', + internalReference: { name: 'usernameInvitee' }, + } as any); + room.internalReference = {} as any; + room.internalReference.t = RoomType.DIRECT_MESSAGE; + roomAdapter.getFederatedRoomByInternalId.resolves(room); + bridge.isUserIdFromTheSameHomeserver.resolves(false); + bridge.inviteToRoom.returns(new Promise((resolve) => resolve({}))); + await service.inviteUserToAFederatedRoom({ + normalizedInviteeId: 'normalizedInviteeId', + rawInviteeId: 'rawInviteeId', + inviteeUsernameOnly: 'inviteeUsernameOnly', + } as any); + + expect(roomAdapter.addUserToRoom.called).to.be.true; + }); + }); + + describe('#sendMessageFromRocketChat()', () => { + it('should throw an error if the sender does not exists ', async () => { + userAdapter.getFederatedUserByInternalId.resolves(undefined); + try { + await service.sendMessageFromRocketChat({ internalSenderId: 'internalSenderId' } as any); + } catch (e: any) { + expect(e.message).to.be.equal('Could not find user id for internalSenderId'); + } + }); + + it('should throw an error if the room does not exists', async () => { + userAdapter.getFederatedUserByInternalId.resolves({} as any); + roomAdapter.getFederatedRoomByInternalId.resolves(undefined); + try { + await service.sendMessageFromRocketChat({ internalRoomId: 'internalRoomId' } as any); + } catch (e: any) { + expect(e.message).to.be.equal('Could not find room id for internalRoomId'); + } + }); + + it('should send the message through the bridge', async () => { + userAdapter.getFederatedUserByInternalId.resolves({ externalId: 'externalId' } as any); + roomAdapter.getFederatedRoomByInternalId.resolves({ externalId: 'externalId' } as any); + await service.sendMessageFromRocketChat({ message: { msg: 'text' } } as any); + expect(bridge.sendMessage.calledWith('externalId', 'externalId', 'text')).to.be.true; + }); + }); + + describe('#isAFederatedRoom()', () => { + it('should return false if internalRoomId is undefined', async () => { + expect(await service.isAFederatedRoom('')).to.be.false; + }); + + it('should return false if the room does not exist', async () => { + roomAdapter.getFederatedRoomByInternalId.resolves(undefined); + expect(await service.isAFederatedRoom('')).to.be.false; + }); + + it('should return true if the room is NOT federated', async () => { + const room = FederatedRoom.build(); + room.internalReference = {} as any; + room.internalReference.federated = false; + roomAdapter.getFederatedRoomByInternalId.resolves(room); + expect(await service.isAFederatedRoom('internalRoomId')).to.be.false; + }); + + it('should return true if the room is federated', async () => { + const room = FederatedRoom.build(); + room.internalReference = {} as any; + room.internalReference.federated = true; + roomAdapter.getFederatedRoomByInternalId.resolves(room); + expect(await service.isAFederatedRoom('internalRoomId')).to.be.true; + }); + }); +}); diff --git a/apps/meteor/tests/unit/app/federation-v2/unit/domain/FederatedRoom.spec.ts b/apps/meteor/tests/unit/app/federation-v2/unit/domain/FederatedRoom.spec.ts new file mode 100644 index 000000000000..b6d7f0011368 --- /dev/null +++ b/apps/meteor/tests/unit/app/federation-v2/unit/domain/FederatedRoom.spec.ts @@ -0,0 +1,169 @@ +import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; +import { expect } from 'chai'; + +import { FederatedRoom } from '../../../../../../app/federation-v2/server/domain/FederatedRoom'; + +describe('Federation - Domain - FederatedRoom', () => { + const members = [{ internalReference: { id: 'userId' } }, { internalReference: { id: 'userId2' } }] as any; + + describe('#createInstance()', () => { + it('should set the internal room name when it was provided', () => { + const federatedRoom = FederatedRoom.createInstance('!externalId@id', 'externalId', { id: 'userId' } as any, 'p' as any, 'myRoomName'); + expect(federatedRoom.internalReference.name).to.be.equal('myRoomName'); + expect(federatedRoom.internalReference.fname).to.be.equal('myRoomName'); + }); + + it('should generate automatically a room name when it was not provided', () => { + const federatedRoom = FederatedRoom.createInstance('!externalId@id', 'externalId', { id: 'userId' } as any, 'p' as any); + expect(federatedRoom.internalReference.name).to.be.equal('Federation-externalId'); + expect(federatedRoom.internalReference.fname).to.be.equal('Federation-externalId'); + }); + + it('should set the members property when the room is a direct message one', () => { + const federatedRoom = FederatedRoom.createInstance( + '!externalId@id', + 'externalId', + { id: 'userId' } as any, + RoomType.DIRECT_MESSAGE, + '', + members, + ); + expect(federatedRoom.members).to.be.eql(members); + }); + + it('should NOT set the members property when the room is NOT a direct message one', () => { + const federatedRoom = FederatedRoom.createInstance( + '!externalId@id', + 'externalId', + { id: 'userId' } as any, + RoomType.CHANNEL, + '', + members, + ); + expect(federatedRoom.members).to.be.undefined; + }); + + it('should return an instance of FederatedRoom', () => { + const federatedRoom = FederatedRoom.createInstance('!externalId@id', 'externalId', { id: 'userId' } as any, RoomType.CHANNEL); + expect(federatedRoom).to.be.instanceOf(FederatedRoom); + }); + }); + + describe('#isDirectMessage()', () => { + it('should return true if its a direct message room', () => { + const federatedRoom = FederatedRoom.createInstance('!externalId@id', 'externalId', { id: 'userId' } as any, RoomType.DIRECT_MESSAGE); + expect(federatedRoom.isDirectMessage()).to.be.true; + }); + + it('should return false if its NOT a direct message room', () => { + const federatedRoom = FederatedRoom.createInstance('!externalId@id', 'externalId', { id: 'userId' } as any, RoomType.CHANNEL); + expect(federatedRoom.isDirectMessage()).to.be.false; + }); + }); + + describe('#setRoomType()', () => { + it('should set the Room type if its not a direct message room', () => { + const federatedRoom = FederatedRoom.createInstance('!externalId@id', 'externalId', { id: 'userId' } as any, RoomType.PRIVATE_GROUP); + federatedRoom.setRoomType(RoomType.CHANNEL); + expect(federatedRoom.internalReference.t).to.be.equal(RoomType.CHANNEL); + }); + + it('should throw an error when trying to set the room type if its a direct message room', () => { + const federatedRoom = FederatedRoom.createInstance('!externalId@id', 'externalId', { id: 'userId' } as any, RoomType.DIRECT_MESSAGE); + expect(() => federatedRoom.setRoomType(RoomType.CHANNEL)).to.be.throw('Its not possible to change a direct message type'); + }); + }); + + describe('#changeRoomName()', () => { + it('should change the Room name if its not a direct message room', () => { + const federatedRoom = FederatedRoom.createInstance('!externalId@id', 'externalId', { id: 'userId' } as any, RoomType.PRIVATE_GROUP); + federatedRoom.changeRoomName('newName'); + expect(federatedRoom.internalReference.name).to.be.equal('newName'); + expect(federatedRoom.internalReference.fname).to.be.equal('newName'); + }); + + it('should throw an error when trying to change the room name if its a direct message room', () => { + const federatedRoom = FederatedRoom.createInstance('!externalId@id', 'externalId', { id: 'userId' } as any, RoomType.DIRECT_MESSAGE); + expect(() => federatedRoom.changeRoomName('newName')).to.be.throw('Its not possible to change a direct message name'); + }); + }); + + describe('#changeRoomTopic()', () => { + it('should change the Room topic if its not a direct message room', () => { + const federatedRoom = FederatedRoom.createInstance('!externalId@id', 'externalId', { id: 'userId' } as any, RoomType.PRIVATE_GROUP); + federatedRoom.changeRoomTopic('newName'); + expect(federatedRoom.internalReference.description).to.be.equal('newName'); + }); + + it('should throw an error when trying to change the room topic if its a direct message room', () => { + const federatedRoom = FederatedRoom.createInstance('!externalId@id', 'externalId', { id: 'userId' } as any, RoomType.DIRECT_MESSAGE); + expect(() => federatedRoom.changeRoomTopic('newName')).to.be.throw('Its not possible to change a direct message topic'); + }); + }); + + describe('#changeRoomTopic()', () => { + it('should change the Room topic if its not a direct message room', () => { + const federatedRoom = FederatedRoom.createInstance('!externalId@id', 'externalId', { id: 'userId' } as any, RoomType.PRIVATE_GROUP); + federatedRoom.changeRoomTopic('newName'); + expect(federatedRoom.internalReference.description).to.be.equal('newName'); + }); + + it('should throw an error when trying to change the room topic if its a direct message room', () => { + const federatedRoom = FederatedRoom.createInstance('!externalId@id', 'externalId', { id: 'userId' } as any, RoomType.DIRECT_MESSAGE); + expect(() => federatedRoom.changeRoomTopic('newName')).to.be.throw('Its not possible to change a direct message topic'); + }); + }); + + describe('#getMembers()', () => { + it('should return the internalReference members if the room is a direct message', () => { + const federatedRoom = FederatedRoom.createInstance( + '!externalId@id', + 'externalId', + { id: 'userId' } as any, + RoomType.DIRECT_MESSAGE, + '', + members, + ); + expect(federatedRoom.getMembers()).to.be.eql(members.map((user: any) => user.internalReference)); + }); + + it('should return an empty array if the room is not a direct message room', () => { + const federatedRoom = FederatedRoom.createInstance( + '!externalId@id', + 'externalId', + { id: 'userId' } as any, + RoomType.CHANNEL, + '', + members, + ); + expect(federatedRoom.getMembers()).to.be.eql([]); + }); + }); + + describe('#isFederated()', () => { + it('should return true if the room is federated', () => { + const federatedRoom = FederatedRoom.createInstance( + '!externalId@id', + 'externalId', + { id: 'userId' } as any, + RoomType.DIRECT_MESSAGE, + '', + members, + ); + federatedRoom.internalReference.federated = true; + expect(federatedRoom.isFederated()).to.be.true; + }); + + it('should return false if the room is NOT federated', () => { + const federatedRoom = FederatedRoom.createInstance( + '!externalId@id', + 'externalId', + { id: 'userId' } as any, + RoomType.DIRECT_MESSAGE, + '', + members, + ); + expect(federatedRoom.isFederated()).to.be.false; + }); + }); +}); diff --git a/apps/meteor/tests/unit/app/federation-v2/unit/domain/FederatedUser.spec.ts b/apps/meteor/tests/unit/app/federation-v2/unit/domain/FederatedUser.spec.ts new file mode 100644 index 000000000000..386877bbd858 --- /dev/null +++ b/apps/meteor/tests/unit/app/federation-v2/unit/domain/FederatedUser.spec.ts @@ -0,0 +1,43 @@ +import { expect } from 'chai'; + +import { FederatedUser } from '../../../../../../app/federation-v2/server/domain/FederatedUser'; + +describe('Federation - Domain - FederatedUser', () => { + describe('#createInstance()', () => { + it('should set the internal user name when it was provided', () => { + const federatedUser = FederatedUser.createInstance('@marcos:matrix.org', { + name: '', + username: 'username', + existsOnlyOnProxyServer: false, + }); + expect(federatedUser.internalReference.username).to.be.equal('username'); + }); + + it('should set the internal name when it was provided', () => { + const federatedUser = FederatedUser.createInstance('@marcos:matrix.org', { + name: 'name', + username: '', + existsOnlyOnProxyServer: false, + }); + expect(federatedUser.internalReference.name).to.be.equal('name'); + }); + + it('should set the existsOnlyOnProxyServer it was provided', () => { + const federatedUser = FederatedUser.createInstance('@marcos:matrix.org', { + name: '', + username: 'username', + existsOnlyOnProxyServer: true, + }); + expect(federatedUser.existsOnlyOnProxyServer).to.be.true; + }); + + it('should return an instance of FederatedUser', () => { + const federatedUser = FederatedUser.createInstance('@marcos:matrix.org', { + name: '', + username: 'username', + existsOnlyOnProxyServer: false, + }); + expect(federatedUser).to.be.instanceOf(FederatedUser); + }); + }); +}); diff --git a/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/matrix/converters/RoomReceiver.spec.ts b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/matrix/converters/RoomReceiver.spec.ts new file mode 100644 index 000000000000..31a67945fa15 --- /dev/null +++ b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/matrix/converters/RoomReceiver.spec.ts @@ -0,0 +1,328 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { expect } from 'chai'; +import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; + +import { MatrixRoomReceiverConverter } from '../../../../../../../../app/federation-v2/server/infrastructure/matrix/converters/RoomReceiver'; +import { + FederationRoomCreateInputDto, + FederationRoomChangeMembershipDto, + FederationRoomSendInternalMessageDto, + FederationRoomChangeJoinRulesDto, + FederationRoomChangeNameDto, + FederationRoomChangeTopicDto, +} from '../../../../../../../../app/federation-v2/server/application/input/RoomReceiverDto'; +import { MatrixEventType } from '../../../../../../../../app/federation-v2/server/infrastructure/matrix/definitions/MatrixEventType'; +import { RoomJoinRules } from '../../../../../../../../app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentSetRoomJoinRules'; +import { AddMemberToRoomMembership } from '../../../../../../../../app/federation-v2/server/infrastructure/matrix/definitions/IMatrixEventContent/IMatrixEventContentAddMemberToRoom'; +import { EVENT_ORIGIN } from '../../../../../../../../app/federation-v2/server/domain/IFederationBridge'; + +describe('Federation - Infrastructure - Matrix - MatrixRoomReceiverConverter', () => { + describe('#toRoomCreateDto()', () => { + const event = { + content: { was_internally_programatically_created: true, name: 'roomName' }, + room_id: '!roomId:matrix.org', + sender: '@marcos.defendi:matrix.org', + }; + + it('should return an instance of FederationRoomCreateInputDto', () => { + expect(MatrixRoomReceiverConverter.toRoomCreateDto({} as any)).to.be.instanceOf(FederationRoomCreateInputDto); + }); + + it('should return the basic room properties correctly (normalizedRoomId without any "!" and only the part before the ":") if any', () => { + const result = MatrixRoomReceiverConverter.toRoomCreateDto({ room_id: event.room_id } as any); + expect(result.externalRoomId).to.be.equal('!roomId:matrix.org'); + expect(result.normalizedRoomId).to.be.equal('roomId'); + }); + + it('should return the external room name and room type when the room state is present on the event and it has the correct events', () => { + const state = [ + { type: MatrixEventType.ROOM_NAME_CHANGED, content: { name: event.content.name } }, + { type: MatrixEventType.ROOM_JOIN_RULES_CHANGED, content: { join_rule: RoomJoinRules.JOIN } }, + ]; + const result = MatrixRoomReceiverConverter.toRoomCreateDto({ unsigned: { invite_room_state: state } } as any); + expect(result.externalRoomName).to.be.equal(event.content.name); + expect(result.roomType).to.be.equal(RoomType.CHANNEL); + }); + + it('should convert to the expected (private) room type when the join rule is equal to INVITE', () => { + const state = [ + { type: MatrixEventType.ROOM_NAME_CHANGED, content: { name: event.content.name } }, + { type: MatrixEventType.ROOM_JOIN_RULES_CHANGED, content: { join_rule: RoomJoinRules.INVITE } }, + ]; + const result = MatrixRoomReceiverConverter.toRoomCreateDto({ unsigned: { invite_room_state: state } } as any); + expect(result.externalRoomName).to.be.equal(event.content.name); + expect(result.roomType).to.be.equal(RoomType.PRIVATE_GROUP); + }); + + it('should convert to the expected (channel) room type when the join rule is equal to JOIN', () => { + const state = [{ type: MatrixEventType.ROOM_JOIN_RULES_CHANGED, content: { join_rule: RoomJoinRules.JOIN } }]; + const result = MatrixRoomReceiverConverter.toRoomCreateDto({ invite_room_state: state } as any); + expect(result.roomType).to.be.equal(RoomType.CHANNEL); + }); + + it('should convert the inviter id to the a rc-format like (without any @ in it)', () => { + const result = MatrixRoomReceiverConverter.toRoomCreateDto({ sender: event.sender } as any); + expect(result.normalizedInviterId).to.be.equal('marcos.defendi:matrix.org'); + }); + + it('should set wasInternallyProgramaticallyCreated accordingly to the event', () => { + const result = MatrixRoomReceiverConverter.toRoomCreateDto({ content: event.content } as any); + expect(result.wasInternallyProgramaticallyCreated).to.be.true; + }); + + it('should convert the event properly', () => { + const result = MatrixRoomReceiverConverter.toRoomCreateDto(event as any); + expect(result).to.be.eql({ + externalRoomId: '!roomId:matrix.org', + normalizedRoomId: 'roomId', + externalInviterId: '@marcos.defendi:matrix.org', + normalizedInviterId: 'marcos.defendi:matrix.org', + wasInternallyProgramaticallyCreated: true, + }); + }); + }); + + describe('#toChangeRoomMembershipDto()', () => { + const event = { + content: { name: 'roomName' }, + room_id: '!roomId:matrix.org', + sender: '@marcos.defendi:matrix.org', + state_key: '@marcos.defendi2:matrix.org', + }; + + it('should return an instance of FederationRoomChangeMembershipDto', () => { + expect(MatrixRoomReceiverConverter.toChangeRoomMembershipDto({} as any)).to.be.instanceOf(FederationRoomChangeMembershipDto); + }); + + it('should return the basic room properties correctly (normalizedRoomId without any "!" and only the part before the ":") if any', () => { + const result = MatrixRoomReceiverConverter.toChangeRoomMembershipDto({ room_id: event.room_id } as any); + expect(result.externalRoomId).to.be.equal('!roomId:matrix.org'); + expect(result.normalizedRoomId).to.be.equal('roomId'); + }); + + it('should return the external room name and room type when the room state is present on the event and it has the correct events', () => { + const state = [ + { type: MatrixEventType.ROOM_NAME_CHANGED, content: { name: event.content.name } }, + { type: MatrixEventType.ROOM_JOIN_RULES_CHANGED, content: { join_rule: RoomJoinRules.JOIN } }, + ]; + const result = MatrixRoomReceiverConverter.toChangeRoomMembershipDto({ unsigned: { invite_room_state: state } } as any); + expect(result.externalRoomName).to.be.equal(event.content.name); + expect(result.roomType).to.be.equal(RoomType.CHANNEL); + }); + + it('should convert to the expected (private) room type when the join rule is equal to INVITE', () => { + const state = [ + { type: MatrixEventType.ROOM_NAME_CHANGED, content: { name: event.content.name } }, + { type: MatrixEventType.ROOM_JOIN_RULES_CHANGED, content: { join_rule: RoomJoinRules.INVITE } }, + ]; + const result = MatrixRoomReceiverConverter.toChangeRoomMembershipDto({ unsigned: { invite_room_state: state } } as any); + expect(result.externalRoomName).to.be.equal(event.content.name); + expect(result.roomType).to.be.equal(RoomType.PRIVATE_GROUP); + }); + + it('should convert to the expected (channel) room type when the join rule is equal to JOIN', () => { + const state = [{ type: MatrixEventType.ROOM_JOIN_RULES_CHANGED, content: { join_rule: RoomJoinRules.JOIN } }]; + const result = MatrixRoomReceiverConverter.toChangeRoomMembershipDto({ invite_room_state: state } as any); + expect(result.roomType).to.be.equal(RoomType.CHANNEL); + }); + + it('should convert to the expected (direct) room type when the join rule is equal to INVITE and its a direct message', () => { + const state = [{ type: MatrixEventType.ROOM_JOIN_RULES_CHANGED, content: { join_rule: RoomJoinRules.INVITE } }]; + const result = MatrixRoomReceiverConverter.toChangeRoomMembershipDto({ + invite_room_state: state, + content: { is_direct: true }, + } as any); + expect(result.roomType).to.be.equal(RoomType.DIRECT_MESSAGE); + }); + + it('should convert the inviter id to the a rc-format like (without any @ in it)', () => { + const result = MatrixRoomReceiverConverter.toChangeRoomMembershipDto({ sender: event.sender } as any); + expect(result.normalizedInviterId).to.be.equal('marcos.defendi:matrix.org'); + }); + + it('should convert the invitee id to the a rc-format like (without any @ in it)', () => { + const result = MatrixRoomReceiverConverter.toChangeRoomMembershipDto({ state_key: event.sender } as any); + expect(result.normalizedInviteeId).to.be.equal('marcos.defendi:matrix.org'); + }); + + it('should convert the inviter id to the a rc-format username like (without any @ in it and just the part before the ":")', () => { + const result = MatrixRoomReceiverConverter.toChangeRoomMembershipDto({ sender: event.sender } as any); + expect(result.inviterUsernameOnly).to.be.equal('marcos.defendi'); + }); + + it('should convert the invitee id to the a rc-format username like (without any @ in it and just the part before the ":")', () => { + const result = MatrixRoomReceiverConverter.toChangeRoomMembershipDto({ state_key: event.sender } as any); + expect(result.inviteeUsernameOnly).to.be.equal('marcos.defendi'); + }); + + it('should set leave to true if its a LEAVE event', () => { + const result = MatrixRoomReceiverConverter.toChangeRoomMembershipDto({ + content: { membership: AddMemberToRoomMembership.LEAVE }, + } as any); + expect(result.leave).to.be.true; + }); + + it('should set leave to false if its NOT a LEAVE event', () => { + const result = MatrixRoomReceiverConverter.toChangeRoomMembershipDto({ + content: { membership: AddMemberToRoomMembership.JOIN }, + } as any); + expect(result.leave).to.be.false; + }); + + it('should set the event origin as REMOTE if the users are from different home servers', () => { + const result = MatrixRoomReceiverConverter.toChangeRoomMembershipDto({ sender: 'a:matrix.org', state_key: 'a:matrix2.org' } as any); + expect(result.eventOrigin).to.be.equal(EVENT_ORIGIN.REMOTE); + }); + + it('should set the event origin as LOCAL if the users are from different home servers', () => { + const result = MatrixRoomReceiverConverter.toChangeRoomMembershipDto({ sender: 'a:matrix.org', state_key: 'a:matrix.org' } as any); + expect(result.eventOrigin).to.be.equal(EVENT_ORIGIN.LOCAL); + }); + + it('should convert the event properly', () => { + const result = MatrixRoomReceiverConverter.toChangeRoomMembershipDto(event as any); + expect(result).to.be.eql({ + externalRoomId: '!roomId:matrix.org', + normalizedRoomId: 'roomId', + externalInviterId: '@marcos.defendi:matrix.org', + normalizedInviterId: 'marcos.defendi:matrix.org', + externalInviteeId: '@marcos.defendi2:matrix.org', + normalizedInviteeId: 'marcos.defendi2:matrix.org', + inviteeUsernameOnly: 'marcos.defendi2', + inviterUsernameOnly: 'marcos.defendi', + eventOrigin: EVENT_ORIGIN.LOCAL, + leave: false, + }); + }); + }); + + describe('#toSendRoomMessageDto()', () => { + const event = { + content: { body: 'msg' }, + room_id: '!roomId:matrix.org', + sender: '@marcos.defendi:matrix.org', + }; + + it('should return an instance of FederationRoomSendInternalMessageDto', () => { + expect(MatrixRoomReceiverConverter.toSendRoomMessageDto({} as any)).to.be.instanceOf(FederationRoomSendInternalMessageDto); + }); + + it('should return the basic room properties correctly (normalizedRoomId without any "!" and only the part before the ":") if any', () => { + const result = MatrixRoomReceiverConverter.toSendRoomMessageDto({ room_id: event.room_id } as any); + expect(result.externalRoomId).to.be.equal('!roomId:matrix.org'); + expect(result.normalizedRoomId).to.be.equal('roomId'); + }); + + it('should convert the sender id to the a rc-format like (without any @ in it)', () => { + const result = MatrixRoomReceiverConverter.toSendRoomMessageDto({ sender: event.sender } as any); + expect(result.normalizedSenderId).to.be.equal('marcos.defendi:matrix.org'); + }); + + it('should convert the event properly', () => { + const result = MatrixRoomReceiverConverter.toSendRoomMessageDto(event as any); + expect(result).to.be.eql({ + externalRoomId: '!roomId:matrix.org', + normalizedRoomId: 'roomId', + externalSenderId: '@marcos.defendi:matrix.org', + normalizedSenderId: 'marcos.defendi:matrix.org', + text: 'msg', + }); + }); + }); + + describe('#toRoomChangeJoinRulesDto()', () => { + const event = { + content: { join_rule: RoomJoinRules.JOIN }, + room_id: '!roomId:matrix.org', + sender: '@marcos.defendi:matrix.org', + }; + + it('should return an instance of FederationRoomChangeJoinRulesDto', () => { + expect(MatrixRoomReceiverConverter.toRoomChangeJoinRulesDto({} as any)).to.be.instanceOf(FederationRoomChangeJoinRulesDto); + }); + + it('should return the basic room properties correctly (normalizedRoomId without any "!" and only the part before the ":") if any', () => { + const result = MatrixRoomReceiverConverter.toRoomChangeJoinRulesDto({ room_id: event.room_id } as any); + expect(result.externalRoomId).to.be.equal('!roomId:matrix.org'); + expect(result.normalizedRoomId).to.be.equal('roomId'); + }); + + it('should convert to the expected (private) room type when the join rule is equal to INVITE', () => { + const result = MatrixRoomReceiverConverter.toRoomChangeJoinRulesDto({ content: { join_rule: RoomJoinRules.INVITE } } as any); + expect(result.roomType).to.be.equal(RoomType.PRIVATE_GROUP); + }); + + it('should convert to the expected (channel) room type when the join rule is equal to JOIN', () => { + const result = MatrixRoomReceiverConverter.toRoomChangeJoinRulesDto({ content: { join_rule: RoomJoinRules.JOIN } } as any); + expect(result.roomType).to.be.equal(RoomType.CHANNEL); + }); + + it('should convert the event properly', () => { + const result = MatrixRoomReceiverConverter.toRoomChangeJoinRulesDto(event as any); + expect(result).to.be.eql({ + externalRoomId: '!roomId:matrix.org', + normalizedRoomId: 'roomId', + roomType: RoomType.CHANNEL, + }); + }); + }); + + describe('#toRoomChangeNameDto()', () => { + const event = { + content: { name: '@roomName' }, + room_id: '!roomId:matrix.org', + sender: '@marcos.defendi:matrix.org', + }; + + it('should return an instance of toRoomChangeNameDto', () => { + expect(MatrixRoomReceiverConverter.toRoomChangeNameDto({} as any)).to.be.instanceOf(FederationRoomChangeNameDto); + }); + + it('should return the basic room properties correctly (normalizedRoomId without any "!" and only the part before the ":") if any', () => { + const result = MatrixRoomReceiverConverter.toRoomChangeNameDto({ room_id: event.room_id } as any); + expect(result.externalRoomId).to.be.equal('!roomId:matrix.org'); + expect(result.normalizedRoomId).to.be.equal('roomId'); + }); + + it('should convert the roomName to a normalized version without starting with @', () => { + const result = MatrixRoomReceiverConverter.toRoomChangeNameDto({ content: event.content } as any); + expect(result.normalizedRoomName).to.be.equal('roomName'); + }); + + it('should convert the event properly', () => { + const result = MatrixRoomReceiverConverter.toRoomChangeNameDto(event as any); + expect(result).to.be.eql({ + externalRoomId: '!roomId:matrix.org', + normalizedRoomId: 'roomId', + normalizedRoomName: 'roomName', + }); + }); + }); + + describe('#toRoomChangeTopicDto()', () => { + const event = { + content: { topic: 'room topic' }, + room_id: '!roomId:matrix.org', + sender: '@marcos.defendi:matrix.org', + }; + + it('should return an instance of FederationRoomChangeTopicDto', () => { + expect(MatrixRoomReceiverConverter.toRoomChangeTopicDto({} as any)).to.be.instanceOf(FederationRoomChangeTopicDto); + }); + + it('should return the basic room properties correctly (normalizedRoomId without any "!" and only the part before the ":") if any', () => { + const result = MatrixRoomReceiverConverter.toRoomChangeTopicDto({ room_id: event.room_id } as any); + expect(result.externalRoomId).to.be.equal('!roomId:matrix.org'); + expect(result.normalizedRoomId).to.be.equal('roomId'); + }); + + it('should convert the event properly', () => { + const result = MatrixRoomReceiverConverter.toRoomChangeTopicDto(event as any); + expect(result).to.be.eql({ + externalRoomId: '!roomId:matrix.org', + normalizedRoomId: 'roomId', + roomTopic: 'room topic', + }); + }); + }); +}); diff --git a/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/matrix/handlers/BaseEvent.spec.ts b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/matrix/handlers/BaseEvent.spec.ts new file mode 100644 index 000000000000..a8ff82b762f2 --- /dev/null +++ b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/matrix/handlers/BaseEvent.spec.ts @@ -0,0 +1,45 @@ +import { expect, spy } from 'chai'; + +import { MatrixBaseEventHandler } from '../../../../../../../../app/federation-v2/server/infrastructure/matrix/handlers/BaseEvent'; + +describe('Federation - Infrastructure - Matrix - MatrixBaseEventHandler', () => { + describe('#equals()', () => { + class MyHandler extends MatrixBaseEventHandler { + public constructor(type: any) { + super(type); + } + + public handle(): Promise { + throw new Error('Method not implemented.'); + } + } + const myHandler = new MyHandler('type' as any); + + it('should return true if the type is equals to the provided one', () => { + expect(myHandler.equals('type' as any)).to.be.true; + }); + + it('should return false if the type is different to the provided one', () => { + expect(myHandler.equals('different' as any)).to.be.false; + }); + }); + + describe('#handle()', () => { + const spyFn = spy(); + class MyHandler extends MatrixBaseEventHandler { + public constructor(type: any) { + super(type); + } + + public async handle(): Promise { + spyFn(); + } + } + const myHandler = new MyHandler('type' as any); + + it('should call the handler fn in the implementated class', () => { + myHandler.handle(); + expect(spyFn).to.be.called; + }); + }); +}); diff --git a/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/matrix/handlers/MatrixEventsHandler.spec.ts b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/matrix/handlers/MatrixEventsHandler.spec.ts new file mode 100644 index 000000000000..bbdde4978286 --- /dev/null +++ b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/matrix/handlers/MatrixEventsHandler.spec.ts @@ -0,0 +1,25 @@ +import { expect, spy } from 'chai'; + +import { MatrixEventsHandler } from '../../../../../../../../app/federation-v2/server/infrastructure/matrix/handlers'; + +describe('Federation - Infrastructure - Matrix - MatrixEventsHandler', () => { + describe('#handleEvent()', () => { + const spyFn = spy(); + const myHandler = new MatrixEventsHandler([ + { + equals: (eventType: string): boolean => eventType === 'eventType', + handle: spyFn, + }, + ] as any); + + it('should call the handler fn properly', async () => { + await myHandler.handleEvent({ type: 'eventType' } as any); + expect(spyFn).to.have.been.called.with({ type: 'eventType' }); + }); + + it('should NOT call the handler if there is no handler for the event', async () => { + await myHandler.handleEvent({ type: 'eventType2' } as any); + expect(spyFn).to.not.be.called; + }); + }); +}); diff --git a/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/queue/InMemoryQueue.spec.ts b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/queue/InMemoryQueue.spec.ts new file mode 100644 index 000000000000..a79437b3494d --- /dev/null +++ b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/queue/InMemoryQueue.spec.ts @@ -0,0 +1,28 @@ +import { expect, spy } from 'chai'; +import mock from 'mock-require'; + +import { InMemoryQueue } from '../../../../../../../app/federation-v2/server/infrastructure/queue/InMemoryQueue'; + +mock('fastq', { + promise: (handler: Function) => ({ + push: async (task: any): Promise => handler(task), + }), +}); + +describe('Federation - Infrastructure - Queue - InMemoryQueue', () => { + const queue = new InMemoryQueue(); + + describe('#addToQueue()', () => { + it('should throw an error if the instance was not set beforehand', () => { + expect(() => queue.addToQueue({})).to.throw('You need to set the handler first'); + }); + + it('should push the task to the queue instance to be handled when the instance was properly defined', () => { + const spiedCb = spy(); + const concurrency = 1; + queue.setHandler(spiedCb, concurrency); + queue.addToQueue({ task: 'my-task' }); + expect(spiedCb).to.have.been.called.with({ task: 'my-task' }); + }); + }); +}); diff --git a/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/adapters/Room.spec.ts b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/adapters/Room.spec.ts new file mode 100644 index 000000000000..19edac2efe00 --- /dev/null +++ b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/adapters/Room.spec.ts @@ -0,0 +1,3 @@ +// describe('Federation - Infrastructure - RocketChat - RocketChatRoomAdapter', () => { + +// }); diff --git a/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/adapters/Settings.spec.ts b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/adapters/Settings.spec.ts new file mode 100644 index 000000000000..f5d7859a0c1c --- /dev/null +++ b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/adapters/Settings.spec.ts @@ -0,0 +1,3 @@ +// describe('Federation - Infrastructure - RocketChat - RocketChatSettingsAdapter', () => { + +// }); diff --git a/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/adapters/User.spec.ts b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/adapters/User.spec.ts new file mode 100644 index 000000000000..29bf5f30b0aa --- /dev/null +++ b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/adapters/User.spec.ts @@ -0,0 +1,5 @@ +// // import { expect } from 'chai'; + +// describe('Federation - Infrastructure - RocketChat - RocketChatUserAdapter', () => { +// +// }); diff --git a/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/converters/RoomSender.spec.ts b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/converters/RoomSender.spec.ts new file mode 100644 index 000000000000..575f51ec3a92 --- /dev/null +++ b/apps/meteor/tests/unit/app/federation-v2/unit/infrastructure/rocket-chat/converters/RoomSender.spec.ts @@ -0,0 +1,72 @@ +import { expect } from 'chai'; +import { IMessage } from '@rocket.chat/core-typings'; + +import { FederationRoomSenderConverter } from '../../../../../../../../app/federation-v2/server/infrastructure/rocket-chat/converters/RoomSender'; +import { + FederationRoomInviteUserDto, + FederationRoomSendExternalMessageDto, +} from '../../../../../../../../app/federation-v2/server/application/input/RoomSenderDto'; + +describe('Federation - Infrastructure - RocketChat - FederationRoomSenderConverter', () => { + describe('#toRoomInviteUserDto()', () => { + it('should return an instance of FederationRoomInviteUserDto', () => { + expect( + FederationRoomSenderConverter.toRoomInviteUserDto('internalInviterId', 'internalRoomId', 'externalInviteeId'), + ).to.be.instanceOf(FederationRoomInviteUserDto); + }); + + it('should return the normalizedInviteeId property without any @ if any', () => { + expect( + FederationRoomSenderConverter.toRoomInviteUserDto('internalInviterId', 'internalRoomId', '@externalInviteeId:server-name.com') + .normalizedInviteeId, + ).to.be.equal('externalInviteeId:server-name.com'); + }); + + it('should return the inviteeUsernameOnly property without any @ if any and only the first part before ":"', () => { + expect( + FederationRoomSenderConverter.toRoomInviteUserDto('internalInviterId', 'internalRoomId', '@externalInviteeId:server-name.com') + .inviteeUsernameOnly, + ).to.be.equal('externalInviteeId'); + }); + + it('should return the normalizedInviteeId AND inviteeUsernameOnly equals to the rawInviteeId if it does not have any special chars', () => { + const result = FederationRoomSenderConverter.toRoomInviteUserDto('internalInviterId', 'internalRoomId', 'externalInviteeId'); + expect(result.rawInviteeId).to.be.equal('externalInviteeId'); + expect(result.normalizedInviteeId).to.be.equal('externalInviteeId'); + expect(result.inviteeUsernameOnly).to.be.equal('externalInviteeId'); + }); + + it('should have all the properties set', () => { + const internalInviterId = 'internalInviterId'; + const internalRoomId = 'internalRoomId'; + const externalInviteeId = 'externalInviteeId'; + const result: any = FederationRoomSenderConverter.toRoomInviteUserDto(internalInviterId, internalRoomId, externalInviteeId); + expect(result).to.be.eql({ + internalInviterId, + internalRoomId, + rawInviteeId: externalInviteeId, + normalizedInviteeId: externalInviteeId, + inviteeUsernameOnly: externalInviteeId, + }); + }); + }); + describe('#toSendExternalMessageDto()', () => { + it('should return an instance of FederationRoomSendExternalMessageDto', () => { + expect( + FederationRoomSenderConverter.toSendExternalMessageDto('internalSenderId', 'internalRoomId', { msg: 'text' } as IMessage), + ).to.be.instanceOf(FederationRoomSendExternalMessageDto); + }); + + it('should have all the properties set', () => { + const internalSenderId = 'internalSenderId'; + const internalRoomId = 'internalRoomId'; + const msg = { msg: 'text' } as IMessage; + const result: any = FederationRoomSenderConverter.toSendExternalMessageDto(internalSenderId, internalRoomId, msg); + expect(result).to.be.eql({ + internalSenderId, + internalRoomId, + message: msg, + }); + }); + }); +}); diff --git a/packages/core-typings/src/IRoom.ts b/packages/core-typings/src/IRoom.ts index dc09fe72a312..647fbe290171 100644 --- a/packages/core-typings/src/IRoom.ts +++ b/packages/core-typings/src/IRoom.ts @@ -84,6 +84,7 @@ export interface IRoom extends IRocketChatRecord { description?: string; createdOTR?: boolean; e2eKeyId?: string; + federated?: boolean; channel?: { _id: string }; } diff --git a/yarn.lock b/yarn.lock index b8f3140d3d2a..b6f5c8dcf825 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4826,6 +4826,7 @@ __metadata: "@types/rewire": ^2.5.28 "@types/semver": ^7.3.9 "@types/sharp": ^0.30.2 + "@types/sinon": ^10.0.11 "@types/speakeasy": ^2.0.7 "@types/string-strip-html": ^5.0.0 "@types/supertest": ^2.0.11 @@ -4984,6 +4985,7 @@ __metadata: rewire: ^6.0.0 semver: ^7.3.7 sharp: ^0.30.4 + sinon: ^14.0.0 sip.js: ^0.20.0 sodium-native: ^3.3.0 sodium-plus: ^0.9.0 @@ -5248,7 +5250,7 @@ __metadata: languageName: node linkType: hard -"@sinonjs/commons@npm:^1.7.0": +"@sinonjs/commons@npm:^1.6.0, @sinonjs/commons@npm:^1.7.0, @sinonjs/commons@npm:^1.8.3": version: 1.8.3 resolution: "@sinonjs/commons@npm:1.8.3" dependencies: @@ -5257,6 +5259,15 @@ __metadata: languageName: node linkType: hard +"@sinonjs/fake-timers@npm:>=5, @sinonjs/fake-timers@npm:^9.1.2": + version: 9.1.2 + resolution: "@sinonjs/fake-timers@npm:9.1.2" + dependencies: + "@sinonjs/commons": ^1.7.0 + checksum: 7d3aef54e17c1073101cb64d953157c19d62a40e261a30923fa1ee337b049c5f29cc47b1f0c477880f42b5659848ba9ab897607ac8ea4acd5c30ddcfac57fca6 + languageName: node + linkType: hard + "@sinonjs/fake-timers@npm:^8.0.1": version: 8.1.0 resolution: "@sinonjs/fake-timers@npm:8.1.0" @@ -5266,6 +5277,24 @@ __metadata: languageName: node linkType: hard +"@sinonjs/samsam@npm:^6.1.1": + version: 6.1.1 + resolution: "@sinonjs/samsam@npm:6.1.1" + dependencies: + "@sinonjs/commons": ^1.6.0 + lodash.get: ^4.4.2 + type-detect: ^4.0.8 + checksum: a09b0914bf573f0da82bd03c64ba413df81a7c173818dc3f0a90c2652240ac835ef583f4d52f0b215e626633c91a4095c255e0669f6ead97241319f34f05e7fc + languageName: node + linkType: hard + +"@sinonjs/text-encoding@npm:^0.7.1": + version: 0.7.1 + resolution: "@sinonjs/text-encoding@npm:0.7.1" + checksum: 130de0bb568c5f8a611ec21d1a4e3f80ab0c5ec333010f49cfc1adc5cba6d8808699c8a587a46b0f0b016a1f4c1389bc96141e773e8460fcbb441875b2e91ba7 + languageName: node + linkType: hard + "@slack/client@npm:^4.12.0": version: 4.12.0 resolution: "@slack/client@npm:4.12.0" @@ -7807,6 +7836,22 @@ __metadata: languageName: node linkType: hard +"@types/sinon@npm:^10.0.11": + version: 10.0.11 + resolution: "@types/sinon@npm:10.0.11" + dependencies: + "@types/sinonjs__fake-timers": "*" + checksum: 196f3e26985dca5dfb593592e4b64463e536c047a9f43aa2b328b16024a3b0e3fb27b7a3f3972c6ef75749f55012737eb6c63a1c2e9782b7fe5cbbd25f75fd62 + languageName: node + linkType: hard + +"@types/sinonjs__fake-timers@npm:*": + version: 8.1.2 + resolution: "@types/sinonjs__fake-timers@npm:8.1.2" + checksum: bbc73a5ab6c0ec974929392f3d6e1e8db4ebad97ec506d785301e1c3d8a4f98a35b1aa95b97035daef02886fd8efd7788a2fa3ced2ec7105988bfd8dce61eedd + languageName: node + linkType: hard + "@types/sizzle@npm:*": version: 2.3.3 resolution: "@types/sizzle@npm:2.3.3" @@ -13944,6 +13989,13 @@ __metadata: languageName: node linkType: hard +"diff@npm:^5.0.0": + version: 5.1.0 + resolution: "diff@npm:5.1.0" + checksum: c7bf0df7c9bfbe1cf8a678fd1b2137c4fb11be117a67bc18a0e03ae75105e8533dbfb1cda6b46beb3586ef5aed22143ef9d70713977d5fb1f9114e21455fba90 + languageName: node + linkType: hard + "diffie-hellman@npm:^5.0.0": version: 5.0.3 resolution: "diffie-hellman@npm:5.0.3" @@ -21338,6 +21390,13 @@ __metadata: languageName: node linkType: hard +"just-extend@npm:^4.0.2": + version: 4.2.1 + resolution: "just-extend@npm:4.2.1" + checksum: ff9fdede240fad313efeeeb68a660b942e5586d99c0058064c78884894a2690dc09bba44c994ad4e077e45d913fef01a9240c14a72c657b53687ac58de53b39c + languageName: node + linkType: hard + "jwa@npm:^1.4.1": version: 1.4.1 resolution: "jwa@npm:1.4.1" @@ -24088,6 +24147,19 @@ __metadata: languageName: node linkType: hard +"nise@npm:^5.1.1": + version: 5.1.1 + resolution: "nise@npm:5.1.1" + dependencies: + "@sinonjs/commons": ^1.8.3 + "@sinonjs/fake-timers": ">=5" + "@sinonjs/text-encoding": ^0.7.1 + just-extend: ^4.0.2 + path-to-regexp: ^1.7.0 + checksum: d8be29e84a014743c9a10f428fac86f294ac5f92bed1f606fe9b551e935f494d8e0ce1af8a12673c6014010ec7f771f2d48aa5c8e116f223eb4f40c5e1ab44b3 + languageName: node + linkType: hard + "nkeys.js@npm:^1.0.0-9": version: 1.0.0-9 resolution: "nkeys.js@npm:1.0.0-9" @@ -25750,6 +25822,15 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:^1.7.0": + version: 1.8.0 + resolution: "path-to-regexp@npm:1.8.0" + dependencies: + isarray: 0.0.1 + checksum: 709f6f083c0552514ef4780cb2e7e4cf49b0cc89a97439f2b7cc69a608982b7690fb5d1720a7473a59806508fc2dae0be751ba49f495ecf89fd8fbc62abccbcd + languageName: node + linkType: hard + "path-to-regexp@npm:^6.2.0": version: 6.2.0 resolution: "path-to-regexp@npm:6.2.0" @@ -30106,6 +30187,20 @@ __metadata: languageName: node linkType: hard +"sinon@npm:^14.0.0": + version: 14.0.0 + resolution: "sinon@npm:14.0.0" + dependencies: + "@sinonjs/commons": ^1.8.3 + "@sinonjs/fake-timers": ^9.1.2 + "@sinonjs/samsam": ^6.1.1 + diff: ^5.0.0 + nise: ^5.1.1 + supports-color: ^7.2.0 + checksum: b2aeeb0cdc2cd30f904ccbcd60bae4e1b3dcf3aeeface09c1832db0336be0dbaa461f3b91b769bed84f05c83d45d5072a9da7ee14bc7289daeda2a1214fe173c + languageName: node + linkType: hard + "sip.js@npm:^0.20.0": version: 0.20.0 resolution: "sip.js@npm:0.20.0" @@ -31457,7 +31552,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0": +"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0, supports-color@npm:^7.2.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" dependencies: @@ -32666,7 +32761,7 @@ __metadata: languageName: node linkType: hard -"type-detect@npm:4.0.8, type-detect@npm:^4.0.0, type-detect@npm:^4.0.5": +"type-detect@npm:4.0.8, type-detect@npm:^4.0.0, type-detect@npm:^4.0.5, type-detect@npm:^4.0.8": version: 4.0.8 resolution: "type-detect@npm:4.0.8" checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15 From c3de0dd87a9a0964603e12605d3a9b14143b2744 Mon Sep 17 00:00:00 2001 From: Patrick Pankotsch Date: Mon, 6 Jun 2022 16:40:57 +0200 Subject: [PATCH 4/8] [FIX] Bump meteor-node-stubs to version 1.2.3 (#25669) * Bumb meteoer-node-stubs to version 1.2.3 to fix issue #25460 * yarn.lock Co-authored-by: Guilherme Gazzo --- apps/meteor/package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/meteor/package.json b/apps/meteor/package.json index ddbe4132fac6..aabd707580e3 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -290,7 +290,7 @@ "mailparser": "^3.4.0", "marked": "^0.7.0", "mem": "^8.1.1", - "meteor-node-stubs": "^1.2.1", + "meteor-node-stubs": "^1.2.3", "mime-db": "^1.52.0", "mime-type": "^4.0.0", "mkdirp": "^1.0.4", diff --git a/yarn.lock b/yarn.lock index b6f5c8dcf825..bd6380a7cf07 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4934,7 +4934,7 @@ __metadata: mailparser: ^3.4.0 marked: ^0.7.0 mem: ^8.1.1 - meteor-node-stubs: ^1.2.1 + meteor-node-stubs: ^1.2.3 mime-db: ^1.52.0 mime-type: ^4.0.0 mkdirp: ^1.0.4 @@ -23053,7 +23053,7 @@ __metadata: languageName: node linkType: hard -"meteor-node-stubs@npm:^1.2.1": +"meteor-node-stubs@npm:^1.2.3": version: 1.2.3 resolution: "meteor-node-stubs@npm:1.2.3" dependencies: From 9a4c6cbeb5bf50af2d6232597969fc4cec398e70 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Mon, 6 Jun 2022 14:40:21 -0300 Subject: [PATCH 5/8] [FIX] Thread Message Preview (#25709) * Correct directory capitalization * Move some message blocks * Move some message blocks * Move some elements * Move some elements * Segregate message and thread preview at top-level * Add `ColorElement` * Add `MessageBodyPreview` * Detach markup from message * Cascade some markup styles * also render origin Co-authored-by: gabriellsh --- .../Message/MessageBodyRender/BigEmoji.tsx | 19 ----- .../Message/MessageBodyRender/Bold.tsx | 28 ------- .../Message/MessageBodyRender/CodeLine.tsx | 6 -- .../Message/MessageBodyRender/Heading.tsx | 23 ------ .../Message/MessageBodyRender/Image.tsx | 21 ----- .../Message/MessageBodyRender/Inline.tsx | 50 ------------ .../Message/MessageBodyRender/InlineCode.tsx | 19 ----- .../Message/MessageBodyRender/Italic.tsx | 29 ------- .../Message/MessageBodyRender/Link.tsx | 37 --------- .../Message/MessageBodyRender/Mention.tsx | 40 ---------- .../MessageBodyRender/MentionChannel.tsx | 23 ------ .../Message/MessageBodyRender/OrderedList.tsx | 16 ---- .../Message/MessageBodyRender/Paragraph.tsx | 12 --- .../Message/MessageBodyRender/Quote.tsx | 36 --------- .../Message/MessageBodyRender/Strike.tsx | 28 ------- .../Message/MessageBodyRender/TaskList.tsx | 23 ------ .../MessageBodyRender/UnorderedList.tsx | 16 ---- .../definitions/ChannelMention.ts | 3 - .../definitions/UserMention.ts | 3 - .../Message/MessageBodyRender/index.tsx | 79 ------------------- .../client/components/gazzodown/Markup.tsx | 55 +++++++++++++ .../MarkupInteractionContext.ts} | 23 +++--- .../components/gazzodown/PreviewMarkup.tsx | 74 +++++++++++++++++ .../gazzodown/blocks/BigEmojiBlock.tsx | 18 +++++ .../blocks/CodeBlock.tsx} | 26 +++--- .../gazzodown/blocks/HeadingBlock.tsx | 23 ++++++ .../gazzodown/blocks/OrderedListBlock.tsx | 20 +++++ .../gazzodown/blocks/ParagraphBlock.tsx | 16 ++++ .../gazzodown/blocks/PreviewBigEmojiBlock.tsx | 18 +++++ .../gazzodown/blocks/QuoteBlock.tsx | 18 +++++ .../gazzodown/blocks/TaskListBlock.tsx | 21 +++++ .../gazzodown/blocks/UnorderedListBlock.tsx | 20 +++++ .../gazzodown/elements/BigEmojiElement.tsx | 32 ++++++++ .../gazzodown/elements/BoldSpan.tsx | 36 +++++++++ .../elements/ChannelMentionElement.tsx | 25 ++++++ .../gazzodown/elements/CodeElement.tsx | 15 ++++ .../gazzodown/elements/ColorElement.tsx | 26 ++++++ .../gazzodown/elements/EmojiElement.tsx | 28 +++++++ .../gazzodown/elements/ImageElement.tsx | 45 +++++++++++ .../gazzodown/elements/InlineElements.tsx | 64 +++++++++++++++ .../gazzodown/elements/ItalicSpan.tsx | 36 +++++++++ .../gazzodown/elements/LinkSpan.tsx | 42 ++++++++++ .../elements/PlainSpan.tsx} | 11 ++- .../elements/PreviewColorElement.tsx | 33 ++++++++ .../elements/PreviewEmojiElement.tsx | 28 +++++++ .../elements/PreviewInlineElements.tsx | 51 ++++++++++++ .../gazzodown/elements/StrikeSpan.tsx | 36 +++++++++ .../gazzodown/elements/UserMentionElement.tsx | 43 ++++++++++ .../Attachments/ActionAttachtment.tsx | 0 .../Attachments/Attachment/Action.tsx | 0 .../Attachments/Attachment/Attachment.tsx | 0 .../Attachments/Attachment/Author.tsx | 0 .../Attachments/Attachment/AuthorAvatar.tsx | 0 .../Attachments/Attachment/AuthorName.tsx | 0 .../Attachments/Attachment/Block.tsx | 0 .../Attachments/Attachment/Collapse.tsx | 0 .../Attachments/Attachment/Content.tsx | 0 .../Attachments/Attachment/Details.tsx | 0 .../Attachments/Attachment/Download.tsx | 0 .../Attachments/Attachment/Inner.tsx | 0 .../Attachments/Attachment/Row.tsx | 0 .../Attachments/Attachment/Size.tsx | 0 .../Attachments/Attachment/Text.tsx | 0 .../Attachments/Attachment/Thumb.tsx | 0 .../Attachments/Attachment/Title.tsx | 0 .../Attachments/Attachment/TitleLink.tsx | 0 .../Attachments/Attachment/index.tsx | 0 .../Attachments/Attachments.stories.tsx | 0 .../Attachments/Attachments.tsx | 0 .../Attachments/DefaultAttachment.tsx | 0 .../Attachments/FieldsAttachment/Field.tsx | 0 .../FieldsAttachment/ShortField.tsx | 0 .../Attachments/FieldsAttachment/index.tsx | 0 .../Attachments/Files/AudioAttachment.tsx | 0 .../Files/GenericFileAttachment.tsx | 0 .../Attachments/Files/ImageAttachment.tsx | 0 .../Attachments/Files/PDFAttachment.tsx | 0 .../Attachments/Files/VideoAttachment.tsx | 0 .../Attachments/Files/index.tsx | 0 .../{Message => message}/Attachments/Item.tsx | 0 .../Attachments/QuoteAttachment.tsx | 0 .../Attachments/components/Image.tsx | 0 .../Attachments/components/ImageBox.tsx | 0 .../Attachments/components/Load.tsx | 0 .../Attachments/components/Retry.stories.tsx | 0 .../Attachments/components/Retry.tsx | 0 .../Attachments/hooks/useCollapse.tsx | 0 .../Attachments/hooks/useLoadImage.tsx | 0 .../Attachments/index.tsx | 0 .../providers/AttachmentProvider.tsx | 0 .../MessageActions/Action.tsx | 0 .../MessageActions/Actions.tsx | 0 .../MessageActions/index.tsx | 0 .../{Message => message}/MessageEmoji.tsx | 13 +-- .../Metrics/Broadcast.tsx | 0 .../{Message => message}/Metrics/Content.tsx | 0 .../Metrics/ContentItem.tsx | 0 .../Metrics/Discussion.tsx | 0 .../Metrics/Metrics.stories.tsx | 0 .../{Message => message}/Metrics/Metrics.tsx | 0 .../Metrics/MetricsFollowing.tsx | 0 .../Metrics/MetricsItem.tsx | 0 .../Metrics/MetricsItemIcon.tsx | 0 .../Metrics/MetricsItemLabel.tsx | 0 .../{Message => message}/Metrics/Reply.tsx | 0 .../{Message => message}/Metrics/Thread.tsx | 0 .../{Message => message}/Metrics/index.tsx | 0 .../NotificationStatus/All.tsx | 0 .../NotificationStatus/Me.tsx | 0 .../NotificationStatus/NotificationStatus.tsx | 0 .../NotificationStatus/Unread.tsx | 0 .../NotificationStatus/index.ts | 0 .../Oembed/definitions.ts | 0 .../{Message => message}/StatusMessage.tsx | 0 .../helpers/followSyle.ts | 0 .../hooks/useBlockRendered.ts | 0 .../client/providers/MeteorProvider.tsx | 2 +- apps/meteor/client/templates.ts | 12 ++- .../client/views/blocks/MessageBlock.js | 2 +- .../MessageList/components/MessageContent.tsx | 10 +-- .../components/MessageContentBody.tsx | 74 +++++++++++++---- .../MessageList/components/MessageSystem.tsx | 4 +- .../components/ThreadMessagePreview.tsx | 14 ++-- .../components/ThreadMessagePreviewBody.tsx | 17 ++++ .../UrlPreview/OEmbedCollapseable.tsx | 2 +- .../components/UrlPreview/UrlPreview.tsx | 2 +- .../MessageList/hooks/useParsedMessage.ts | 2 +- .../Threads/components/Message.js | 4 +- 128 files changed, 965 insertions(+), 587 deletions(-) delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/BigEmoji.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/Bold.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/CodeLine.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/Heading.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/Image.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/Inline.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/InlineCode.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/Italic.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/Link.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/Mention.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/MentionChannel.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/OrderedList.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/Paragraph.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/Quote.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/Strike.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/TaskList.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/UnorderedList.tsx delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/definitions/ChannelMention.ts delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/definitions/UserMention.ts delete mode 100644 apps/meteor/client/components/Message/MessageBodyRender/index.tsx create mode 100644 apps/meteor/client/components/gazzodown/Markup.tsx rename apps/meteor/client/components/{Message/MessageBodyRender/contexts/MessageBodyContext.ts => gazzodown/MarkupInteractionContext.ts} (62%) create mode 100644 apps/meteor/client/components/gazzodown/PreviewMarkup.tsx create mode 100644 apps/meteor/client/components/gazzodown/blocks/BigEmojiBlock.tsx rename apps/meteor/client/components/{Message/MessageBodyRender/Code.tsx => gazzodown/blocks/CodeBlock.tsx} (59%) create mode 100644 apps/meteor/client/components/gazzodown/blocks/HeadingBlock.tsx create mode 100644 apps/meteor/client/components/gazzodown/blocks/OrderedListBlock.tsx create mode 100644 apps/meteor/client/components/gazzodown/blocks/ParagraphBlock.tsx create mode 100644 apps/meteor/client/components/gazzodown/blocks/PreviewBigEmojiBlock.tsx create mode 100644 apps/meteor/client/components/gazzodown/blocks/QuoteBlock.tsx create mode 100644 apps/meteor/client/components/gazzodown/blocks/TaskListBlock.tsx create mode 100644 apps/meteor/client/components/gazzodown/blocks/UnorderedListBlock.tsx create mode 100644 apps/meteor/client/components/gazzodown/elements/BigEmojiElement.tsx create mode 100644 apps/meteor/client/components/gazzodown/elements/BoldSpan.tsx create mode 100644 apps/meteor/client/components/gazzodown/elements/ChannelMentionElement.tsx create mode 100644 apps/meteor/client/components/gazzodown/elements/CodeElement.tsx create mode 100644 apps/meteor/client/components/gazzodown/elements/ColorElement.tsx create mode 100644 apps/meteor/client/components/gazzodown/elements/EmojiElement.tsx create mode 100644 apps/meteor/client/components/gazzodown/elements/ImageElement.tsx create mode 100644 apps/meteor/client/components/gazzodown/elements/InlineElements.tsx create mode 100644 apps/meteor/client/components/gazzodown/elements/ItalicSpan.tsx create mode 100644 apps/meteor/client/components/gazzodown/elements/LinkSpan.tsx rename apps/meteor/client/components/{Message/MessageBodyRender/PlainText.tsx => gazzodown/elements/PlainSpan.tsx} (61%) create mode 100644 apps/meteor/client/components/gazzodown/elements/PreviewColorElement.tsx create mode 100644 apps/meteor/client/components/gazzodown/elements/PreviewEmojiElement.tsx create mode 100644 apps/meteor/client/components/gazzodown/elements/PreviewInlineElements.tsx create mode 100644 apps/meteor/client/components/gazzodown/elements/StrikeSpan.tsx create mode 100644 apps/meteor/client/components/gazzodown/elements/UserMentionElement.tsx rename apps/meteor/client/components/{Message => message}/Attachments/ActionAttachtment.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/Action.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/Attachment.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/Author.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/AuthorAvatar.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/AuthorName.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/Block.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/Collapse.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/Content.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/Details.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/Download.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/Inner.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/Row.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/Size.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/Text.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/Thumb.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/Title.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/TitleLink.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachment/index.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachments.stories.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Attachments.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/DefaultAttachment.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/FieldsAttachment/Field.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/FieldsAttachment/ShortField.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/FieldsAttachment/index.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Files/AudioAttachment.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Files/GenericFileAttachment.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Files/ImageAttachment.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Files/PDFAttachment.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Files/VideoAttachment.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Files/index.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/Item.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/QuoteAttachment.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/components/Image.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/components/ImageBox.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/components/Load.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/components/Retry.stories.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/components/Retry.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/hooks/useCollapse.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/hooks/useLoadImage.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/index.tsx (100%) rename apps/meteor/client/components/{Message => message}/Attachments/providers/AttachmentProvider.tsx (100%) rename apps/meteor/client/components/{Message => message}/MessageActions/Action.tsx (100%) rename apps/meteor/client/components/{Message => message}/MessageActions/Actions.tsx (100%) rename apps/meteor/client/components/{Message => message}/MessageActions/index.tsx (100%) rename apps/meteor/client/components/{Message => message}/MessageEmoji.tsx (60%) rename apps/meteor/client/components/{Message => message}/Metrics/Broadcast.tsx (100%) rename apps/meteor/client/components/{Message => message}/Metrics/Content.tsx (100%) rename apps/meteor/client/components/{Message => message}/Metrics/ContentItem.tsx (100%) rename apps/meteor/client/components/{Message => message}/Metrics/Discussion.tsx (100%) rename apps/meteor/client/components/{Message => message}/Metrics/Metrics.stories.tsx (100%) rename apps/meteor/client/components/{Message => message}/Metrics/Metrics.tsx (100%) rename apps/meteor/client/components/{Message => message}/Metrics/MetricsFollowing.tsx (100%) rename apps/meteor/client/components/{Message => message}/Metrics/MetricsItem.tsx (100%) rename apps/meteor/client/components/{Message => message}/Metrics/MetricsItemIcon.tsx (100%) rename apps/meteor/client/components/{Message => message}/Metrics/MetricsItemLabel.tsx (100%) rename apps/meteor/client/components/{Message => message}/Metrics/Reply.tsx (100%) rename apps/meteor/client/components/{Message => message}/Metrics/Thread.tsx (100%) rename apps/meteor/client/components/{Message => message}/Metrics/index.tsx (100%) rename apps/meteor/client/components/{Message => message}/NotificationStatus/All.tsx (100%) rename apps/meteor/client/components/{Message => message}/NotificationStatus/Me.tsx (100%) rename apps/meteor/client/components/{Message => message}/NotificationStatus/NotificationStatus.tsx (100%) rename apps/meteor/client/components/{Message => message}/NotificationStatus/Unread.tsx (100%) rename apps/meteor/client/components/{Message => message}/NotificationStatus/index.ts (100%) rename apps/meteor/client/components/{Message => message}/Oembed/definitions.ts (100%) rename apps/meteor/client/components/{Message => message}/StatusMessage.tsx (100%) rename apps/meteor/client/components/{Message => message}/helpers/followSyle.ts (100%) rename apps/meteor/client/components/{Message => message}/hooks/useBlockRendered.ts (100%) create mode 100644 apps/meteor/client/views/room/MessageList/components/ThreadMessagePreviewBody.tsx diff --git a/apps/meteor/client/components/Message/MessageBodyRender/BigEmoji.tsx b/apps/meteor/client/components/Message/MessageBodyRender/BigEmoji.tsx deleted file mode 100644 index 65b09cf9aa54..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/BigEmoji.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { BigEmoji as ASTBigEmoji } from '@rocket.chat/message-parser'; -import React, { ReactElement } from 'react'; - -import MessageEmoji from '../MessageEmoji'; - -type BigEmojiProps = { - value: ASTBigEmoji['value']; - isThreadPreview?: boolean; -}; - -const BigEmoji = ({ value, isThreadPreview }: BigEmojiProps): ReactElement => ( - <> - {value.map((block, index) => ( - - ))} - -); - -export default BigEmoji; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/Bold.tsx b/apps/meteor/client/components/Message/MessageBodyRender/Bold.tsx deleted file mode 100644 index 085cb203ea39..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/Bold.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Bold as ASTBold } from '@rocket.chat/message-parser'; -import React, { FC } from 'react'; - -import Italic from './Italic'; -import Link from './Link'; -import PlainText from './PlainText'; -import Strike from './Strike'; - -const Bold: FC<{ value: ASTBold['value'] }> = ({ value = [] }) => ( - - {value.map((block, index) => { - switch (block.type) { - case 'LINK': - return ; - case 'PLAIN_TEXT': - return ; - case 'STRIKE': - return <Strike key={index} value={block.value} />; - case 'ITALIC': - return <Italic key={index} value={block.value} />; - default: - return null; - } - })} - </strong> -); - -export default Bold; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/CodeLine.tsx b/apps/meteor/client/components/Message/MessageBodyRender/CodeLine.tsx deleted file mode 100644 index 275d970db306..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/CodeLine.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { CodeLine as ASTCodeLine } from '@rocket.chat/message-parser'; -import React, { FC } from 'react'; - -const CodeLine: FC<{ value: ASTCodeLine['value'] }> = ({ value }) => <div>{value.type === 'PLAIN_TEXT' && value.value}</div>; - -export default CodeLine; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/Heading.tsx b/apps/meteor/client/components/Message/MessageBodyRender/Heading.tsx deleted file mode 100644 index 0c67e5252e10..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/Heading.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Heading as ASTHeading } from '@rocket.chat/message-parser'; -import React, { FC } from 'react'; - -import PlainText from './PlainText'; - -const Heading: FC<{ value: ASTHeading['value']; level: ASTHeading['level'] }> = ({ value = [], level = 1 }) => { - const HeadingTag = `h${level}` as keyof JSX.IntrinsicElements; - - return ( - <HeadingTag> - {value.map((block, index) => { - switch (block.type) { - case 'PLAIN_TEXT': - return <PlainText key={index} value={block.value} />; - default: - return null; - } - })} - </HeadingTag> - ); -}; - -export default Heading; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/Image.tsx b/apps/meteor/client/components/Message/MessageBodyRender/Image.tsx deleted file mode 100644 index d98db0d46b93..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/Image.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Image as ASTImage } from '@rocket.chat/message-parser'; -import React, { FC } from 'react'; - -type ImageProps = { - value: ASTImage['value']; -}; - -const style = { - maxWidth: '100%', -}; - -const Image: FC<ImageProps> = ({ value }) => { - const { src, label } = value; - return ( - <a href={src.value} target='_blank' rel='noopener noreferrer'> - <img src={src.value} data-title={src.value} alt={String(label.value)} style={style} /> - </a> - ); -}; - -export default Image; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/Inline.tsx b/apps/meteor/client/components/Message/MessageBodyRender/Inline.tsx deleted file mode 100644 index caf183f4802b..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/Inline.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Paragraph as ASTParagraph } from '@rocket.chat/message-parser'; -import React, { FC } from 'react'; - -import MessageEmoji from '../MessageEmoji'; -import Bold from './Bold'; -import Image from './Image'; -import InlineCode from './InlineCode'; -import Italic from './Italic'; -import Link from './Link'; -import Mention from './Mention'; -import MentionChannel from './MentionChannel'; -import PlainText from './PlainText'; -import Strike from './Strike'; -import { useMessageBodyIsThreadPreview } from './contexts/MessageBodyContext'; - -const Inline: FC<{ value: ASTParagraph['value'] }> = ({ value = [] }) => { - const isThreadPreview = useMessageBodyIsThreadPreview(); - return ( - <> - {value.map((block, index) => { - switch (block.type) { - case 'IMAGE': - return <Image key={index} value={block.value} />; - case 'PLAIN_TEXT': - return <PlainText key={index} value={block.value} />; - case 'BOLD': - return <Bold key={index} value={block.value} />; - case 'STRIKE': - return <Strike key={index} value={block.value} />; - case 'ITALIC': - return <Italic key={index} value={block.value} />; - case 'LINK': - return <Link key={index} value={block.value} />; - case 'MENTION_USER': - return <Mention key={index} value={block.value} />; - case 'MENTION_CHANNEL': - return <MentionChannel key={index} value={block.value} />; - case 'EMOJI': - return <MessageEmoji isThreadPreview={isThreadPreview} key={index} emojiHandle={`:${block.value.value}:`} />; - case 'INLINE_CODE': - return <InlineCode key={index} value={block.value} />; - default: - return null; - } - })} - </> - ); -}; - -export default Inline; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/InlineCode.tsx b/apps/meteor/client/components/Message/MessageBodyRender/InlineCode.tsx deleted file mode 100644 index c4bceb55adc8..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/InlineCode.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { InlineCode as ASTInlineCode } from '@rocket.chat/message-parser'; -import React, { ReactElement, FC } from 'react'; - -import PlainText from './PlainText'; - -const InlineCode: FC<{ value: ASTInlineCode['value'] }> = ({ value }) => ( - <code className='code-colors inline'> - {((block): ReactElement | null => { - switch (block.type) { - case 'PLAIN_TEXT': - return <PlainText value={block.value} />; - default: - return null; - } - })(value)} - </code> -); - -export default InlineCode; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/Italic.tsx b/apps/meteor/client/components/Message/MessageBodyRender/Italic.tsx deleted file mode 100644 index 3d98a4300776..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/Italic.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Italic as ASTItalic } from '@rocket.chat/message-parser'; -import React, { FC } from 'react'; - -import Bold from './Bold'; -import Link from './Link'; -import PlainText from './PlainText'; -import Strike from './Strike'; - -const Italic: FC<{ value: ASTItalic['value'] }> = ({ value = [] }) => ( - <i> - {value.map((block, index) => { - switch (block.type) { - case 'LINK': - return <Link key={index} value={block.value} />; - case 'PLAIN_TEXT': - return <PlainText key={index} value={block.value} />; - case 'STRIKE': - return <Strike key={index} value={block.value} />; - case 'BOLD': - return <Bold key={index} value={block.value} />; - - default: - return null; - } - })} - </i> -); - -export default Italic; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/Link.tsx b/apps/meteor/client/components/Message/MessageBodyRender/Link.tsx deleted file mode 100644 index edf528ba98f5..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/Link.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Link as ASTLink } from '@rocket.chat/message-parser'; -import React, { FC } from 'react'; - -import { baseURI } from '../../../lib/baseURI'; -import Bold from './Bold'; -import Italic from './Italic'; -import PlainText from './PlainText'; -import Strike from './Strike'; - -type LinkProps = { - value: ASTLink['value']; -}; - -const Link: FC<LinkProps> = ({ value }) => { - const { src, label } = value; - const target = src.value.indexOf(baseURI) === 0 ? '' : '_blank'; - return ( - <a href={src.value} data-title={src.value} target={target} rel='noopener noreferrer'> - {((block: ASTLink['value']['label']): JSX.Element | string | null => { - switch (block.type) { - case 'PLAIN_TEXT': - return <PlainText value={block.value} />; - case 'STRIKE': - return <Strike value={block.value} />; - case 'ITALIC': - return <Italic value={block.value} />; - case 'BOLD': - return <Bold value={block.value} />; - default: - return null; - } - })(label)} - </a> - ); -}; - -export default Link; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/Mention.tsx b/apps/meteor/client/components/Message/MessageBodyRender/Mention.tsx deleted file mode 100644 index b1a539e24cbe..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/Mention.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { UserMention as ASTUserMention } from '@rocket.chat/message-parser'; -import { useUserId } from '@rocket.chat/ui-contexts'; -import React, { FC, memo } from 'react'; - -import { useMessageBodyUserMentions, useMessageBodyMentionClick } from './contexts/MessageBodyContext'; - -const Mention: FC<{ value: ASTUserMention['value'] }> = ({ value: { value: mention } }) => { - const uid = useUserId(); - const mentions = useMessageBodyUserMentions(); - const mentioned = mentions.find((mentioned) => mentioned.username === mention); - const onUserMentionClick = useMessageBodyMentionClick(); - const classNames = ['mention-link']; - if (mention === 'all') { - classNames.push('mention-link--all'); - classNames.push('mention-link--group'); - } else if (mention === 'here') { - classNames.push('mention-link--here'); - classNames.push('mention-link--group'); - } else if (mentioned && mentioned._id === uid) { - classNames.push('mention-link--me'); - classNames.push('mention-link--user'); - } else { - classNames.push('mention-link--user'); - } - return ( - <> - {mentioned && ( - <span - onClick={classNames.includes('mention-link--user') ? onUserMentionClick(mention) : undefined} - className={classNames.join(' ')} - > - {mentioned.name || mention} - </span> - )} - {!mentioned && `@${mention}`} - </> - ); -}; - -export default memo(Mention); diff --git a/apps/meteor/client/components/Message/MessageBodyRender/MentionChannel.tsx b/apps/meteor/client/components/Message/MessageBodyRender/MentionChannel.tsx deleted file mode 100644 index 40e39a9beb51..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/MentionChannel.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { UserMention as ASTUserMention } from '@rocket.chat/message-parser'; -import React, { FC, memo } from 'react'; - -import { useMessageBodyChannelMentions, useMessageBodyChannelMentionClick } from './contexts/MessageBodyContext'; - -const Mention: FC<{ value: ASTUserMention['value'] }> = ({ value: { value: mention } }) => { - const mentions = useMessageBodyChannelMentions(); - const mentioned = mentions.find((mentioned) => mentioned.name === mention); - const onChannelMentionClick = useMessageBodyChannelMentionClick(); - - return ( - <> - {mentioned && ( - <span onClick={onChannelMentionClick(mentioned._id)} className='mention-link mention-link--room'> - #{mention} - </span> - )} - {!mentioned && `#${mention}`} - </> - ); -}; - -export default memo(Mention); diff --git a/apps/meteor/client/components/Message/MessageBodyRender/OrderedList.tsx b/apps/meteor/client/components/Message/MessageBodyRender/OrderedList.tsx deleted file mode 100644 index 742abd1dca9d..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/OrderedList.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { OrderedList as ASTOrderedList } from '@rocket.chat/message-parser'; -import React, { FC } from 'react'; - -import Inline from './Inline'; - -const OrderedList: FC<{ value: ASTOrderedList['value'] }> = ({ value }) => ( - <ol> - {value.map(({ value, number }, index) => ( - <li key={index} value={Number(number)}> - <Inline value={value} /> - </li> - ))} - </ol> -); - -export default OrderedList; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/Paragraph.tsx b/apps/meteor/client/components/Message/MessageBodyRender/Paragraph.tsx deleted file mode 100644 index 75fbe9cf62dc..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/Paragraph.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Paragraph as ASTParagraph } from '@rocket.chat/message-parser'; -import React, { FC } from 'react'; - -import Inline from './Inline'; - -const Paragraph: FC<{ value: ASTParagraph['value'] }> = ({ value = [] }) => ( - <p> - <Inline value={value} /> - </p> -); - -export default Paragraph; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/Quote.tsx b/apps/meteor/client/components/Message/MessageBodyRender/Quote.tsx deleted file mode 100644 index 5250816825bf..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/Quote.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { css } from '@rocket.chat/css-in-js'; -import { Box } from '@rocket.chat/fuselage'; -import colors from '@rocket.chat/fuselage-tokens/colors'; -import { Quote as ASTQuote } from '@rocket.chat/message-parser'; -import React, { FC } from 'react'; - -import Paragraph from './Paragraph'; - -const hover = css` - &:hover, - &:focus { - background: ${colors.n200} !important; - border-color: ${colors.n300} !important; - border-inline-start-color: ${colors.n600} !important; - } -`; - -const Quote: FC<{ value: ASTQuote['value'] }> = ({ value }) => ( - <Box - is='blockquote' - className={hover} - pi='x8' - borderRadius='x2' - borderWidth='x2' - borderStyle='solid' - backgroundColor='neutral-100' - borderColor='neutral-200' - borderInlineStartColor='neutral-600' - > - {value.map((item, index) => ( - <Paragraph key={index} value={item.value} /> - ))} - </Box> -); - -export default Quote; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/Strike.tsx b/apps/meteor/client/components/Message/MessageBodyRender/Strike.tsx deleted file mode 100644 index 497917565222..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/Strike.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Strike as ASTStrike } from '@rocket.chat/message-parser'; -import React, { FC } from 'react'; - -import Bold from './Bold'; -import Italic from './Italic'; -import Link from './Link'; -import PlainText from './PlainText'; - -const Strike: FC<{ value: ASTStrike['value'] }> = ({ value = [] }) => ( - <del> - {value.map((block, index) => { - switch (block.type) { - case 'LINK': - return <Link key={index} value={block.value} />; - case 'PLAIN_TEXT': - return <PlainText key={index} value={block.value} />; - case 'BOLD': - return <Bold key={index} value={block.value} />; - case 'ITALIC': - return <Italic key={index} value={block.value} />; - default: - return null; - } - })} - </del> -); - -export default Strike; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/TaskList.tsx b/apps/meteor/client/components/Message/MessageBodyRender/TaskList.tsx deleted file mode 100644 index f35be1beb974..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/TaskList.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { CheckBox } from '@rocket.chat/fuselage'; -import { Tasks as ASTTasks } from '@rocket.chat/message-parser'; -import React, { FC } from 'react'; - -import Inline from './Inline'; - -const TaksList: FC<{ value: ASTTasks['value'] }> = ({ value }) => ( - <ul - style={{ - listStyle: 'none', - marginLeft: 0, - paddingLeft: 0, - }} - > - {value.map((item) => ( - <li> - <CheckBox checked={item.status} /> <Inline value={item.value} /> - </li> - ))} - </ul> -); - -export default TaksList; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/UnorderedList.tsx b/apps/meteor/client/components/Message/MessageBodyRender/UnorderedList.tsx deleted file mode 100644 index e2a3076fa057..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/UnorderedList.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { UnorderedList as ASTUnorderedList } from '@rocket.chat/message-parser'; -import React, { FC } from 'react'; - -import Inline from './Inline'; - -const UnorderedList: FC<{ value: ASTUnorderedList['value'] }> = ({ value }) => ( - <ul> - {value.map((item, index) => ( - <li key={index}> - <Inline value={item.value} /> - </li> - ))} - </ul> -); - -export default UnorderedList; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/definitions/ChannelMention.ts b/apps/meteor/client/components/Message/MessageBodyRender/definitions/ChannelMention.ts deleted file mode 100644 index e2213cbf0df6..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/definitions/ChannelMention.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { IRoom } from '@rocket.chat/core-typings'; - -export type ChannelMention = Pick<IRoom, '_id' | 'name'>; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/definitions/UserMention.ts b/apps/meteor/client/components/Message/MessageBodyRender/definitions/UserMention.ts deleted file mode 100644 index 3fdabc0446a9..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/definitions/UserMention.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { IUser } from '@rocket.chat/core-typings'; - -export type UserMention = Pick<IUser, '_id' | 'name' | 'username'>; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/index.tsx b/apps/meteor/client/components/Message/MessageBodyRender/index.tsx deleted file mode 100644 index 96e78d4daf2c..000000000000 --- a/apps/meteor/client/components/Message/MessageBodyRender/index.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { BigEmoji as ASTBigEmoji, MarkdownAST } from '@rocket.chat/message-parser'; -import React, { FC, memo, MouseEvent } from 'react'; - -import BigEmoji from './BigEmoji'; -import Code from './Code'; -import Heading from './Heading'; -import OrderedList from './OrderedList'; -import Paragraph from './Paragraph'; -import Quote from './Quote'; -import TaskList from './TaskList'; -import UnorderedList from './UnorderedList'; -import { MessageBodyContext } from './contexts/MessageBodyContext'; -import { ChannelMention } from './definitions/ChannelMention'; -import { UserMention } from './definitions/UserMention'; - -type BodyProps = { - tokens: MarkdownAST; - mentions: UserMention[]; - channels: ChannelMention[]; - onUserMentionClick?: (username: string) => (e: MouseEvent<HTMLDivElement>) => void; - onChannelMentionClick?: (id: string) => (e: MouseEvent<HTMLDivElement>) => void; - isThreadPreview?: boolean; -}; - -const isBigEmoji = (tokens: MarkdownAST): tokens is [ASTBigEmoji] => tokens.length === 1 && tokens[0].type === 'BIG_EMOJI'; - -const MessageBodyRender: FC<BodyProps> = ({ - tokens, - mentions = [], - channels = [], - onUserMentionClick, - onChannelMentionClick, - isThreadPreview, -}) => { - if (isBigEmoji(tokens)) { - return <BigEmoji value={tokens[0].value} isThreadPreview={isThreadPreview} />; - } - - return ( - <MessageBodyContext.Provider value={{ mentions, channels, onUserMentionClick, onChannelMentionClick, isThreadPreview }}> - {tokens.map((block, index) => { - if (block.type === 'UNORDERED_LIST') { - return <UnorderedList value={block.value} key={index} />; - } - - if (block.type === 'QUOTE') { - return <Quote value={block.value} key={index} />; - } - if (block.type === 'TASKS') { - return <TaskList value={block.value} key={index} />; - } - - if (block.type === 'ORDERED_LIST') { - return <OrderedList value={block.value} key={index} />; - } - - if (block.type === 'PARAGRAPH') { - return <Paragraph value={block.value} key={index} />; - } - - if (block.type === 'CODE') { - return <Code {...block} key={index} />; - } - - if (block.type === 'HEADING') { - return <Heading value={block.value} level={block.level} key={index} />; - } - - if (block.type === 'LINE_BREAK') { - return <br key={index} />; - } - - return null; - })} - </MessageBodyContext.Provider> - ); -}; - -export default memo(MessageBodyRender); diff --git a/apps/meteor/client/components/gazzodown/Markup.tsx b/apps/meteor/client/components/gazzodown/Markup.tsx new file mode 100644 index 000000000000..1bd557c0ae55 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/Markup.tsx @@ -0,0 +1,55 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { memo, ReactElement } from 'react'; + +import BigEmojiBlock from './blocks/BigEmojiBlock'; +import CodeBlock from './blocks/CodeBlock'; +import HeadingBlock from './blocks/HeadingBlock'; +import OrderedListBlock from './blocks/OrderedListBlock'; +import ParagraphBlock from './blocks/ParagraphBlock'; +import QuoteBlock from './blocks/QuoteBlock'; +import TaskList from './blocks/TaskListBlock'; +import UnorderedListBlock from './blocks/UnorderedListBlock'; + +type MarkupProps = { + tokens: MessageParser.MarkdownAST; +}; + +const Markup = ({ tokens }: MarkupProps): ReactElement => ( + <> + {tokens.map((block, index) => { + switch (block.type) { + case 'BIG_EMOJI': + return <BigEmojiBlock key={index} emojis={block.value} />; + + case 'PARAGRAPH': + return <ParagraphBlock key={index} children={block.value} />; + + case 'HEADING': + return <HeadingBlock key={index} level={block.level} children={block.value} />; + + case 'UNORDERED_LIST': + return <UnorderedListBlock key={index} items={block.value} />; + + case 'ORDERED_LIST': + return <OrderedListBlock key={index} items={block.value} />; + + case 'TASKS': + return <TaskList key={index} tasks={block.value} />; + + case 'QUOTE': + return <QuoteBlock key={index} children={block.value} />; + + case 'CODE': + return <CodeBlock key={index} language={block.language} lines={block.value} />; + + case 'LINE_BREAK': + return <br key={index} />; + + default: + return null; + } + })} + </> +); + +export default memo(Markup); diff --git a/apps/meteor/client/components/Message/MessageBodyRender/contexts/MessageBodyContext.ts b/apps/meteor/client/components/gazzodown/MarkupInteractionContext.ts similarity index 62% rename from apps/meteor/client/components/Message/MessageBodyRender/contexts/MessageBodyContext.ts rename to apps/meteor/client/components/gazzodown/MarkupInteractionContext.ts index f257d210299e..70d6cb103d27 100644 --- a/apps/meteor/client/components/Message/MessageBodyRender/contexts/MessageBodyContext.ts +++ b/apps/meteor/client/components/gazzodown/MarkupInteractionContext.ts @@ -1,38 +1,35 @@ +import { IRoom, IUser } from '@rocket.chat/core-typings'; import { createContext, useContext, MouseEvent } from 'react'; -import { ChannelMention } from '../definitions/ChannelMention'; -import { UserMention } from '../definitions/UserMention'; +type UserMention = Pick<IUser, '_id' | 'name' | 'username'>; +type ChannelMention = Pick<IRoom, '_id' | 'name'>; -type MessageBodyContextType = { +type MarkupInteractionContextValue = { mentions?: UserMention[]; channels?: ChannelMention[]; - isThreadPreview?: boolean; onUserMentionClick?: (username: string) => (e: MouseEvent<HTMLDivElement>) => void; onChannelMentionClick?: (id: string) => (e: MouseEvent<HTMLDivElement>) => void; }; -export const MessageBodyContext = createContext<MessageBodyContextType>({ +export const MarkupInteractionContext = createContext<MarkupInteractionContextValue>({ mentions: [], channels: [], }); -export const useMessageBodyContext = (): MessageBodyContextType => useContext(MessageBodyContext); - -export const useMessageBodyIsThreadPreview = (): MessageBodyContextType['isThreadPreview'] => - useContext(MessageBodyContext).isThreadPreview; +export const useMarkupInteractionContext = (): MarkupInteractionContextValue => useContext(MarkupInteractionContext); export const useMessageBodyUserMentions = (): UserMention[] => { - const { mentions = [] } = useMessageBodyContext(); + const { mentions = [] } = useMarkupInteractionContext(); return mentions; }; export const useMessageBodyChannelMentions = (): ChannelMention[] => { - const { channels = [] } = useMessageBodyContext(); + const { channels = [] } = useMarkupInteractionContext(); return channels; }; export const useMessageBodyMentionClick = (): ((username: string) => (e: MouseEvent<HTMLDivElement>) => void) => { - const { onUserMentionClick } = useMessageBodyContext(); + const { onUserMentionClick } = useMarkupInteractionContext(); if (!onUserMentionClick) { console.warn('onUserMentionClick is not defined'); return (username: string) => (): void => { @@ -43,7 +40,7 @@ export const useMessageBodyMentionClick = (): ((username: string) => (e: MouseEv }; export const useMessageBodyChannelMentionClick = (): ((id: string) => (e: MouseEvent<HTMLDivElement>) => void) => { - const { onChannelMentionClick } = useMessageBodyContext(); + const { onChannelMentionClick } = useMarkupInteractionContext(); if (!onChannelMentionClick) { console.warn('onChannelMentionClick is not defined'); return (username: string) => (): void => { diff --git a/apps/meteor/client/components/gazzodown/PreviewMarkup.tsx b/apps/meteor/client/components/gazzodown/PreviewMarkup.tsx new file mode 100644 index 000000000000..0ed7fa7a64f8 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/PreviewMarkup.tsx @@ -0,0 +1,74 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { memo, ReactElement } from 'react'; + +import PreviewBigEmojiBlock from './blocks/PreviewBigEmojiBlock'; +import PreviewInlineElements from './elements/PreviewInlineElements'; + +const isOnlyBigEmojiBlock = (tokens: MessageParser.MarkdownAST): tokens is [MessageParser.BigEmoji] => + tokens.length === 1 && tokens[0].type === 'BIG_EMOJI'; + +type PreviewMarkupProps = { + tokens: MessageParser.MarkdownAST; +}; + +const PreviewMarkup = ({ tokens }: PreviewMarkupProps): ReactElement | null => { + if (isOnlyBigEmojiBlock(tokens)) { + return <PreviewBigEmojiBlock emojis={tokens[0].value} />; + } + + const firstBlock = tokens.find((block) => block.type !== 'LINE_BREAK'); + + if (!firstBlock) { + return null; + } + + switch (firstBlock.type) { + case 'PARAGRAPH': + return <PreviewInlineElements children={firstBlock.value} />; + + case 'HEADING': + return <>{firstBlock.value.map((plain) => plain.value).join('')}</>; + + case 'UNORDERED_LIST': + case 'ORDERED_LIST': { + const firstItem = firstBlock.value[0]; + + return ( + <> + {firstItem.number ? `${firstItem.number}.` : '-'} <PreviewInlineElements children={firstItem.value} /> + </> + ); + } + + case 'TASKS': { + const firstTask = firstBlock.value[0]; + + return ( + <> + {firstTask.status ? '\u2611' : '\u2610'} <PreviewInlineElements children={firstTask.value} /> + </> + ); + } + + case 'QUOTE': { + const firstParagraph = firstBlock.value[0]; + + return ( + <> + &gt; <PreviewInlineElements children={firstParagraph.value} /> + </> + ); + } + + case 'CODE': { + const firstLine = firstBlock.value.find((line) => line.value.value.trim()); + + return firstLine ? <>{firstLine.value.value.trim()}</> : null; + } + + default: + return null; + } +}; + +export default memo(PreviewMarkup); diff --git a/apps/meteor/client/components/gazzodown/blocks/BigEmojiBlock.tsx b/apps/meteor/client/components/gazzodown/blocks/BigEmojiBlock.tsx new file mode 100644 index 000000000000..4c5262fd4ab4 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/blocks/BigEmojiBlock.tsx @@ -0,0 +1,18 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement } from 'react'; + +import BigEmojiElement from '../elements/BigEmojiElement'; + +type BigEmojiBlockProps = { + emojis: MessageParser.Emoji[]; +}; + +const BigEmojiBlock = ({ emojis }: BigEmojiBlockProps): ReactElement => ( + <> + {emojis.map((emoji, index) => ( + <BigEmojiElement key={index} handle={emoji.value.value} /> + ))} + </> +); + +export default BigEmojiBlock; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/Code.tsx b/apps/meteor/client/components/gazzodown/blocks/CodeBlock.tsx similarity index 59% rename from apps/meteor/client/components/Message/MessageBodyRender/Code.tsx rename to apps/meteor/client/components/gazzodown/blocks/CodeBlock.tsx index 49402f74666a..886ec71f7188 100644 --- a/apps/meteor/client/components/Message/MessageBodyRender/Code.tsx +++ b/apps/meteor/client/components/gazzodown/blocks/CodeBlock.tsx @@ -1,8 +1,7 @@ -import { Code as ASTCode } from '@rocket.chat/message-parser'; -import React, { FC, useEffect, useState } from 'react'; +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement, useEffect, useState } from 'react'; import hljs, { register } from '../../../../app/markdown/lib/hljs'; -import CodeLine from './CodeLine'; type hljsResult = { language: string; @@ -12,24 +11,31 @@ type hljsResult = { const isHljsResult = (result: any): result is hljsResult => result?.value; -const Code: FC<ASTCode> = ({ value = [], language }) => { +type CodeBlockProps = { + language?: string; + lines: MessageParser.CodeLine[]; +}; + +const CodeBlock = ({ lines = [], language }: CodeBlockProps): ReactElement => { const [code, setCode] = useState<(JSX.Element | null)[] | { language: string; code: string }>(() => - value.map((block, index) => { + lines.map((block, index) => { switch (block.type) { case 'CODE_LINE': - return <CodeLine key={index} value={block.value} />; + return <div key={index}>{block.value.type === 'PLAIN_TEXT' ? block.value.value : null}</div>; + default: return null; } }), ); + useEffect(() => { !language || language === 'none' - ? setCode(hljs.highlightAuto(value.map((line) => line.value.value).join('\n'))) + ? setCode(hljs.highlightAuto(lines.map((line) => line.value.value).join('\n'))) : register(language).then(() => { - setCode(hljs.highlight(language, value.map((line) => line.value.value).join('\n'))); + setCode(hljs.highlight(language, lines.map((line) => line.value.value).join('\n'))); }); - }, [language, value]); + }, [language, lines]); return ( <code className={`code-colors hljs ${language}`}> @@ -46,4 +52,4 @@ const Code: FC<ASTCode> = ({ value = [], language }) => { ); }; -export default Code; +export default CodeBlock; diff --git a/apps/meteor/client/components/gazzodown/blocks/HeadingBlock.tsx b/apps/meteor/client/components/gazzodown/blocks/HeadingBlock.tsx new file mode 100644 index 000000000000..1d21893f8a12 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/blocks/HeadingBlock.tsx @@ -0,0 +1,23 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement } from 'react'; + +import PlainSpan from '../elements/PlainSpan'; + +type HeadingBlockProps = { + children?: MessageParser.Plain[]; + level?: 1 | 2 | 3 | 4; +}; + +const HeadingBlock = ({ children = [], level = 1 }: HeadingBlockProps): ReactElement => { + const HeadingTag = `h${level}` as const; + + return ( + <HeadingTag> + {children.map((block, index) => ( + <PlainSpan key={index} text={block.value} /> + ))} + </HeadingTag> + ); +}; + +export default HeadingBlock; diff --git a/apps/meteor/client/components/gazzodown/blocks/OrderedListBlock.tsx b/apps/meteor/client/components/gazzodown/blocks/OrderedListBlock.tsx new file mode 100644 index 000000000000..2da9078bbcc6 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/blocks/OrderedListBlock.tsx @@ -0,0 +1,20 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement } from 'react'; + +import InlineElements from '../elements/InlineElements'; + +type OrderedListBlockProps = { + items: MessageParser.ListItem[]; +}; + +const OrderedListBlock = ({ items }: OrderedListBlockProps): ReactElement => ( + <ol> + {items.map(({ value, number }, index) => ( + <li key={index} value={number}> + <InlineElements children={value} /> + </li> + ))} + </ol> +); + +export default OrderedListBlock; diff --git a/apps/meteor/client/components/gazzodown/blocks/ParagraphBlock.tsx b/apps/meteor/client/components/gazzodown/blocks/ParagraphBlock.tsx new file mode 100644 index 000000000000..084f4808a390 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/blocks/ParagraphBlock.tsx @@ -0,0 +1,16 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement } from 'react'; + +import InlineElements from '../elements/InlineElements'; + +type ParagraphBlockProps = { + children: MessageParser.Inlines[]; +}; + +const ParagraphBlock = ({ children }: ParagraphBlockProps): ReactElement => ( + <p> + <InlineElements children={children} /> + </p> +); + +export default ParagraphBlock; diff --git a/apps/meteor/client/components/gazzodown/blocks/PreviewBigEmojiBlock.tsx b/apps/meteor/client/components/gazzodown/blocks/PreviewBigEmojiBlock.tsx new file mode 100644 index 000000000000..1ba958fc9aa2 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/blocks/PreviewBigEmojiBlock.tsx @@ -0,0 +1,18 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement } from 'react'; + +import PreviewEmojiElement from '../elements/PreviewEmojiElement'; + +type PreviewBigEmojiBlockProps = { + emojis: MessageParser.Emoji[]; +}; + +const PreviewBigEmojiBlock = ({ emojis }: PreviewBigEmojiBlockProps): ReactElement => ( + <> + {emojis.map((emoji, index) => ( + <PreviewEmojiElement key={index} handle={emoji.value.value} /> + ))} + </> +); + +export default PreviewBigEmojiBlock; diff --git a/apps/meteor/client/components/gazzodown/blocks/QuoteBlock.tsx b/apps/meteor/client/components/gazzodown/blocks/QuoteBlock.tsx new file mode 100644 index 000000000000..aa2f4c44fec2 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/blocks/QuoteBlock.tsx @@ -0,0 +1,18 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement } from 'react'; + +import ParagraphBlock from './ParagraphBlock'; + +type QuoteBlockProps = { + children: MessageParser.Paragraph[]; +}; + +const QuoteBlock = ({ children }: QuoteBlockProps): ReactElement => ( + <blockquote> + {children.map((paragraph, index) => ( + <ParagraphBlock key={index} children={paragraph.value} /> + ))} + </blockquote> +); + +export default QuoteBlock; diff --git a/apps/meteor/client/components/gazzodown/blocks/TaskListBlock.tsx b/apps/meteor/client/components/gazzodown/blocks/TaskListBlock.tsx new file mode 100644 index 000000000000..1f0cfe8196fe --- /dev/null +++ b/apps/meteor/client/components/gazzodown/blocks/TaskListBlock.tsx @@ -0,0 +1,21 @@ +import { CheckBox } from '@rocket.chat/fuselage'; +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement } from 'react'; + +import InlineElements from '../elements/InlineElements'; + +type TaskListBlockProps = { + tasks: MessageParser.Task[]; +}; + +const TaksListBlock = ({ tasks }: TaskListBlockProps): ReactElement => ( + <ul className='task-list'> + {tasks.map((item, index) => ( + <li key={index}> + <CheckBox checked={item.status} /> <InlineElements children={item.value} /> + </li> + ))} + </ul> +); + +export default TaksListBlock; diff --git a/apps/meteor/client/components/gazzodown/blocks/UnorderedListBlock.tsx b/apps/meteor/client/components/gazzodown/blocks/UnorderedListBlock.tsx new file mode 100644 index 000000000000..42ba4425abff --- /dev/null +++ b/apps/meteor/client/components/gazzodown/blocks/UnorderedListBlock.tsx @@ -0,0 +1,20 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement } from 'react'; + +import InlineElements from '../elements/InlineElements'; + +type UnorderedListBlockProps = { + items: MessageParser.ListItem[]; +}; + +const UnorderedListBlock = ({ items }: UnorderedListBlockProps): ReactElement => ( + <ul> + {items.map((item, index) => ( + <li key={index}> + <InlineElements children={item.value} /> + </li> + ))} + </ul> +); + +export default UnorderedListBlock; diff --git a/apps/meteor/client/components/gazzodown/elements/BigEmojiElement.tsx b/apps/meteor/client/components/gazzodown/elements/BigEmojiElement.tsx new file mode 100644 index 000000000000..0387d1532ca8 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/BigEmojiElement.tsx @@ -0,0 +1,32 @@ +import { MessageEmoji } from '@rocket.chat/fuselage'; +import React, { ReactElement, useMemo } from 'react'; + +import { getEmojiClassNameAndDataTitle } from '../../../lib/utils/renderEmoji'; + +type BigEmojiElementProps = { + handle: string; +}; + +const BigEmojiElement = ({ handle }: BigEmojiElementProps): ReactElement => { + const emojiProps = useMemo(() => { + const props = getEmojiClassNameAndDataTitle(`:${handle}:`); + + if (!props.className && !props.name) { + return undefined; + } + + return props; + }, [handle]); + + if (!emojiProps) { + return <>:${handle}:</>; + } + + return ( + <MessageEmoji big {...emojiProps}> + :{handle}: + </MessageEmoji> + ); +}; + +export default BigEmojiElement; diff --git a/apps/meteor/client/components/gazzodown/elements/BoldSpan.tsx b/apps/meteor/client/components/gazzodown/elements/BoldSpan.tsx new file mode 100644 index 000000000000..14e079ebe6b2 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/BoldSpan.tsx @@ -0,0 +1,36 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement } from 'react'; + +import ItalicSpan from './ItalicSpan'; +import LinkSpan from './LinkSpan'; +import PlainSpan from './PlainSpan'; +import StrikeSpan from './StrikeSpan'; + +type BoldSpanProps = { + children: (MessageParser.Link | MessageParser.MarkupExcluding<MessageParser.Bold>)[]; +}; + +const BoldSpan = ({ children }: BoldSpanProps): ReactElement => ( + <strong> + {children.map((block, index) => { + switch (block.type) { + case 'LINK': + return <LinkSpan key={index} href={block.value.src.value} label={block.value.label} />; + + case 'PLAIN_TEXT': + return <PlainSpan key={index} text={block.value} />; + + case 'STRIKE': + return <StrikeSpan key={index} children={block.value} />; + + case 'ITALIC': + return <ItalicSpan key={index} children={block.value} />; + + default: + return null; + } + })} + </strong> +); + +export default BoldSpan; diff --git a/apps/meteor/client/components/gazzodown/elements/ChannelMentionElement.tsx b/apps/meteor/client/components/gazzodown/elements/ChannelMentionElement.tsx new file mode 100644 index 000000000000..2d17fbddc952 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/ChannelMentionElement.tsx @@ -0,0 +1,25 @@ +import React, { memo, ReactElement } from 'react'; + +import { useMessageBodyChannelMentions, useMessageBodyChannelMentionClick } from '../MarkupInteractionContext'; + +type ChannelMentionElementProps = { + mention: string; +}; + +const ChannelMentionElement = ({ mention }: ChannelMentionElementProps): ReactElement => { + const mentions = useMessageBodyChannelMentions(); + const mentioned = mentions.find((mentioned) => mentioned.name === mention); + const onChannelMentionClick = useMessageBodyChannelMentionClick(); + + if (!mentioned) { + return <>#{mention}</>; + } + + return ( + <span className='mention-link mention-link--room' onClick={onChannelMentionClick(mentioned._id)}> + #{mention} + </span> + ); +}; + +export default memo(ChannelMentionElement); diff --git a/apps/meteor/client/components/gazzodown/elements/CodeElement.tsx b/apps/meteor/client/components/gazzodown/elements/CodeElement.tsx new file mode 100644 index 000000000000..3a0b8e5cd1c5 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/CodeElement.tsx @@ -0,0 +1,15 @@ +import React, { ReactElement } from 'react'; + +import PlainSpan from './PlainSpan'; + +type CodeElementProps = { + code: string; +}; + +const CodeElement = ({ code }: CodeElementProps): ReactElement => ( + <code className='code-colors inline'> + <PlainSpan text={code} /> + </code> +); + +export default CodeElement; diff --git a/apps/meteor/client/components/gazzodown/elements/ColorElement.tsx b/apps/meteor/client/components/gazzodown/elements/ColorElement.tsx new file mode 100644 index 000000000000..152486b01f7d --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/ColorElement.tsx @@ -0,0 +1,26 @@ +import React, { ReactElement } from 'react'; + +type ColorElementProps = { + r: number; + g: number; + b: number; + a: number; +}; + +const ColorElement = ({ r, g, b, a }: ColorElementProps): ReactElement => ( + <span> + <span + style={{ + backgroundColor: `rgba(${r}, ${g}, ${b}, ${(a / 255) * 100}%)`, + display: 'inline-block', + width: '1em', + height: '1em', + verticalAlign: 'middle', + marginInlineEnd: '0.5em', + }} + /> + rgba({r}, {g}, {b}, {(a / 255) * 100}%) + </span> +); + +export default ColorElement; diff --git a/apps/meteor/client/components/gazzodown/elements/EmojiElement.tsx b/apps/meteor/client/components/gazzodown/elements/EmojiElement.tsx new file mode 100644 index 000000000000..a8c5f15db5bf --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/EmojiElement.tsx @@ -0,0 +1,28 @@ +import { MessageEmoji } from '@rocket.chat/fuselage'; +import React, { ReactElement, useMemo } from 'react'; + +import { getEmojiClassNameAndDataTitle } from '../../../lib/utils/renderEmoji'; + +type EmojiElementProps = { + handle: string; +}; + +const EmojiElement = ({ handle }: EmojiElementProps): ReactElement => { + const emojiProps = useMemo(() => { + const props = getEmojiClassNameAndDataTitle(`:${handle}:`); + + if (!props.className && !props.name) { + return undefined; + } + + return props; + }, [handle]); + + if (!emojiProps) { + return <>:${handle}:</>; + } + + return <MessageEmoji {...emojiProps}>:{handle}:</MessageEmoji>; +}; + +export default EmojiElement; diff --git a/apps/meteor/client/components/gazzodown/elements/ImageElement.tsx b/apps/meteor/client/components/gazzodown/elements/ImageElement.tsx new file mode 100644 index 000000000000..de1c4e501046 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/ImageElement.tsx @@ -0,0 +1,45 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement, useMemo } from 'react'; + +const flattenMarkup = (markup: MessageParser.Markup | MessageParser.Link): string => { + switch (markup.type) { + case 'PLAIN_TEXT': + return markup.value; + + case 'ITALIC': + case 'BOLD': + case 'STRIKE': + return markup.value.map(flattenMarkup).join(''); + + case 'LINK': { + const label = flattenMarkup(markup.value.label); + const href = markup.value.src.value; + + return label ? `${label} (${href})` : href; + } + + default: + return ''; + } +}; + +const style = { + maxWidth: '100%', +}; + +type ImageElementProps = { + src: string; + alt: MessageParser.Markup; +}; + +const ImageElement = ({ src, alt }: ImageElementProps): ReactElement => { + const plainAlt = useMemo(() => flattenMarkup(alt), [alt]); + + return ( + <a href={src} target='_blank' rel='noopener noreferrer' title={plainAlt}> + <img src={src} data-title={src} alt={plainAlt} style={style} /> + </a> + ); +}; + +export default ImageElement; diff --git a/apps/meteor/client/components/gazzodown/elements/InlineElements.tsx b/apps/meteor/client/components/gazzodown/elements/InlineElements.tsx new file mode 100644 index 000000000000..c60a3c6c1d57 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/InlineElements.tsx @@ -0,0 +1,64 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement } from 'react'; + +import BoldSpan from './BoldSpan'; +import ChannelMentionElement from './ChannelMentionElement'; +import CodeElement from './CodeElement'; +import ColorElement from './ColorElement'; +import EmojiElement from './EmojiElement'; +import ImageElement from './ImageElement'; +import ItalicSpan from './ItalicSpan'; +import LinkSpan from './LinkSpan'; +import PlainSpan from './PlainSpan'; +import StrikeSpan from './StrikeSpan'; +import UserMentionElement from './UserMentionElement'; + +type InlineElementsProps = { + children: MessageParser.Inlines[]; +}; + +const InlineElements = ({ children }: InlineElementsProps): ReactElement => ( + <> + {children.map((child, index) => { + switch (child.type) { + case 'BOLD': + return <BoldSpan key={index} children={child.value} />; + + case 'STRIKE': + return <StrikeSpan key={index} children={child.value} />; + + case 'ITALIC': + return <ItalicSpan key={index} children={child.value} />; + + case 'LINK': + return <LinkSpan key={index} href={child.value.src.value} label={child.value.label} />; + + case 'PLAIN_TEXT': + return <PlainSpan key={index} text={child.value} />; + + case 'IMAGE': + return <ImageElement key={index} src={child.value.src.value} alt={child.value.label} />; + + case 'MENTION_USER': + return <UserMentionElement key={index} mention={child.value.value} />; + + case 'MENTION_CHANNEL': + return <ChannelMentionElement key={index} mention={child.value.value} />; + + case 'INLINE_CODE': + return <CodeElement key={index} code={child.value.value} />; + + case 'EMOJI': + return <EmojiElement key={index} handle={child.value.value} />; + + case 'COLOR': + return <ColorElement key={index} {...child.value} />; + + default: + return null; + } + })} + </> +); + +export default InlineElements; diff --git a/apps/meteor/client/components/gazzodown/elements/ItalicSpan.tsx b/apps/meteor/client/components/gazzodown/elements/ItalicSpan.tsx new file mode 100644 index 000000000000..f1e06950ca0b --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/ItalicSpan.tsx @@ -0,0 +1,36 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement } from 'react'; + +import BoldSpan from './BoldSpan'; +import LinkSpan from './LinkSpan'; +import PlainSpan from './PlainSpan'; +import StrikeSpan from './StrikeSpan'; + +type ItalicSpanProps = { + children: (MessageParser.Link | MessageParser.MarkupExcluding<MessageParser.Italic>)[]; +}; + +const ItalicSpan = ({ children }: ItalicSpanProps): ReactElement => ( + <i> + {children.map((block, index) => { + switch (block.type) { + case 'LINK': + return <LinkSpan key={index} href={block.value.src.value} label={block.value.label} />; + + case 'PLAIN_TEXT': + return <PlainSpan key={index} text={block.value} />; + + case 'STRIKE': + return <StrikeSpan key={index} children={block.value} />; + + case 'BOLD': + return <BoldSpan key={index} children={block.value} />; + + default: + return null; + } + })} + </i> +); + +export default ItalicSpan; diff --git a/apps/meteor/client/components/gazzodown/elements/LinkSpan.tsx b/apps/meteor/client/components/gazzodown/elements/LinkSpan.tsx new file mode 100644 index 000000000000..581ddc9970dc --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/LinkSpan.tsx @@ -0,0 +1,42 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement } from 'react'; + +import { baseURI } from '../../../lib/baseURI'; +import BoldSpan from './BoldSpan'; +import ItalicSpan from './ItalicSpan'; +import PlainSpan from './PlainSpan'; +import StrikeSpan from './StrikeSpan'; + +type LinkSpanProps = { + href: string; + label: MessageParser.Markup; +}; + +const LinkSpan = ({ href, label }: LinkSpanProps): ReactElement => { + const attrs = href.indexOf(baseURI) !== 0 ? { rel: 'noopener noreferrer', target: '_blank' } : {}; + + return ( + <a href={href} data-title={href} {...attrs}> + {((block: MessageParser.Markup): JSX.Element | string | null => { + switch (block.type) { + case 'PLAIN_TEXT': + return <PlainSpan text={block.value} />; + + case 'STRIKE': + return <StrikeSpan children={block.value} />; + + case 'ITALIC': + return <ItalicSpan children={block.value} />; + + case 'BOLD': + return <BoldSpan children={block.value} />; + + default: + return null; + } + })(label)} + </a> + ); +}; + +export default LinkSpan; diff --git a/apps/meteor/client/components/Message/MessageBodyRender/PlainText.tsx b/apps/meteor/client/components/gazzodown/elements/PlainSpan.tsx similarity index 61% rename from apps/meteor/client/components/Message/MessageBodyRender/PlainText.tsx rename to apps/meteor/client/components/gazzodown/elements/PlainSpan.tsx index 5c0cd2c4011e..a82d13b9cbb6 100644 --- a/apps/meteor/client/components/Message/MessageBodyRender/PlainText.tsx +++ b/apps/meteor/client/components/gazzodown/elements/PlainSpan.tsx @@ -1,14 +1,13 @@ -import { Plain as ASTPlain } from '@rocket.chat/message-parser'; -import React, { FC, memo } from 'react'; +import React, { memo, ReactElement } from 'react'; import { useMessageListHighlights, useMessageListKatex } from '../../../views/room/MessageList/contexts/MessageListContext'; import CustomText from '../../CustomText'; -type PlainTextType = { - value: ASTPlain['value']; +type PlainSpanProps = { + text: string; }; -const PlainText: FC<PlainTextType> = ({ value: text }) => { +const PlainSpan = ({ text }: PlainSpanProps): ReactElement => { const highlights = useMessageListHighlights(); const katex = useMessageListKatex(); @@ -19,4 +18,4 @@ const PlainText: FC<PlainTextType> = ({ value: text }) => { return <>{text}</>; }; -export default memo(PlainText); +export default memo(PlainSpan); diff --git a/apps/meteor/client/components/gazzodown/elements/PreviewColorElement.tsx b/apps/meteor/client/components/gazzodown/elements/PreviewColorElement.tsx new file mode 100644 index 000000000000..10f36d4f2075 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/PreviewColorElement.tsx @@ -0,0 +1,33 @@ +import React, { ReactElement } from 'react'; + +const toHexByte = (value: number): string => value.toString(16).padStart(2, '0'); + +type PreviewColorElementProps = { + r: number; + g: number; + b: number; + a: number; +}; + +const PreviewColorElement = ({ r, g, b, a }: PreviewColorElementProps): ReactElement => { + if (a === 255) { + return ( + <> + #{toHexByte(r)} + {toHexByte(g)} + {toHexByte(b)} + </> + ); + } + + return ( + <> + #{toHexByte(r)} + {toHexByte(g)} + {toHexByte(b)} + {toHexByte(a)} + </> + ); +}; + +export default PreviewColorElement; diff --git a/apps/meteor/client/components/gazzodown/elements/PreviewEmojiElement.tsx b/apps/meteor/client/components/gazzodown/elements/PreviewEmojiElement.tsx new file mode 100644 index 000000000000..4976666f4707 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/PreviewEmojiElement.tsx @@ -0,0 +1,28 @@ +import { ThreadMessageEmoji } from '@rocket.chat/fuselage'; +import React, { ReactElement, useMemo } from 'react'; + +import { getEmojiClassNameAndDataTitle } from '../../../lib/utils/renderEmoji'; + +type PreviewEmojiElementProps = { + handle: string; +}; + +const PreviewEmojiElement = ({ handle }: PreviewEmojiElementProps): ReactElement => { + const emojiProps = useMemo(() => { + const props = getEmojiClassNameAndDataTitle(`:${handle}:`); + + if (!props.className && !props.name) { + return undefined; + } + + return props; + }, [handle]); + + if (!emojiProps) { + return <>:${handle}:</>; + } + + return <ThreadMessageEmoji {...emojiProps}>:{handle}:</ThreadMessageEmoji>; +}; + +export default PreviewEmojiElement; diff --git a/apps/meteor/client/components/gazzodown/elements/PreviewInlineElements.tsx b/apps/meteor/client/components/gazzodown/elements/PreviewInlineElements.tsx new file mode 100644 index 000000000000..096fa7438de5 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/PreviewInlineElements.tsx @@ -0,0 +1,51 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { Fragment, ReactElement } from 'react'; + +import PreviewColorElement from './PreviewColorElement'; +import PreviewEmojiElement from './PreviewEmojiElement'; + +type PreviewInlineElementsProps = { + children: MessageParser.Inlines[]; +}; + +const PreviewInlineElements = ({ children }: PreviewInlineElementsProps): ReactElement => ( + <> + {children.map((child, index) => { + switch (child.type) { + case 'BOLD': + case 'ITALIC': + case 'STRIKE': + return <PreviewInlineElements key={index} children={child.value} />; + + case 'LINK': + return <PreviewInlineElements key={index} children={[child.value.label]} />; + + case 'PLAIN_TEXT': + return <Fragment key={index} children={child.value} />; + + case 'IMAGE': + return <PreviewInlineElements key={index} children={[child.value.label]} />; + + case 'MENTION_USER': + return <Fragment key={index}>@{child.value.value}</Fragment>; + + case 'MENTION_CHANNEL': + return <Fragment key={index}>#{child.value.value}</Fragment>; + + case 'INLINE_CODE': + return <Fragment key={index} children={child.value.value} />; + + case 'EMOJI': + return <PreviewEmojiElement key={index} handle={child.value.value} />; + + case 'COLOR': + return <PreviewColorElement key={index} {...child.value} />; + + default: + return null; + } + })} + </> +); + +export default PreviewInlineElements; diff --git a/apps/meteor/client/components/gazzodown/elements/StrikeSpan.tsx b/apps/meteor/client/components/gazzodown/elements/StrikeSpan.tsx new file mode 100644 index 000000000000..b70ad7ec1baa --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/StrikeSpan.tsx @@ -0,0 +1,36 @@ +import * as MessageParser from '@rocket.chat/message-parser'; +import React, { ReactElement } from 'react'; + +import BoldSpan from './BoldSpan'; +import ItalicSpan from './ItalicSpan'; +import LinkSpan from './LinkSpan'; +import PlainSpan from './PlainSpan'; + +type StrikeSpanProps = { + children: (MessageParser.Link | MessageParser.MarkupExcluding<MessageParser.Strike>)[]; +}; + +const StrikeSpan = ({ children }: StrikeSpanProps): ReactElement => ( + <del> + {children.map((block, index) => { + switch (block.type) { + case 'LINK': + return <LinkSpan key={index} href={block.value.src.value} label={block.value.label} />; + + case 'PLAIN_TEXT': + return <PlainSpan key={index} text={block.value} />; + + case 'BOLD': + return <BoldSpan key={index} children={block.value} />; + + case 'ITALIC': + return <ItalicSpan key={index} children={block.value} />; + + default: + return null; + } + })} + </del> +); + +export default StrikeSpan; diff --git a/apps/meteor/client/components/gazzodown/elements/UserMentionElement.tsx b/apps/meteor/client/components/gazzodown/elements/UserMentionElement.tsx new file mode 100644 index 000000000000..8a0b63033c25 --- /dev/null +++ b/apps/meteor/client/components/gazzodown/elements/UserMentionElement.tsx @@ -0,0 +1,43 @@ +import { useUserId } from '@rocket.chat/ui-contexts'; +import React, { memo, ReactElement, useMemo } from 'react'; + +import { useMessageBodyUserMentions, useMessageBodyMentionClick } from '../MarkupInteractionContext'; + +type UserMentionElementProps = { + mention: string; +}; + +const UserMentionElement = ({ mention }: UserMentionElementProps): ReactElement => { + const uid = useUserId(); + const mentions = useMessageBodyUserMentions(); + const mentioned = mentions.find((mentioned) => mentioned.username === mention); + const onUserMentionClick = useMessageBodyMentionClick(); + + const classNames = useMemo(() => { + if (mention === 'all') { + return 'mention-link mention-link--all mention-link--group'; + } + + if (mention === 'here') { + return 'mention-link mention-link--here mention-link--group'; + } + + if (mentioned && mentioned._id === uid) { + return 'mention-link mention-link--me mention-link--user'; + } + + return 'mention-link mention-link--user'; + }, [mention, mentioned, uid]); + + if (!mentioned) { + return <>@{mention}</>; + } + + return ( + <span className={classNames} onClick={mention !== 'all' && mention !== 'here' ? onUserMentionClick(mention) : undefined}> + {mentioned.name || mention} + </span> + ); +}; + +export default memo(UserMentionElement); diff --git a/apps/meteor/client/components/Message/Attachments/ActionAttachtment.tsx b/apps/meteor/client/components/message/Attachments/ActionAttachtment.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/ActionAttachtment.tsx rename to apps/meteor/client/components/message/Attachments/ActionAttachtment.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Action.tsx b/apps/meteor/client/components/message/Attachments/Attachment/Action.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/Action.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/Action.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Attachment.tsx b/apps/meteor/client/components/message/Attachments/Attachment/Attachment.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/Attachment.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/Attachment.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Author.tsx b/apps/meteor/client/components/message/Attachments/Attachment/Author.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/Author.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/Author.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/AuthorAvatar.tsx b/apps/meteor/client/components/message/Attachments/Attachment/AuthorAvatar.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/AuthorAvatar.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/AuthorAvatar.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/AuthorName.tsx b/apps/meteor/client/components/message/Attachments/Attachment/AuthorName.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/AuthorName.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/AuthorName.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Block.tsx b/apps/meteor/client/components/message/Attachments/Attachment/Block.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/Block.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/Block.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Collapse.tsx b/apps/meteor/client/components/message/Attachments/Attachment/Collapse.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/Collapse.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/Collapse.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Content.tsx b/apps/meteor/client/components/message/Attachments/Attachment/Content.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/Content.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/Content.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Details.tsx b/apps/meteor/client/components/message/Attachments/Attachment/Details.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/Details.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/Details.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Download.tsx b/apps/meteor/client/components/message/Attachments/Attachment/Download.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/Download.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/Download.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Inner.tsx b/apps/meteor/client/components/message/Attachments/Attachment/Inner.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/Inner.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/Inner.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Row.tsx b/apps/meteor/client/components/message/Attachments/Attachment/Row.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/Row.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/Row.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Size.tsx b/apps/meteor/client/components/message/Attachments/Attachment/Size.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/Size.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/Size.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Text.tsx b/apps/meteor/client/components/message/Attachments/Attachment/Text.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/Text.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/Text.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Thumb.tsx b/apps/meteor/client/components/message/Attachments/Attachment/Thumb.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/Thumb.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/Thumb.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/Title.tsx b/apps/meteor/client/components/message/Attachments/Attachment/Title.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/Title.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/Title.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/TitleLink.tsx b/apps/meteor/client/components/message/Attachments/Attachment/TitleLink.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/TitleLink.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/TitleLink.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachment/index.tsx b/apps/meteor/client/components/message/Attachments/Attachment/index.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachment/index.tsx rename to apps/meteor/client/components/message/Attachments/Attachment/index.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachments.stories.tsx b/apps/meteor/client/components/message/Attachments/Attachments.stories.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachments.stories.tsx rename to apps/meteor/client/components/message/Attachments/Attachments.stories.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Attachments.tsx b/apps/meteor/client/components/message/Attachments/Attachments.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Attachments.tsx rename to apps/meteor/client/components/message/Attachments/Attachments.tsx diff --git a/apps/meteor/client/components/Message/Attachments/DefaultAttachment.tsx b/apps/meteor/client/components/message/Attachments/DefaultAttachment.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/DefaultAttachment.tsx rename to apps/meteor/client/components/message/Attachments/DefaultAttachment.tsx diff --git a/apps/meteor/client/components/Message/Attachments/FieldsAttachment/Field.tsx b/apps/meteor/client/components/message/Attachments/FieldsAttachment/Field.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/FieldsAttachment/Field.tsx rename to apps/meteor/client/components/message/Attachments/FieldsAttachment/Field.tsx diff --git a/apps/meteor/client/components/Message/Attachments/FieldsAttachment/ShortField.tsx b/apps/meteor/client/components/message/Attachments/FieldsAttachment/ShortField.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/FieldsAttachment/ShortField.tsx rename to apps/meteor/client/components/message/Attachments/FieldsAttachment/ShortField.tsx diff --git a/apps/meteor/client/components/Message/Attachments/FieldsAttachment/index.tsx b/apps/meteor/client/components/message/Attachments/FieldsAttachment/index.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/FieldsAttachment/index.tsx rename to apps/meteor/client/components/message/Attachments/FieldsAttachment/index.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Files/AudioAttachment.tsx b/apps/meteor/client/components/message/Attachments/Files/AudioAttachment.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Files/AudioAttachment.tsx rename to apps/meteor/client/components/message/Attachments/Files/AudioAttachment.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Files/GenericFileAttachment.tsx b/apps/meteor/client/components/message/Attachments/Files/GenericFileAttachment.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Files/GenericFileAttachment.tsx rename to apps/meteor/client/components/message/Attachments/Files/GenericFileAttachment.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Files/ImageAttachment.tsx b/apps/meteor/client/components/message/Attachments/Files/ImageAttachment.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Files/ImageAttachment.tsx rename to apps/meteor/client/components/message/Attachments/Files/ImageAttachment.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Files/PDFAttachment.tsx b/apps/meteor/client/components/message/Attachments/Files/PDFAttachment.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Files/PDFAttachment.tsx rename to apps/meteor/client/components/message/Attachments/Files/PDFAttachment.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Files/VideoAttachment.tsx b/apps/meteor/client/components/message/Attachments/Files/VideoAttachment.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Files/VideoAttachment.tsx rename to apps/meteor/client/components/message/Attachments/Files/VideoAttachment.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Files/index.tsx b/apps/meteor/client/components/message/Attachments/Files/index.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Files/index.tsx rename to apps/meteor/client/components/message/Attachments/Files/index.tsx diff --git a/apps/meteor/client/components/Message/Attachments/Item.tsx b/apps/meteor/client/components/message/Attachments/Item.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/Item.tsx rename to apps/meteor/client/components/message/Attachments/Item.tsx diff --git a/apps/meteor/client/components/Message/Attachments/QuoteAttachment.tsx b/apps/meteor/client/components/message/Attachments/QuoteAttachment.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/QuoteAttachment.tsx rename to apps/meteor/client/components/message/Attachments/QuoteAttachment.tsx diff --git a/apps/meteor/client/components/Message/Attachments/components/Image.tsx b/apps/meteor/client/components/message/Attachments/components/Image.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/components/Image.tsx rename to apps/meteor/client/components/message/Attachments/components/Image.tsx diff --git a/apps/meteor/client/components/Message/Attachments/components/ImageBox.tsx b/apps/meteor/client/components/message/Attachments/components/ImageBox.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/components/ImageBox.tsx rename to apps/meteor/client/components/message/Attachments/components/ImageBox.tsx diff --git a/apps/meteor/client/components/Message/Attachments/components/Load.tsx b/apps/meteor/client/components/message/Attachments/components/Load.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/components/Load.tsx rename to apps/meteor/client/components/message/Attachments/components/Load.tsx diff --git a/apps/meteor/client/components/Message/Attachments/components/Retry.stories.tsx b/apps/meteor/client/components/message/Attachments/components/Retry.stories.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/components/Retry.stories.tsx rename to apps/meteor/client/components/message/Attachments/components/Retry.stories.tsx diff --git a/apps/meteor/client/components/Message/Attachments/components/Retry.tsx b/apps/meteor/client/components/message/Attachments/components/Retry.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/components/Retry.tsx rename to apps/meteor/client/components/message/Attachments/components/Retry.tsx diff --git a/apps/meteor/client/components/Message/Attachments/hooks/useCollapse.tsx b/apps/meteor/client/components/message/Attachments/hooks/useCollapse.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/hooks/useCollapse.tsx rename to apps/meteor/client/components/message/Attachments/hooks/useCollapse.tsx diff --git a/apps/meteor/client/components/Message/Attachments/hooks/useLoadImage.tsx b/apps/meteor/client/components/message/Attachments/hooks/useLoadImage.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/hooks/useLoadImage.tsx rename to apps/meteor/client/components/message/Attachments/hooks/useLoadImage.tsx diff --git a/apps/meteor/client/components/Message/Attachments/index.tsx b/apps/meteor/client/components/message/Attachments/index.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/index.tsx rename to apps/meteor/client/components/message/Attachments/index.tsx diff --git a/apps/meteor/client/components/Message/Attachments/providers/AttachmentProvider.tsx b/apps/meteor/client/components/message/Attachments/providers/AttachmentProvider.tsx similarity index 100% rename from apps/meteor/client/components/Message/Attachments/providers/AttachmentProvider.tsx rename to apps/meteor/client/components/message/Attachments/providers/AttachmentProvider.tsx diff --git a/apps/meteor/client/components/Message/MessageActions/Action.tsx b/apps/meteor/client/components/message/MessageActions/Action.tsx similarity index 100% rename from apps/meteor/client/components/Message/MessageActions/Action.tsx rename to apps/meteor/client/components/message/MessageActions/Action.tsx diff --git a/apps/meteor/client/components/Message/MessageActions/Actions.tsx b/apps/meteor/client/components/message/MessageActions/Actions.tsx similarity index 100% rename from apps/meteor/client/components/Message/MessageActions/Actions.tsx rename to apps/meteor/client/components/message/MessageActions/Actions.tsx diff --git a/apps/meteor/client/components/Message/MessageActions/index.tsx b/apps/meteor/client/components/message/MessageActions/index.tsx similarity index 100% rename from apps/meteor/client/components/Message/MessageActions/index.tsx rename to apps/meteor/client/components/message/MessageActions/index.tsx diff --git a/apps/meteor/client/components/Message/MessageEmoji.tsx b/apps/meteor/client/components/message/MessageEmoji.tsx similarity index 60% rename from apps/meteor/client/components/Message/MessageEmoji.tsx rename to apps/meteor/client/components/message/MessageEmoji.tsx index bdb65c9655d8..4a2c226c1cd1 100644 --- a/apps/meteor/client/components/Message/MessageEmoji.tsx +++ b/apps/meteor/client/components/message/MessageEmoji.tsx @@ -4,25 +4,26 @@ import React, { ReactElement } from 'react'; import { getEmojiClassNameAndDataTitle } from '../../lib/utils/renderEmoji'; type MessageEmojiProps = { - emojiHandle: string; // :emoji: + handle: string; big?: boolean; isThreadPreview?: boolean; }; -function MessageEmoji({ emojiHandle, big, isThreadPreview }: MessageEmojiProps): ReactElement { - const emojiProps = getEmojiClassNameAndDataTitle(emojiHandle); +function MessageEmoji({ handle, big, isThreadPreview }: MessageEmojiProps): ReactElement { + handle = `:${handle}:`; + const emojiProps = getEmojiClassNameAndDataTitle(handle); if (!emojiProps.className && !emojiProps.name) { - return <>{emojiHandle}</>; + return <>{handle}</>; } if (isThreadPreview) { - return <ThreadMessageEmoji {...emojiProps}>{emojiHandle}</ThreadMessageEmoji>; + return <ThreadMessageEmoji {...emojiProps}>{handle}</ThreadMessageEmoji>; } return ( <MessageEmojiBase big={big} {...emojiProps}> - {emojiHandle} + {handle} </MessageEmojiBase> ); } diff --git a/apps/meteor/client/components/Message/Metrics/Broadcast.tsx b/apps/meteor/client/components/message/Metrics/Broadcast.tsx similarity index 100% rename from apps/meteor/client/components/Message/Metrics/Broadcast.tsx rename to apps/meteor/client/components/message/Metrics/Broadcast.tsx diff --git a/apps/meteor/client/components/Message/Metrics/Content.tsx b/apps/meteor/client/components/message/Metrics/Content.tsx similarity index 100% rename from apps/meteor/client/components/Message/Metrics/Content.tsx rename to apps/meteor/client/components/message/Metrics/Content.tsx diff --git a/apps/meteor/client/components/Message/Metrics/ContentItem.tsx b/apps/meteor/client/components/message/Metrics/ContentItem.tsx similarity index 100% rename from apps/meteor/client/components/Message/Metrics/ContentItem.tsx rename to apps/meteor/client/components/message/Metrics/ContentItem.tsx diff --git a/apps/meteor/client/components/Message/Metrics/Discussion.tsx b/apps/meteor/client/components/message/Metrics/Discussion.tsx similarity index 100% rename from apps/meteor/client/components/Message/Metrics/Discussion.tsx rename to apps/meteor/client/components/message/Metrics/Discussion.tsx diff --git a/apps/meteor/client/components/Message/Metrics/Metrics.stories.tsx b/apps/meteor/client/components/message/Metrics/Metrics.stories.tsx similarity index 100% rename from apps/meteor/client/components/Message/Metrics/Metrics.stories.tsx rename to apps/meteor/client/components/message/Metrics/Metrics.stories.tsx diff --git a/apps/meteor/client/components/Message/Metrics/Metrics.tsx b/apps/meteor/client/components/message/Metrics/Metrics.tsx similarity index 100% rename from apps/meteor/client/components/Message/Metrics/Metrics.tsx rename to apps/meteor/client/components/message/Metrics/Metrics.tsx diff --git a/apps/meteor/client/components/Message/Metrics/MetricsFollowing.tsx b/apps/meteor/client/components/message/Metrics/MetricsFollowing.tsx similarity index 100% rename from apps/meteor/client/components/Message/Metrics/MetricsFollowing.tsx rename to apps/meteor/client/components/message/Metrics/MetricsFollowing.tsx diff --git a/apps/meteor/client/components/Message/Metrics/MetricsItem.tsx b/apps/meteor/client/components/message/Metrics/MetricsItem.tsx similarity index 100% rename from apps/meteor/client/components/Message/Metrics/MetricsItem.tsx rename to apps/meteor/client/components/message/Metrics/MetricsItem.tsx diff --git a/apps/meteor/client/components/Message/Metrics/MetricsItemIcon.tsx b/apps/meteor/client/components/message/Metrics/MetricsItemIcon.tsx similarity index 100% rename from apps/meteor/client/components/Message/Metrics/MetricsItemIcon.tsx rename to apps/meteor/client/components/message/Metrics/MetricsItemIcon.tsx diff --git a/apps/meteor/client/components/Message/Metrics/MetricsItemLabel.tsx b/apps/meteor/client/components/message/Metrics/MetricsItemLabel.tsx similarity index 100% rename from apps/meteor/client/components/Message/Metrics/MetricsItemLabel.tsx rename to apps/meteor/client/components/message/Metrics/MetricsItemLabel.tsx diff --git a/apps/meteor/client/components/Message/Metrics/Reply.tsx b/apps/meteor/client/components/message/Metrics/Reply.tsx similarity index 100% rename from apps/meteor/client/components/Message/Metrics/Reply.tsx rename to apps/meteor/client/components/message/Metrics/Reply.tsx diff --git a/apps/meteor/client/components/Message/Metrics/Thread.tsx b/apps/meteor/client/components/message/Metrics/Thread.tsx similarity index 100% rename from apps/meteor/client/components/Message/Metrics/Thread.tsx rename to apps/meteor/client/components/message/Metrics/Thread.tsx diff --git a/apps/meteor/client/components/Message/Metrics/index.tsx b/apps/meteor/client/components/message/Metrics/index.tsx similarity index 100% rename from apps/meteor/client/components/Message/Metrics/index.tsx rename to apps/meteor/client/components/message/Metrics/index.tsx diff --git a/apps/meteor/client/components/Message/NotificationStatus/All.tsx b/apps/meteor/client/components/message/NotificationStatus/All.tsx similarity index 100% rename from apps/meteor/client/components/Message/NotificationStatus/All.tsx rename to apps/meteor/client/components/message/NotificationStatus/All.tsx diff --git a/apps/meteor/client/components/Message/NotificationStatus/Me.tsx b/apps/meteor/client/components/message/NotificationStatus/Me.tsx similarity index 100% rename from apps/meteor/client/components/Message/NotificationStatus/Me.tsx rename to apps/meteor/client/components/message/NotificationStatus/Me.tsx diff --git a/apps/meteor/client/components/Message/NotificationStatus/NotificationStatus.tsx b/apps/meteor/client/components/message/NotificationStatus/NotificationStatus.tsx similarity index 100% rename from apps/meteor/client/components/Message/NotificationStatus/NotificationStatus.tsx rename to apps/meteor/client/components/message/NotificationStatus/NotificationStatus.tsx diff --git a/apps/meteor/client/components/Message/NotificationStatus/Unread.tsx b/apps/meteor/client/components/message/NotificationStatus/Unread.tsx similarity index 100% rename from apps/meteor/client/components/Message/NotificationStatus/Unread.tsx rename to apps/meteor/client/components/message/NotificationStatus/Unread.tsx diff --git a/apps/meteor/client/components/Message/NotificationStatus/index.ts b/apps/meteor/client/components/message/NotificationStatus/index.ts similarity index 100% rename from apps/meteor/client/components/Message/NotificationStatus/index.ts rename to apps/meteor/client/components/message/NotificationStatus/index.ts diff --git a/apps/meteor/client/components/Message/Oembed/definitions.ts b/apps/meteor/client/components/message/Oembed/definitions.ts similarity index 100% rename from apps/meteor/client/components/Message/Oembed/definitions.ts rename to apps/meteor/client/components/message/Oembed/definitions.ts diff --git a/apps/meteor/client/components/Message/StatusMessage.tsx b/apps/meteor/client/components/message/StatusMessage.tsx similarity index 100% rename from apps/meteor/client/components/Message/StatusMessage.tsx rename to apps/meteor/client/components/message/StatusMessage.tsx diff --git a/apps/meteor/client/components/Message/helpers/followSyle.ts b/apps/meteor/client/components/message/helpers/followSyle.ts similarity index 100% rename from apps/meteor/client/components/Message/helpers/followSyle.ts rename to apps/meteor/client/components/message/helpers/followSyle.ts diff --git a/apps/meteor/client/components/Message/hooks/useBlockRendered.ts b/apps/meteor/client/components/message/hooks/useBlockRendered.ts similarity index 100% rename from apps/meteor/client/components/Message/hooks/useBlockRendered.ts rename to apps/meteor/client/components/message/hooks/useBlockRendered.ts diff --git a/apps/meteor/client/providers/MeteorProvider.tsx b/apps/meteor/client/providers/MeteorProvider.tsx index 89aef340fdae..8504a59b8b15 100644 --- a/apps/meteor/client/providers/MeteorProvider.tsx +++ b/apps/meteor/client/providers/MeteorProvider.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; -import AttachmentProvider from '../components/Message/Attachments/providers/AttachmentProvider'; +import AttachmentProvider from '../components/message/Attachments/providers/AttachmentProvider'; import AuthorizationProvider from './AuthorizationProvider'; import AvatarUrlProvider from './AvatarUrlProvider'; import { CallProvider } from './CallProvider'; diff --git a/apps/meteor/client/templates.ts b/apps/meteor/client/templates.ts index 36d18aeb84d2..eecf160432d4 100644 --- a/apps/meteor/client/templates.ts +++ b/apps/meteor/client/templates.ts @@ -2,18 +2,18 @@ import { HTML } from 'meteor/htmljs'; import { createTemplateForComponent } from './lib/portals/createTemplateForComponent'; -createTemplateForComponent('MessageActions', () => import('./components/Message/MessageActions')); +createTemplateForComponent('MessageActions', () => import('./components/message/MessageActions')); -createTemplateForComponent('reactAttachments', () => import('./components/Message/Attachments')); +createTemplateForComponent('reactAttachments', () => import('./components/message/Attachments')); -createTemplateForComponent('ThreadMetric', () => import('./components/Message/Metrics/Thread'), { +createTemplateForComponent('ThreadMetric', () => import('./components/message/Metrics/Thread'), { renderContainerView: () => HTML.DIV({ style: 'min-height: 36px;', }), }); -createTemplateForComponent('DiscussionMetric', () => import('./components/Message/Metrics/Discussion'), { +createTemplateForComponent('DiscussionMetric', () => import('./components/message/Metrics/Discussion'), { renderContainerView: () => HTML.DIV({ style: 'min-height: 36px;', @@ -21,10 +21,8 @@ createTemplateForComponent('DiscussionMetric', () => import('./components/Messag }); createTemplateForComponent('MessageList', () => import('./views/room/MessageList/MessageList')); -/** @deprecated */ -createTemplateForComponent('MessageBody', () => import('./components/Message/MessageBodyRender')); -createTemplateForComponent('BroadCastMetric', () => import('./components/Message/Metrics/Broadcast')); +createTemplateForComponent('BroadCastMetric', () => import('./components/message/Metrics/Broadcast')); createTemplateForComponent( 'Checkbox', diff --git a/apps/meteor/client/views/blocks/MessageBlock.js b/apps/meteor/client/views/blocks/MessageBlock.js index 035f09eb1582..85e6fc577ab1 100644 --- a/apps/meteor/client/views/blocks/MessageBlock.js +++ b/apps/meteor/client/views/blocks/MessageBlock.js @@ -3,7 +3,7 @@ import { UiKitMessage, UiKitComponent, kitContext, messageParser } from '@rocket import React from 'react'; import * as ActionManager from '../../../app/ui-message/client/ActionManager'; -import { useBlockRendered } from '../../components/Message/hooks/useBlockRendered'; +import { useBlockRendered } from '../../components/message/hooks/useBlockRendered'; import { renderMessageBody } from '../../lib/utils/renderMessageBody'; import './textParsers'; diff --git a/apps/meteor/client/views/room/MessageList/components/MessageContent.tsx b/apps/meteor/client/views/room/MessageList/components/MessageContent.tsx index d19900d7b51b..d0e4fa98290c 100644 --- a/apps/meteor/client/views/room/MessageList/components/MessageContent.tsx +++ b/apps/meteor/client/views/room/MessageList/components/MessageContent.tsx @@ -5,11 +5,11 @@ import { useTranslation, useUserId, TranslationKey } from '@rocket.chat/ui-conte import React, { FC, memo } from 'react'; import { isE2EEMessage } from '../../../../../lib/isE2EEMessage'; -import Attachments from '../../../../components/Message/Attachments'; -import MessageActions from '../../../../components/Message/MessageActions'; -import BroadcastMetric from '../../../../components/Message/Metrics/Broadcast'; -import DiscussionMetric from '../../../../components/Message/Metrics/Discussion'; -import ThreadMetric from '../../../../components/Message/Metrics/Thread'; +import Attachments from '../../../../components/message/Attachments'; +import MessageActions from '../../../../components/message/MessageActions'; +import BroadcastMetric from '../../../../components/message/Metrics/Broadcast'; +import DiscussionMetric from '../../../../components/message/Metrics/Discussion'; +import ThreadMetric from '../../../../components/message/Metrics/Thread'; import { useUserData } from '../../../../hooks/useUserData'; import { UserPresence } from '../../../../lib/presence'; import MessageBlockUiKit from '../../../blocks/MessageBlock'; diff --git a/apps/meteor/client/views/room/MessageList/components/MessageContentBody.tsx b/apps/meteor/client/views/room/MessageList/components/MessageContentBody.tsx index bace0b3af3e8..30e91f803638 100644 --- a/apps/meteor/client/views/room/MessageList/components/MessageContentBody.tsx +++ b/apps/meteor/client/views/room/MessageList/components/MessageContentBody.tsx @@ -1,28 +1,72 @@ -/* eslint-disable complexity */ import { IMessage } from '@rocket.chat/core-typings'; -import React, { FC, memo } from 'react'; +import { css } from '@rocket.chat/css-in-js'; +import { Box } from '@rocket.chat/fuselage'; +import colors from '@rocket.chat/fuselage-tokens/colors'; +import React, { ReactElement } from 'react'; -import MessageBodyRender from '../../../../components/Message/MessageBodyRender'; +import Markup from '../../../../components/gazzodown/Markup'; +import { MarkupInteractionContext } from '../../../../components/gazzodown/MarkupInteractionContext'; import { useMessageActions } from '../../contexts/MessageContext'; import { useParsedMessage } from '../hooks/useParsedMessage'; -const MessageContentBody: FC<{ message: IMessage; isThreadPreview?: boolean }> = ({ message, isThreadPreview }) => { +type MessageContentBodyProps = { + message: IMessage; +}; + +const MessageContentBody = ({ message }: MessageContentBodyProps): ReactElement => { + const tokens = useParsedMessage(message); + const { actions: { openRoom, openUserCard }, } = useMessageActions(); - const tokens = useParsedMessage(message); - return ( - <MessageBodyRender - onUserMentionClick={openUserCard} - onChannelMentionClick={openRoom} - mentions={message?.mentions || []} - channels={message?.channels || []} - tokens={tokens} - isThreadPreview={isThreadPreview} - /> + <Box + className={css` + > blockquote { + padding-inline: 8px; + border-radius: 2px; + border-width: 2px; + border-style: solid; + background-color: var(--rcx-color-neutral-100, ${colors.n100}); + border-color: var(--rcx-color-neutral-200, ${colors.n200}); + border-inline-start-color: var(--rcx-color-neutral-600, ${colors.n600}); + + &:hover, + &:focus { + background-color: var(--rcx-color-neutral-200, ${colors.n200}); + border-color: var(--rcx-color-neutral-300, ${colors.n300}); + border-inline-start-color: var(--rcx-color-neutral-600, ${colors.n600}); + } + } + + > ul.task-list { + > li::before { + display: none; + } + + > li > .rcx-check-box > .rcx-check-box__input:focus + .rcx-check-box__fake { + z-index: 1; + } + + list-style: none; + margin-inline-start: 0; + padding-inline-start: 0; + } + `} + > + <MarkupInteractionContext.Provider + value={{ + mentions: message?.mentions ?? [], + channels: message?.channels ?? [], + onUserMentionClick: openUserCard, + onChannelMentionClick: openRoom, + }} + > + <Markup tokens={tokens} /> + </MarkupInteractionContext.Provider> + </Box> ); }; -export default memo(MessageContentBody); +export default MessageContentBody; diff --git a/apps/meteor/client/views/room/MessageList/components/MessageSystem.tsx b/apps/meteor/client/views/room/MessageList/components/MessageSystem.tsx index 0f67fbd36bb3..651ca7715bad 100644 --- a/apps/meteor/client/views/room/MessageList/components/MessageSystem.tsx +++ b/apps/meteor/client/views/room/MessageList/components/MessageSystem.tsx @@ -14,9 +14,9 @@ import { TranslationKey, useTranslation } from '@rocket.chat/ui-contexts'; import React, { FC, memo } from 'react'; import { MessageTypes } from '../../../../../app/ui-utils/client'; -import Attachments from '../../../../components/Message/Attachments'; -import MessageActions from '../../../../components/Message/MessageActions'; import UserAvatar from '../../../../components/avatar/UserAvatar'; +import Attachments from '../../../../components/message/Attachments'; +import MessageActions from '../../../../components/message/MessageActions'; import { useUserData } from '../../../../hooks/useUserData'; import { getUserDisplayName } from '../../../../lib/getUserDisplayName'; import { UserPresence } from '../../../../lib/presence'; diff --git a/apps/meteor/client/views/room/MessageList/components/ThreadMessagePreview.tsx b/apps/meteor/client/views/room/MessageList/components/ThreadMessagePreview.tsx index 72808129e822..8ab8cf934b9c 100644 --- a/apps/meteor/client/views/room/MessageList/components/ThreadMessagePreview.tsx +++ b/apps/meteor/client/views/room/MessageList/components/ThreadMessagePreview.tsx @@ -20,7 +20,7 @@ import { useMessageActions } from '../../contexts/MessageContext'; import { useIsSelecting, useToggleSelect, useIsSelectedMessage, useCountSelected } from '../contexts/SelectedMessagesContext'; import { useMessageBody } from '../hooks/useMessageBody'; import { useParentMessage } from '../hooks/useParentMessage'; -import MessageContentBody from './MessageContentBody'; +import ThreadMessagePreviewBody from './ThreadMessagePreviewBody'; export const ThreadMessagePreview: FC<{ message: IThreadMessage; sequential: boolean }> = ({ message, sequential, ...props }) => { const { @@ -48,7 +48,13 @@ export const ThreadMessagePreview: FC<{ message: IThreadMessage; sequential: boo <ThreadMessageIconThread /> </ThreadMessageLeftContainer> <ThreadMessageContainer> - <ThreadMessageOrigin>{parentMessage.phase === AsyncStatePhase.RESOLVED ? body : <Skeleton />}</ThreadMessageOrigin> + <ThreadMessageOrigin> + {parentMessage.phase === AsyncStatePhase.RESOLVED ? ( + <ThreadMessagePreviewBody message={{ ...parentMessage.value, msg: body }} /> + ) : ( + <Skeleton /> + )} + </ThreadMessageOrigin> <ThreadMessageUnfollow /> </ThreadMessageContainer> </ThreadMessageRow> @@ -59,9 +65,7 @@ export const ThreadMessagePreview: FC<{ message: IThreadMessage; sequential: boo {isSelecting && <CheckBox checked={isSelected} onChange={toggleSelected} />} </ThreadMessageLeftContainer> <ThreadMessageContainer> - <ThreadMessageBody> - {message.ignored ? t('Message_Ignored') : <MessageContentBody isThreadPreview message={message} />} - </ThreadMessageBody> + <ThreadMessageBody>{message.ignored ? t('Message_Ignored') : <ThreadMessagePreviewBody message={message} />}</ThreadMessageBody> </ThreadMessageContainer> </ThreadMessageRow> </ThreadMessageTemplate> diff --git a/apps/meteor/client/views/room/MessageList/components/ThreadMessagePreviewBody.tsx b/apps/meteor/client/views/room/MessageList/components/ThreadMessagePreviewBody.tsx new file mode 100644 index 000000000000..8909d3a606a4 --- /dev/null +++ b/apps/meteor/client/views/room/MessageList/components/ThreadMessagePreviewBody.tsx @@ -0,0 +1,17 @@ +import { IMessage } from '@rocket.chat/core-typings'; +import React, { ReactElement } from 'react'; + +import PreviewMarkup from '../../../../components/gazzodown/PreviewMarkup'; +import { useParsedMessage } from '../hooks/useParsedMessage'; + +type ThreadMessagePreviewBodyProps = { + message: IMessage; +}; + +const ThreadMessagePreviewBody = ({ message }: ThreadMessagePreviewBodyProps): ReactElement => { + const tokens = useParsedMessage(message); + + return <PreviewMarkup tokens={tokens} />; +}; + +export default ThreadMessagePreviewBody; diff --git a/apps/meteor/client/views/room/MessageList/components/UrlPreview/OEmbedCollapseable.tsx b/apps/meteor/client/views/room/MessageList/components/UrlPreview/OEmbedCollapseable.tsx index 3033c1901e8f..359336862f4c 100644 --- a/apps/meteor/client/views/room/MessageList/components/UrlPreview/OEmbedCollapseable.tsx +++ b/apps/meteor/client/views/room/MessageList/components/UrlPreview/OEmbedCollapseable.tsx @@ -2,7 +2,7 @@ import { MessageGenericPreview, Box } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement, ReactNode } from 'react'; -import { useCollapse } from '../../../../../components/Message/Attachments/hooks/useCollapse'; +import { useCollapse } from '../../../../../components/message/Attachments/hooks/useCollapse'; import OEmbedPreviewContent from './OEmbedPreviewContent'; import type { PreviewMetadata } from './PreviewList'; diff --git a/apps/meteor/client/views/room/MessageList/components/UrlPreview/UrlPreview.tsx b/apps/meteor/client/views/room/MessageList/components/UrlPreview/UrlPreview.tsx index 6f2b37efb20b..bfd57b453349 100644 --- a/apps/meteor/client/views/room/MessageList/components/UrlPreview/UrlPreview.tsx +++ b/apps/meteor/client/views/room/MessageList/components/UrlPreview/UrlPreview.tsx @@ -2,7 +2,7 @@ import { Box } from '@rocket.chat/fuselage'; import { useAttachmentAutoLoadEmbedMedia, useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement } from 'react'; -import { useCollapse } from '../../../../../components/Message/Attachments/hooks/useCollapse'; +import { useCollapse } from '../../../../../components/message/Attachments/hooks/useCollapse'; import type { UrlPreview as UrlPreviewType } from './PreviewList'; import UrlPreviewResolver from './UrlPreviewResolver'; diff --git a/apps/meteor/client/views/room/MessageList/hooks/useParsedMessage.ts b/apps/meteor/client/views/room/MessageList/hooks/useParsedMessage.ts index 2d6fa52ba639..cbde9ae437f5 100644 --- a/apps/meteor/client/views/room/MessageList/hooks/useParsedMessage.ts +++ b/apps/meteor/client/views/room/MessageList/hooks/useParsedMessage.ts @@ -2,7 +2,7 @@ import { IMessage } from '@rocket.chat/core-typings'; import { MarkdownAST, parser } from '@rocket.chat/message-parser'; import { useMemo } from 'react'; -export function useParsedMessage(message: IMessage): MarkdownAST { +export function useParsedMessage(message: Pick<IMessage, 'md' | 'msg'>): MarkdownAST { return useMemo(() => { if (message.md) { return message.md; diff --git a/apps/meteor/client/views/room/contextualBar/Threads/components/Message.js b/apps/meteor/client/views/room/contextualBar/Threads/components/Message.js index 35cae5855b8c..7658bf1e13a3 100644 --- a/apps/meteor/client/views/room/contextualBar/Threads/components/Message.js +++ b/apps/meteor/client/views/room/contextualBar/Threads/components/Message.js @@ -1,10 +1,10 @@ import { Button, Icon, Message, Box } from '@rocket.chat/fuselage'; import React from 'react'; -import * as NotificationStatus from '../../../../../components/Message/NotificationStatus'; -import { followStyle, anchor } from '../../../../../components/Message/helpers/followSyle'; import RawText from '../../../../../components/RawText'; import UserAvatar from '../../../../../components/avatar/UserAvatar'; +import * as NotificationStatus from '../../../../../components/message/NotificationStatus'; +import { followStyle, anchor } from '../../../../../components/message/helpers/followSyle'; function isIterable(obj) { // checks for null and undefined From 264f55098da44ac43438a60084ab511d25c38445 Mon Sep 17 00:00:00 2001 From: Douglas Fabris <devfabris@gmail.com> Date: Mon, 6 Jun 2022 22:59:25 -0300 Subject: [PATCH 6/8] [NEW] Fuselage ToastBar (#25583) Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com> Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz> --- .yarnrc.yml | 1 + apps/meteor/client/lib/utils/handleError.ts | 21 +- .../providers/ToastMessagesProvider.tsx | 25 +- apps/meteor/package.json | 1 + .../tests/e2e/01-forgot-password.spec.ts | 5 +- apps/meteor/tests/e2e/03-login.spec.ts | 5 +- apps/meteor/tests/e2e/09-channel.spec.ts | 46 +- .../e2e/omnichannel-departaments.spec.ts | 6 +- .../tests/e2e/utils/pageobjects/BasePage.ts | 6 +- .../tests/e2e/utils/pageobjects/Global.ts | 16 +- .../tests/e2e/utils/pageobjects/LoginPage.ts | 8 - .../e2e/utils/pageobjects/MainContent.ts | 2 +- yarn.lock | 569 ++++++++++-------- 13 files changed, 395 insertions(+), 316 deletions(-) diff --git a/.yarnrc.yml b/.yarnrc.yml index 404765a22773..1d00b70c6e57 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -11,3 +11,4 @@ plugins: spec: '@yarnpkg/plugin-typescript' yarnPath: .yarn/releases/yarn-3.2.0.cjs +checksumBehavior: 'ignore' diff --git a/apps/meteor/client/lib/utils/handleError.ts b/apps/meteor/client/lib/utils/handleError.ts index 87f4bd5cdcd2..34270ec7b22d 100644 --- a/apps/meteor/client/lib/utils/handleError.ts +++ b/apps/meteor/client/lib/utils/handleError.ts @@ -1,6 +1,8 @@ import { escapeHTML } from '@rocket.chat/string-helpers'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import toastr from 'toastr'; +// import toastr from 'toastr'; + +import { dispatchToastMessage } from '../toast'; const hasXHR = (error: {}): error is { xhr: JQuery.jqXHR } => 'xhr' in error; @@ -31,10 +33,19 @@ export const handleError = (error: {}, useToastr = true): JQuery<HTMLElement> | return; } - return toastr.error( - TAPi18n.__(message, Object.fromEntries(Object.entries(details).map(([key, value]) => [key, escapeHTML(TAPi18n.__(value))]))), - hasErrorTitle(details) ? TAPi18n.__(details.errorTitle) : undefined, - ); + const i18message = + (hasErrorTitle(details) ? TAPi18n.__(details.errorTitle) : '') + + TAPi18n.__(message, Object.fromEntries(Object.entries(details).map(([key, value]) => [key, escapeHTML(TAPi18n.__(value))]))); + + dispatchToastMessage({ + type: 'error', + message: i18message, + }); + + // return toastr.error( + // TAPi18n.__(message, Object.fromEntries(Object.entries(details).map(([key, value]) => [key, escapeHTML(TAPi18n.__(value))]))), + // hasErrorTitle(details) ? TAPi18n.__(details.errorTitle) : undefined, + // ); } return escapeHTML(TAPi18n.__(message, Object.fromEntries(Object.entries(details).map(([key, value]) => [key, TAPi18n.__(value)])))); diff --git a/apps/meteor/client/providers/ToastMessagesProvider.tsx b/apps/meteor/client/providers/ToastMessagesProvider.tsx index 509dabece81e..6758d79fe8f5 100644 --- a/apps/meteor/client/providers/ToastMessagesProvider.tsx +++ b/apps/meteor/client/providers/ToastMessagesProvider.tsx @@ -1,6 +1,7 @@ +import { ToastBarProvider, useToastBarDispatch } from '@rocket.chat/fuselage-toastbar'; import { ToastMessagesContext } from '@rocket.chat/ui-contexts'; import React, { FC, useEffect } from 'react'; -import toastr from 'toastr'; +// import toastr from 'toastr'; import { dispatchToastMessage, subscribeToToastMessages } from '../lib/toast'; import { handleError } from '../lib/utils/handleError'; @@ -9,10 +10,12 @@ const contextValue = { dispatch: dispatchToastMessage, }; -const ToastMessagesProvider: FC = ({ children }) => { +const ToastMessageInnerProvider: FC = ({ children }) => { + const dispatchToastBar = useToastBarDispatch(); + useEffect( () => - subscribeToToastMessages(({ type, message, title, options }) => { + subscribeToToastMessages(({ type, message, title = '' }) => { if (type === 'error' && typeof message === 'object') { handleError(message); return; @@ -22,12 +25,24 @@ const ToastMessagesProvider: FC = ({ children }) => { message = `[${message.name}] ${message.message}`; } - toastr[type](message, title, options); + if (type === 'warning') { + return; + } + + // toastr[type](message, title, options); + dispatchToastBar({ type, message: title + message }); }), - [], + [dispatchToastBar], ); return <ToastMessagesContext.Provider children={children} value={contextValue} />; }; +// eslint-disable-next-line react/no-multi-comp +const ToastMessagesProvider: FC = ({ children }) => ( + <ToastBarProvider> + <ToastMessageInnerProvider children={children} /> + </ToastBarProvider> +); + export default ToastMessagesProvider; diff --git a/apps/meteor/package.json b/apps/meteor/package.json index aabd707580e3..b11d6ed451f1 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -194,6 +194,7 @@ "@rocket.chat/fuselage": "0.31.13", "@rocket.chat/fuselage-hooks": "~0.31.12", "@rocket.chat/fuselage-polyfills": "~0.31.12", + "@rocket.chat/fuselage-toastbar": "next", "@rocket.chat/fuselage-tokens": "~0.31.12", "@rocket.chat/fuselage-ui-kit": "~0.31.12", "@rocket.chat/icons": "~0.31.12", diff --git a/apps/meteor/tests/e2e/01-forgot-password.spec.ts b/apps/meteor/tests/e2e/01-forgot-password.spec.ts index 952cf32e627c..7f087fff746c 100644 --- a/apps/meteor/tests/e2e/01-forgot-password.spec.ts +++ b/apps/meteor/tests/e2e/01-forgot-password.spec.ts @@ -1,13 +1,16 @@ import { test, expect } from '@playwright/test'; import LoginPage from './utils/pageobjects/LoginPage'; +import Global from './utils/pageobjects/Global'; import { VALID_EMAIL, INVALID_EMAIL, INVALID_EMAIL_WITHOUT_MAIL_PROVIDER } from './utils/mocks/userAndPasswordMock'; test.describe('[Forgot Password]', () => { let loginPage: LoginPage; + let global: Global; test.beforeEach(async ({ page, baseURL }) => { loginPage = new LoginPage(page); + global = new Global(page); const baseUrl = baseURL as string; await loginPage.goto(baseUrl); await loginPage.gotToForgotPassword(); @@ -34,6 +37,6 @@ test.describe('[Forgot Password]', () => { test('expect user type a valid email', async () => { await loginPage.emailField().type(VALID_EMAIL); await loginPage.submit(); - await expect(loginPage.getToastMessageSuccess()).toBeVisible(); + await expect(global.getToastBarSuccess()).toBeVisible(); }); }); diff --git a/apps/meteor/tests/e2e/03-login.spec.ts b/apps/meteor/tests/e2e/03-login.spec.ts index 6137e2bed323..223262a0afb8 100644 --- a/apps/meteor/tests/e2e/03-login.spec.ts +++ b/apps/meteor/tests/e2e/03-login.spec.ts @@ -2,14 +2,17 @@ import { test, expect } from '@playwright/test'; import { validUser } from './utils/mocks/userAndPasswordMock'; import LoginPage from './utils/pageobjects/LoginPage'; +import Global from './utils/pageobjects/Global'; import { HOME_SELECTOR } from './utils/mocks/waitSelectorsMock'; test.describe('[Login]', () => { let loginPage: LoginPage; + let global: Global; test.beforeEach(async ({ page, baseURL }) => { const baseUrl = baseURL; loginPage = new LoginPage(page); + global = new Global(page); await loginPage.goto(baseUrl as string); }); @@ -19,7 +22,7 @@ test.describe('[Login]', () => { password: 'any_password1', }; await loginPage.login(invalidUserPassword); - await expect(loginPage.getToastError()).toBeVisible(); + await expect(global.getToastBarError()).toBeVisible(); }); test('expect user make login', async () => { diff --git a/apps/meteor/tests/e2e/09-channel.spec.ts b/apps/meteor/tests/e2e/09-channel.spec.ts index 0ce1aec44c43..555d6e3ce464 100644 --- a/apps/meteor/tests/e2e/09-channel.spec.ts +++ b/apps/meteor/tests/e2e/09-channel.spec.ts @@ -102,15 +102,15 @@ test.describe('[Channel]', () => { test.describe('Adding a user to the room:', async () => { test.beforeAll(async () => { - if (await global.toastAlert().isVisible()) { - await global.dismissToast(); + if (await global.getToastBar().isVisible()) { + await global.dismissToastBar(); } await flexTab.operateFlexTab('members', true); }); test.afterAll(async () => { - if (await global.toastAlert().isVisible()) { - await global.dismissToast(); + if (await global.getToastBar().isVisible()) { + await global.dismissToastBar(); } await flexTab.operateFlexTab('members', false); }); @@ -118,7 +118,7 @@ test.describe('[Channel]', () => { test('expect add people to the room', async () => { await flexTab.addPeopleToChannel(targetUser); hasUserAddedInChannel = true; - await expect(global.toastAlert()).toBeVisible(); + await expect(global.getToastBarSuccess()).toBeVisible(); }); }); @@ -130,8 +130,8 @@ test.describe('[Channel]', () => { }); test.afterAll(async () => { - if (await global.toastAlert().isVisible()) { - await global.dismissToast(); + if (await global.getToastBar().isVisible()) { + await global.dismissToastBar(); } if (await flexTab.mainSideBar().isVisible()) { await flexTab.mainSideBarClose().click(); @@ -158,8 +158,8 @@ test.describe('[Channel]', () => { }); test.afterAll(async () => { - if (await global.toastAlert().isVisible()) { - await global.dismissToast(); + if (await global.getToastBar().isVisible()) { + await global.dismissToastBar(); } if (await flexTab.mainSideBar().isVisible()) { await flexTab.mainSideBarClose().click(); @@ -186,8 +186,8 @@ test.describe('[Channel]', () => { }); test.afterAll(async () => { - if (await global.toastAlert().isVisible()) { - await global.dismissToast(); + if (await global.getToastBar().isVisible()) { + await global.dismissToastBar(); } if (await flexTab.mainSideBar().isVisible()) { await flexTab.mainSideBarClose().click(); @@ -221,8 +221,8 @@ test.describe('[Channel]', () => { }); test.afterAll(async () => { - if (await global.toastAlert().isVisible()) { - await global.dismissToast(); + if (await global.getToastBar().isVisible()) { + await global.dismissToastBar(); } await flexTab.operateFlexTab('members', false); }); @@ -243,8 +243,8 @@ test.describe('[Channel]', () => { }); test.afterAll(async () => { - if (await global.toastAlert().isVisible()) { - await global.dismissToast(); + if (await global.getToastBar().isVisible()) { + await global.dismissToastBar(); } await flexTab.operateFlexTab('members', false); }); @@ -254,8 +254,8 @@ test.describe('[Channel]', () => { }); test('expect dismiss the toast', async () => { - if (await global.toastAlert().isVisible()) { - await global.dismissToast(); + if (await global.getToastBar().isVisible()) { + await global.dismissToastBar(); } }); @@ -279,8 +279,8 @@ test.describe('[Channel]', () => { }); test.afterAll(async () => { - if (await global.toastAlert().isVisible()) { - await global.dismissToast(); + if (await global.getToastBar().isVisible()) { + await global.dismissToastBar(); } await flexTab.operateFlexTab('members', false); }); @@ -296,15 +296,15 @@ test.describe('[Channel]', () => { test.describe('Channel name edit', async () => { test.beforeAll(async () => { - if (await global.toastAlert().isVisible()) { - await global.dismissToast(); + if (await global.getToastBar().isVisible()) { + await global.dismissToastBar(); } await flexTab.operateFlexTab('info', true); }); test.afterAll(async () => { - if (await global.toastAlert().isVisible()) { - await global.dismissToast(); + if (await global.getToastBar().isVisible()) { + await global.dismissToastBar(); } if (await flexTab.mainSideBar().isVisible()) { diff --git a/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts b/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts index 697a06ee9bd1..1237156c2efd 100644 --- a/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts @@ -1,6 +1,7 @@ import { test, Page, expect } from '@playwright/test'; import LoginPage from './utils/pageobjects/LoginPage'; +import Global from './utils/pageobjects/Global'; import SideNav from './utils/pageobjects/SideNav'; import Departments from './utils/pageobjects/Departments'; import { adminLogin } from './utils/mocks/userAndPasswordMock'; @@ -10,6 +11,8 @@ test.describe('[Department]', () => { let sideNav: SideNav; let departments: Departments; let page: Page; + let global: Global; + test.beforeAll(async ({ browser }) => { page = await browser.newPage(); const basePath = '/'; @@ -18,6 +21,7 @@ test.describe('[Department]', () => { loginPage = new LoginPage(page); sideNav = new SideNav(page); departments = new Departments(page); + global = new Global(page); await loginPage.login(adminLogin); await sideNav.sidebarUserMenu().click(); @@ -39,7 +43,7 @@ test.describe('[Department]', () => { }); test.describe('[Create and Edit]', async () => { test.afterEach(async () => { - await departments.toastSuccess.click(); + await global.dismissToastBar(); }); test('expect new department is created', async () => { diff --git a/apps/meteor/tests/e2e/utils/pageobjects/BasePage.ts b/apps/meteor/tests/e2e/utils/pageobjects/BasePage.ts index d920c3fcc5cd..6b2f85211cf1 100644 --- a/apps/meteor/tests/e2e/utils/pageobjects/BasePage.ts +++ b/apps/meteor/tests/e2e/utils/pageobjects/BasePage.ts @@ -1,4 +1,4 @@ -import { Page, Locator } from '@playwright/test'; +import { Page } from '@playwright/test'; class BasePage { private page: Page; @@ -22,9 +22,5 @@ class BasePage { public async keyboardPress(key: string): Promise<void> { await this.getPage().keyboard.press(key); } - - get toastSuccess(): Locator { - return this.getPage().locator('#toast-container'); - } } export default BasePage; diff --git a/apps/meteor/tests/e2e/utils/pageobjects/Global.ts b/apps/meteor/tests/e2e/utils/pageobjects/Global.ts index f6750773844c..7877c9d5815f 100644 --- a/apps/meteor/tests/e2e/utils/pageobjects/Global.ts +++ b/apps/meteor/tests/e2e/utils/pageobjects/Global.ts @@ -40,8 +40,16 @@ class Global extends BasePage { return this.getPage().locator('.rc-modal .upload-preview-title'); } - public toastAlert(): Locator { - return this.getPage().locator('.toast-message'); + public getToastBar(): Locator { + return this.getPage().locator('.rcx-toastbar'); + } + + public getToastBarError(): Locator { + return this.getPage().locator('.rcx-toastbar.rcx-toastbar--error'); + } + + public getToastBarSuccess(): Locator { + return this.getPage().locator('.rcx-toastbar.rcx-toastbar--success'); } public async confirmPopup(): Promise<void> { @@ -50,8 +58,8 @@ class Global extends BasePage { await this.modalConfirm().click(); } - public async dismissToast(): Promise<void> { - await this.toastAlert().click(); + public async dismissToastBar(): Promise<void> { + await this.getToastBar().locator('button').click(); } } diff --git a/apps/meteor/tests/e2e/utils/pageobjects/LoginPage.ts b/apps/meteor/tests/e2e/utils/pageobjects/LoginPage.ts index c5472de404a7..d13f1c03546a 100644 --- a/apps/meteor/tests/e2e/utils/pageobjects/LoginPage.ts +++ b/apps/meteor/tests/e2e/utils/pageobjects/LoginPage.ts @@ -49,14 +49,6 @@ class LoginPage extends BasePage { return this.getPage().locator('[name=confirm-pass]'); } - public getToastError(): Locator { - return this.getPage().locator('.toast'); - } - - public getToastMessageSuccess(): Locator { - return this.getPage().locator('.toast-message'); - } - public emailOrUsernameInvalidText(): Locator { return this.getPage().locator('[name=emailOrUsername]~.input-error'); } diff --git a/apps/meteor/tests/e2e/utils/pageobjects/MainContent.ts b/apps/meteor/tests/e2e/utils/pageobjects/MainContent.ts index be835d133c45..38ce5a9f29e0 100644 --- a/apps/meteor/tests/e2e/utils/pageobjects/MainContent.ts +++ b/apps/meteor/tests/e2e/utils/pageobjects/MainContent.ts @@ -384,7 +384,7 @@ export default class MainContent extends BasePage { break; case 'star': await this.messageStar().click(); - await expect(this.getPage().locator('div.toast-message:has-text("Message has been starred")')).toBeVisible(); + await expect(this.getPage().locator('div.rcx-toastbar:has-text("Message has been starred")')).toBeVisible(); break; case 'unread': await this.messageUnread().click(); diff --git a/yarn.lock b/yarn.lock index bd6380a7cf07..32a8008b8c31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -101,25 +101,25 @@ __metadata: linkType: hard "@babel/core@npm:^7.17.9": - version: 7.18.0 - resolution: "@babel/core@npm:7.18.0" + version: 7.18.2 + resolution: "@babel/core@npm:7.18.2" dependencies: "@ampproject/remapping": ^2.1.0 "@babel/code-frame": ^7.16.7 - "@babel/generator": ^7.18.0 - "@babel/helper-compilation-targets": ^7.17.10 + "@babel/generator": ^7.18.2 + "@babel/helper-compilation-targets": ^7.18.2 "@babel/helper-module-transforms": ^7.18.0 - "@babel/helpers": ^7.18.0 + "@babel/helpers": ^7.18.2 "@babel/parser": ^7.18.0 "@babel/template": ^7.16.7 - "@babel/traverse": ^7.18.0 - "@babel/types": ^7.18.0 + "@babel/traverse": ^7.18.2 + "@babel/types": ^7.18.2 convert-source-map: ^1.7.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 json5: ^2.2.1 semver: ^6.3.0 - checksum: 350b7724a48c80b76f8af11e3cac1ad8ec9021325389f5ae20c713b10d4359c5e60aa7e71a309a3e1893826c46e72eef5df4978eb63eaabc403e8cc4ce5e94fc + checksum: 14a4142c12e004cd2477b7610408d5788ee5dd821ee9e4de204cbb72d9c399d858d9deabc3d49914d5d7c2927548160c19bdc7524b1a9f6acc1ec96a8d9848dd languageName: node linkType: hard @@ -134,14 +134,14 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.18.0": - version: 7.18.0 - resolution: "@babel/generator@npm:7.18.0" +"@babel/generator@npm:^7.18.2": + version: 7.18.2 + resolution: "@babel/generator@npm:7.18.2" dependencies: - "@babel/types": ^7.18.0 + "@babel/types": ^7.18.2 "@jridgewell/gen-mapping": ^0.3.0 jsesc: ^2.5.1 - checksum: 0854b21d94f99e3ac68249a9bbaa0c3a914a600c69c12fffa4a01377d89282174a67e619654e401be4c791414a1d5e825671f089f1c2407694a494dcfab8b06c + checksum: d0661e95532ddd97566d41fec26355a7b28d1cbc4df95fe80cc084c413342935911b48db20910708db39714844ddd614f61c2ec4cca3fb10181418bdcaa2e7a3 languageName: node linkType: hard @@ -178,9 +178,9 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.17.10": - version: 7.17.10 - resolution: "@babel/helper-compilation-targets@npm:7.17.10" +"@babel/helper-compilation-targets@npm:^7.17.10, @babel/helper-compilation-targets@npm:^7.18.2": + version: 7.18.2 + resolution: "@babel/helper-compilation-targets@npm:7.18.2" dependencies: "@babel/compat-data": ^7.17.10 "@babel/helper-validator-option": ^7.16.7 @@ -188,7 +188,7 @@ __metadata: semver: ^6.3.0 peerDependencies: "@babel/core": ^7.0.0 - checksum: 5f547c7ebd372e90fa72c2aaea867e7193166e9f469dec5acde4f0e18a78b80bdca8e02a0f641f3e998be984fb5b802c729a9034faaee8b1a9ef6670cb76f120 + checksum: 4f02e79f20c0b3f8db5049ba8c35027c41ccb3fc7884835d04e49886538e0f55702959db1bb75213c94a5708fec2dc81a443047559a4f184abb884c72c0059b4 languageName: node linkType: hard @@ -295,6 +295,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-environment-visitor@npm:^7.18.2": + version: 7.18.2 + resolution: "@babel/helper-environment-visitor@npm:7.18.2" + checksum: 1a9c8726fad454a082d077952a90f17188e92eabb3de236cb4782c49b39e3f69c327e272b965e9a20ff8abf37d30d03ffa6fd7974625a6c23946f70f7527f5e9 + languageName: node + linkType: hard + "@babel/helper-explode-assignable-expression@npm:^7.16.7": version: 7.16.7 resolution: "@babel/helper-explode-assignable-expression@npm:7.16.7" @@ -427,6 +434,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-replace-supers@npm:^7.18.2": + version: 7.18.2 + resolution: "@babel/helper-replace-supers@npm:7.18.2" + dependencies: + "@babel/helper-environment-visitor": ^7.18.2 + "@babel/helper-member-expression-to-functions": ^7.17.7 + "@babel/helper-optimise-call-expression": ^7.16.7 + "@babel/traverse": ^7.18.2 + "@babel/types": ^7.18.2 + checksum: c0083b7933672dd2aed50b79021c46401c83f41bc2132def19c5414cf8f944251f6d91dd959b2bedada9a7436a80fab629adb486e008566290c82293e89fec05 + languageName: node + linkType: hard + "@babel/helper-simple-access@npm:^7.17.7": version: 7.17.7 resolution: "@babel/helper-simple-access@npm:7.17.7" @@ -436,6 +456,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-simple-access@npm:^7.18.2": + version: 7.18.2 + resolution: "@babel/helper-simple-access@npm:7.18.2" + dependencies: + "@babel/types": ^7.18.2 + checksum: c0862b56db7e120754d89273a039b128c27517389f6a4425ff24e49779791e8fe10061579171fb986be81fa076778acb847c709f6f5e396278d9c5e01360c375 + languageName: node + linkType: hard + "@babel/helper-skip-transparent-expression-wrappers@npm:^7.16.0": version: 7.16.0 resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.16.0" @@ -491,14 +520,14 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.18.0": - version: 7.18.0 - resolution: "@babel/helpers@npm:7.18.0" +"@babel/helpers@npm:^7.18.2": + version: 7.18.2 + resolution: "@babel/helpers@npm:7.18.2" dependencies: "@babel/template": ^7.16.7 - "@babel/traverse": ^7.18.0 - "@babel/types": ^7.18.0 - checksum: 3f41631c0797b052cc22337ee56290700fe7db7bc06b847fcdf2c0043cddc35861855a1acc4c948397838675d2dc694f4fb1b102d1c7eb484ea01e9029916b55 + "@babel/traverse": ^7.18.2 + "@babel/types": ^7.18.2 + checksum: 94620242f23f6d5f9b83a02b1aa1632ffb05b0815e1bb53d3b46d64aa8e771066bba1db8bd267d9091fb00134cfaeda6a8d69d1d4cc2c89658631adfa077ae70 languageName: node linkType: hard @@ -523,11 +552,11 @@ __metadata: linkType: hard "@babel/parser@npm:^7.18.0": - version: 7.18.0 - resolution: "@babel/parser@npm:7.18.0" + version: 7.18.4 + resolution: "@babel/parser@npm:7.18.4" bin: parser: ./bin/babel-parser.js - checksum: 253b5828bf4a0b443301baedc5993d6f7f35aa0d81cf8f2f2f53940904b7067eab7bd2380aee4b3be1d8efd5ae1008eb0fad19bde28f5fbc213c0fdf9a414466 + checksum: e05b2dc720c4b200e088258f3c2a2de5041c140444edc38181d1217b10074e881a7133162c5b62356061f26279f08df5a06ec14c5842996ee8601ad03c57a44f languageName: node linkType: hard @@ -1294,13 +1323,13 @@ __metadata: linkType: hard "@babel/plugin-transform-block-scoping@npm:^7.17.12": - version: 7.17.12 - resolution: "@babel/plugin-transform-block-scoping@npm:7.17.12" + version: 7.18.4 + resolution: "@babel/plugin-transform-block-scoping@npm:7.18.4" dependencies: "@babel/helper-plugin-utils": ^7.17.12 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: ea3d4d88e38367d62a1029d204c5cc0ac410b00779179c8507448001c64784bf8e34c6fa57f23d8b95a835541a2fc67d1076650b1efc99c78f699de354472e49 + checksum: 5fdc8fd2f56f43e275353123fa1cda3df475daf1e9d92c03d5aa1ae50d3a0ccabf80c6168356947d8eb8e6e29098c875bc27fda8c7d4fbca6ffc6eec5d5faa8d languageName: node linkType: hard @@ -1323,20 +1352,20 @@ __metadata: linkType: hard "@babel/plugin-transform-classes@npm:^7.17.12": - version: 7.17.12 - resolution: "@babel/plugin-transform-classes@npm:7.17.12" + version: 7.18.4 + resolution: "@babel/plugin-transform-classes@npm:7.18.4" dependencies: "@babel/helper-annotate-as-pure": ^7.16.7 - "@babel/helper-environment-visitor": ^7.16.7 + "@babel/helper-environment-visitor": ^7.18.2 "@babel/helper-function-name": ^7.17.9 "@babel/helper-optimise-call-expression": ^7.16.7 "@babel/helper-plugin-utils": ^7.17.12 - "@babel/helper-replace-supers": ^7.16.7 + "@babel/helper-replace-supers": ^7.18.2 "@babel/helper-split-export-declaration": ^7.16.7 globals: ^11.1.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 0127b1cc432373965edf28cbfd9e85df5bc77e974ceb80ba32691e050e8fb6792f207d1941529c81d1b9e7a6e82da26ecc445f6f547f0ad5076cd2b27adc18ac + checksum: 968711024c2ed1c08ced754243edde3a663ab40c414ca6fcad1a75f27789f3f52cc78fbafe21c6337c4c6a0dfbeddd1527caff1558ed477790b600a1e6f99cda languageName: node linkType: hard @@ -1453,7 +1482,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-for-of@npm:^7.17.12": +"@babel/plugin-transform-for-of@npm:^7.18.1": version: 7.18.1 resolution: "@babel/plugin-transform-for-of@npm:7.18.1" dependencies: @@ -1550,17 +1579,17 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.18.0": - version: 7.18.0 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.18.0" +"@babel/plugin-transform-modules-commonjs@npm:^7.18.2": + version: 7.18.2 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.18.2" dependencies: "@babel/helper-module-transforms": ^7.18.0 "@babel/helper-plugin-utils": ^7.17.12 - "@babel/helper-simple-access": ^7.17.7 + "@babel/helper-simple-access": ^7.18.2 babel-plugin-dynamic-import-node: ^2.3.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: debb8c952b689def0d3f02d2944b8d031650adcad042277f91c4d137d96c4de1796576d2791fc55217c19004947a37f031c9870d830861075d33d279fe02dda8 + checksum: 99c1c5ce9c353e29eb680ebb5bdf27c076c6403e133a066999298de642423cc7f38cfbac02372d33ed73278da13be23c4be7d60169c3e27bd900a373e61a599a languageName: node linkType: hard @@ -1580,8 +1609,8 @@ __metadata: linkType: hard "@babel/plugin-transform-modules-systemjs@npm:^7.18.0": - version: 7.18.0 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.18.0" + version: 7.18.4 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.18.4" dependencies: "@babel/helper-hoist-variables": ^7.16.7 "@babel/helper-module-transforms": ^7.18.0 @@ -1590,7 +1619,7 @@ __metadata: babel-plugin-dynamic-import-node: ^2.3.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 80fccfc546aab76238d3f4aeb454f61ed885670578f1ab6dc063bba5b5d4cbdf821439ac6ca8bc24449eed752359600b47be717196103d2eabba06de1bf3f732 + checksum: abe6948a1548b20055bf1c56ceab5b17dc283e7cdbcc0525b297b726f0785f1169333b5e685add81337fc749588adb8d96ccba9269565031db006a710e7eaf02 languageName: node linkType: hard @@ -1874,14 +1903,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-template-literals@npm:^7.17.12": - version: 7.17.12 - resolution: "@babel/plugin-transform-template-literals@npm:7.17.12" +"@babel/plugin-transform-template-literals@npm:^7.18.2": + version: 7.18.2 + resolution: "@babel/plugin-transform-template-literals@npm:7.18.2" dependencies: "@babel/helper-plugin-utils": ^7.17.12 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: fec220cea6e7bcd7720c65245e628cdf8e8276379e8ee041e49217b5ebb426911cb738d5b66afa5b1c7d17fc8dbe76d8041dbbce442925d83f08fb510f90507e + checksum: bc0102ed8c789e5bc01053088e2de85b82cebcd4d57af9fdc32ca62f559d3dd19c33e9d26caa71c5fd8e94152e5ce4fc4da19badc2d537620e6dea83bce7eb05 languageName: node linkType: hard @@ -2028,11 +2057,11 @@ __metadata: linkType: hard "@babel/preset-env@npm:^7.16.11": - version: 7.18.0 - resolution: "@babel/preset-env@npm:7.18.0" + version: 7.18.2 + resolution: "@babel/preset-env@npm:7.18.2" dependencies: "@babel/compat-data": ^7.17.10 - "@babel/helper-compilation-targets": ^7.17.10 + "@babel/helper-compilation-targets": ^7.18.2 "@babel/helper-plugin-utils": ^7.17.12 "@babel/helper-validator-option": ^7.16.7 "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ^7.17.12 @@ -2077,12 +2106,12 @@ __metadata: "@babel/plugin-transform-dotall-regex": ^7.16.7 "@babel/plugin-transform-duplicate-keys": ^7.17.12 "@babel/plugin-transform-exponentiation-operator": ^7.16.7 - "@babel/plugin-transform-for-of": ^7.17.12 + "@babel/plugin-transform-for-of": ^7.18.1 "@babel/plugin-transform-function-name": ^7.16.7 "@babel/plugin-transform-literals": ^7.17.12 "@babel/plugin-transform-member-expression-literals": ^7.16.7 "@babel/plugin-transform-modules-amd": ^7.18.0 - "@babel/plugin-transform-modules-commonjs": ^7.18.0 + "@babel/plugin-transform-modules-commonjs": ^7.18.2 "@babel/plugin-transform-modules-systemjs": ^7.18.0 "@babel/plugin-transform-modules-umd": ^7.18.0 "@babel/plugin-transform-named-capturing-groups-regex": ^7.17.12 @@ -2095,12 +2124,12 @@ __metadata: "@babel/plugin-transform-shorthand-properties": ^7.16.7 "@babel/plugin-transform-spread": ^7.17.12 "@babel/plugin-transform-sticky-regex": ^7.16.7 - "@babel/plugin-transform-template-literals": ^7.17.12 + "@babel/plugin-transform-template-literals": ^7.18.2 "@babel/plugin-transform-typeof-symbol": ^7.17.12 "@babel/plugin-transform-unicode-escapes": ^7.16.7 "@babel/plugin-transform-unicode-regex": ^7.16.7 "@babel/preset-modules": ^0.1.5 - "@babel/types": ^7.18.0 + "@babel/types": ^7.18.2 babel-plugin-polyfill-corejs2: ^0.3.0 babel-plugin-polyfill-corejs3: ^0.5.0 babel-plugin-polyfill-regenerator: ^0.3.0 @@ -2108,7 +2137,7 @@ __metadata: semver: ^6.3.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: f2d4f6305c0e1a4f49d89c076f67f93e20d9fce73cde1c74447c2c3cc519b3568a2359815221eb386cac0050fdaf8a032f4f85746aa4c1f4d0b7808dcebf2966 + checksum: f81892a7970cb34643b93917cbbc9b581d5066d892639867521f4a85ec258e69362a37bbb7b899b351e71d26095a97cd2d6e35e5f9ee110715146e0ccc19e700 languageName: node linkType: hard @@ -2210,11 +2239,11 @@ __metadata: linkType: hard "@babel/runtime@npm:^7.17.9": - version: 7.18.0 - resolution: "@babel/runtime@npm:7.18.0" + version: 7.18.3 + resolution: "@babel/runtime@npm:7.18.3" dependencies: regenerator-runtime: ^0.13.4 - checksum: 9d0caa5fe690623fb6c5df6fb3b3581d227b55ef9f7c35eba0da83d10aa756669a81fe521ac4dbc007e5790716bac40ebe71ff098e2d1a9599dd696a282a3e95 + checksum: db8526226aa02cfa35a5a7ac1a34b5f303c62a1f000c7db48cb06c6290e616483e5036ab3c4e7a84d0f3be6d4e2148d5fe5cec9564bf955f505c3e764b83d7f1 languageName: node linkType: hard @@ -2247,21 +2276,21 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.18.0": - version: 7.18.0 - resolution: "@babel/traverse@npm:7.18.0" +"@babel/traverse@npm:^7.18.0, @babel/traverse@npm:^7.18.2": + version: 7.18.2 + resolution: "@babel/traverse@npm:7.18.2" dependencies: "@babel/code-frame": ^7.16.7 - "@babel/generator": ^7.18.0 - "@babel/helper-environment-visitor": ^7.16.7 + "@babel/generator": ^7.18.2 + "@babel/helper-environment-visitor": ^7.18.2 "@babel/helper-function-name": ^7.17.9 "@babel/helper-hoist-variables": ^7.16.7 "@babel/helper-split-export-declaration": ^7.16.7 "@babel/parser": ^7.18.0 - "@babel/types": ^7.18.0 + "@babel/types": ^7.18.2 debug: ^4.1.0 globals: ^11.1.0 - checksum: b80b49ba5cead42c4b09bdfbe926d94179f884d35319a0a3ab5a798c85f16102a7342799fac928b3041337ea2c3f5194f17c4a08f611a474de6eea719b640dd4 + checksum: e21c2d550bf610406cf21ef6fbec525cb1d80b9d6d71af67552478a24ee371203cb4025b23b110ae7288a62a874ad5898daad19ad23daa95dfc8ab47a47a092f languageName: node linkType: hard @@ -2275,13 +2304,13 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.17.12, @babel/types@npm:^7.18.0": - version: 7.18.0 - resolution: "@babel/types@npm:7.18.0" +"@babel/types@npm:^7.17.12, @babel/types@npm:^7.18.0, @babel/types@npm:^7.18.2": + version: 7.18.4 + resolution: "@babel/types@npm:7.18.4" dependencies: "@babel/helper-validator-identifier": ^7.16.7 to-fast-properties: ^2.0.0 - checksum: 151485f94c929171fd6539430c0ae519e8bb67fbc0d856b285328f5e6ecbaf4237b52d7a581b413f5e7b6268d31a4db6ca9bc01372b284b2966aa473fc902f27 + checksum: 85df59beb99c1b95e9e41590442f2ffa1e5b1b558d025489db40c9f7c906bd03a17da26c3ec486e5800e80af27c42ca7eee9506d9212ab17766d2d68d30fbf52 languageName: node linkType: hard @@ -2316,25 +2345,25 @@ __metadata: languageName: node linkType: hard -"@bugsnag/browser@npm:^7.16.5": - version: 7.16.5 - resolution: "@bugsnag/browser@npm:7.16.5" +"@bugsnag/browser@npm:^7.16.7": + version: 7.16.7 + resolution: "@bugsnag/browser@npm:7.16.7" dependencies: - "@bugsnag/core": ^7.16.1 - checksum: 4434c857376a45b92b549a79be97d3a2c8144c4220d0ee6b5866393ecb03d2bc6509ce44df7b53ebfe7397273a41abc6e5d03d96cd18527cface88f11b180dfe + "@bugsnag/core": ^7.16.7 + checksum: c83a8d0184fdc33473043994a41c0e832152cf84a32bcc97663ebc8da17bfde84fd8c971e40ba0c03fd178f4615c6834764af2119c29f44a48d2c68819bbf675 languageName: node linkType: hard -"@bugsnag/core@npm:^7.16.1": - version: 7.16.1 - resolution: "@bugsnag/core@npm:7.16.1" +"@bugsnag/core@npm:^7.16.7": + version: 7.16.7 + resolution: "@bugsnag/core@npm:7.16.7" dependencies: "@bugsnag/cuid": ^3.0.0 "@bugsnag/safe-json-stringify": ^6.0.0 error-stack-parser: ^2.0.3 iserror: 0.0.2 stack-generator: ^2.0.3 - checksum: 53c7803a0457fad905aa712105e819ac89162e09bff0b7b78a2b94d236634b6de5771b5c9761e123b9dbcfbb170890f2f20598993f2748733476884e6f18447c + checksum: 2212ceb090ef9d53d048fc4b03f8f9dd22573838374f4aca7075f8431bf46fad9d07018804e12cc6508925207aee198b1330b1c42e3c71ec7ab112a74cd0534a languageName: node linkType: hard @@ -2346,26 +2375,26 @@ __metadata: linkType: hard "@bugsnag/js@npm:^7.16.2": - version: 7.16.5 - resolution: "@bugsnag/js@npm:7.16.5" + version: 7.16.7 + resolution: "@bugsnag/js@npm:7.16.7" dependencies: - "@bugsnag/browser": ^7.16.5 - "@bugsnag/node": ^7.16.2 - checksum: 2d8cc5b2e757ada851b2139a3087500b386305d9c3dbdb02d3165b73ce7fe4e8208b471c1a31086441dce28a007169eeb699ccb38e9817a0fbbe32430415640f + "@bugsnag/browser": ^7.16.7 + "@bugsnag/node": ^7.16.7 + checksum: ba49fe64646738db130d56f58bd4f206d1e905c441e517b9a82ed823482cb9a6d51cc51cdcb7539baff41c6f2eca69b36bf48004163cdc6bed3f0c5e6450f944 languageName: node linkType: hard -"@bugsnag/node@npm:^7.16.2": - version: 7.16.2 - resolution: "@bugsnag/node@npm:7.16.2" +"@bugsnag/node@npm:^7.16.7": + version: 7.16.7 + resolution: "@bugsnag/node@npm:7.16.7" dependencies: - "@bugsnag/core": ^7.16.1 + "@bugsnag/core": ^7.16.7 byline: ^5.0.0 error-stack-parser: ^2.0.2 iserror: ^0.0.2 pump: ^3.0.0 stack-generator: ^2.0.3 - checksum: 484254472d07aa899ee072741c0516d4cb1c347bf4a78cc5163dd3311add0dad37fb27f12e9a24f959f17814cef52a4b72b1a07c7293b13aa01c75356b9e3297 + checksum: ec03529627319adebaab410615f73728ffc1244372ca4343abc4e5be2c59734e1e4170c6ac7d844004fe34b14679a3e70e50a215b4f364c93e7669991c2e2fea languageName: node linkType: hard @@ -4250,20 +4279,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/css-in-js@npm:^0.31.12, @rocket.chat/css-in-js@npm:~0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/css-in-js@npm:0.31.12" - dependencies: - "@emotion/hash": ^0.8.0 - "@rocket.chat/css-supports": ^0.31.12 - "@rocket.chat/memo": ^0.31.12 - "@rocket.chat/stylis-logical-props-middleware": ^0.31.12 - stylis: ~4.0.13 - checksum: 42c22c976e8fdabf50d45c39fba357d25cd7b21ed5f3b16b1c75efe70f09ac658b5e47124a862445e21266dab38352d88902e739bdc9f866c3a43fcd498765c7 - languageName: node - linkType: hard - -"@rocket.chat/css-in-js@npm:^0.31.13": +"@rocket.chat/css-in-js@npm:^0.31.13, @rocket.chat/css-in-js@npm:~0.31.12": version: 0.31.13 resolution: "@rocket.chat/css-in-js@npm:0.31.13" dependencies: @@ -4276,6 +4292,19 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/css-in-js@npm:~0.31.12-dev.10": + version: 0.31.12-dev.10 + resolution: "@rocket.chat/css-in-js@npm:0.31.12-dev.10" + dependencies: + "@emotion/hash": ^0.8.0 + "@rocket.chat/css-supports": ~0.31.12-dev.10 + "@rocket.chat/memo": ~0.31.12-dev.10 + "@rocket.chat/stylis-logical-props-middleware": ~0.31.12-dev.10 + stylis: ~4.0.13 + checksum: d87f70c358150f502f12abd5bcb572de23534211c9a43aaded1d6f88940b9804161143dfbc897fe730d9d9ab429e2988013a9e8325793ec10866fccd6a5b75ca + languageName: node + linkType: hard + "@rocket.chat/css-supports@npm:^0.31.11": version: 0.31.11 resolution: "@rocket.chat/css-supports@npm:0.31.11" @@ -4285,15 +4314,6 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/css-supports@npm:^0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/css-supports@npm:0.31.12" - dependencies: - "@rocket.chat/memo": ^0.31.12 - checksum: 0f6716b707e6b861589b51e8fdac48fd3fa2512e83e213bbb09379cb93d30f8712cc2e517ba0acf593ec835c2fd57eb54d060030221bb6146fada5b41c228314 - languageName: node - linkType: hard - "@rocket.chat/css-supports@npm:^0.31.13": version: 0.31.13 resolution: "@rocket.chat/css-supports@npm:0.31.13" @@ -4303,6 +4323,15 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/css-supports@npm:~0.31.12-dev.10": + version: 0.31.12-dev.10 + resolution: "@rocket.chat/css-supports@npm:0.31.12-dev.10" + dependencies: + "@rocket.chat/memo": ~0.31.12-dev.10 + checksum: 7cc090f9627d53fd63c9bfaceffdcf4e3acf491ddf6d6fffd1d96e518ada71193b386bb9b4ea6010db8e3e368c081263dcaf50f9810a42d727c7ff7654f07109 + languageName: node + linkType: hard + "@rocket.chat/ddp-streamer@workspace:ee/apps/ddp-streamer": version: 0.0.0-use.local resolution: "@rocket.chat/ddp-streamer@workspace:ee/apps/ddp-streamer" @@ -4346,9 +4375,9 @@ __metadata: linkType: hard "@rocket.chat/emitter@npm:~0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/emitter@npm:0.31.12" - checksum: d66636ebc05f60526b18056c211793176702e915788254b603bb45ea209f740b5247a2c95f27ccb66963866001bf5dcb4e9b46c2ded450749ca5381c6439833b + version: 0.31.13 + resolution: "@rocket.chat/emitter@npm:0.31.13" + checksum: a6b00781b68eb4b584b184e67ad129a3599bb190f153f17d91547d1dacbe83d142c2e202ffbee664459cb9888497d4084d37a83cbd1d2f257c571967c0ee6afb languageName: node linkType: hard @@ -4455,22 +4484,35 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage-hooks@npm:^0.31.12, @rocket.chat/fuselage-hooks@npm:~0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/fuselage-hooks@npm:0.31.12" +"@rocket.chat/fuselage-hooks@npm:^0.31.13, @rocket.chat/fuselage-hooks@npm:~0.31.12": + version: 0.31.13 + resolution: "@rocket.chat/fuselage-hooks@npm:0.31.13" + dependencies: + "@testing-library/user-event": ^13.5.0 + peerDependencies: + "@rocket.chat/fuselage-tokens": "*" + react: ^17.0.2 + use-subscription: ^1.5.1 + checksum: 4b1f63536bafd31cc18d2c6479a253c7b41226c43dddc60adc55681ba971f542452f4db62b845b5b47c0b37f5fb822c7ae9612a3c8d4cc1a026fee9170aceac1 + languageName: node + linkType: hard + +"@rocket.chat/fuselage-hooks@npm:~0.31.12-dev.10": + version: 0.31.12-dev.10 + resolution: "@rocket.chat/fuselage-hooks@npm:0.31.12-dev.10" dependencies: "@testing-library/user-event": ^13.5.0 peerDependencies: "@rocket.chat/fuselage-tokens": "*" react: ^17.0.2 use-subscription: ^1.5.1 - checksum: 4ebb49de7ee49e8f9a76f5b98ad0e11010090f889f965b826207e231502e00e265d8bf15c5c3af193724ce976bd43a9f5a63015a3741cd692c6ed2171db3c491 + checksum: 0e77609116843b2730afee5675475b3c2a265ea9a4f81c161be3ccf3efe51a459612d8e1622bb696b027ca0457df729685b3f376f7b0c9916e93448758952b12 languageName: node linkType: hard "@rocket.chat/fuselage-polyfills@npm:~0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/fuselage-polyfills@npm:0.31.12" + version: 0.31.13 + resolution: "@rocket.chat/fuselage-polyfills@npm:0.31.13" dependencies: "@juggle/resize-observer": ^3.3.1 clipboard-polyfill: ^3.0.3 @@ -4478,18 +4520,26 @@ __metadata: focus-visible: ^5.2.0 focus-within-polyfill: ^5.2.1 new-event-polyfill: ^1.0.1 - checksum: 997db51a38a5b65aecc7d59959e4155697e15ac0992b22934b1cb932b6f8a9c1d65770dfd9f01c079c5c87818f51cc37ceaf22dd16b7d37a0b9278ce4daa9aef + checksum: 650ac1b31a8d6ef3f7cf69706a738a4223295ffb7b7f336d3dead57ed0f1fb3709d5cfd714407e480a5521b6269a03780c0cd0b2181dd58b9ddea9d2538222fd languageName: node linkType: hard -"@rocket.chat/fuselage-tokens@npm:^0.31.12, @rocket.chat/fuselage-tokens@npm:~0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/fuselage-tokens@npm:0.31.12" - checksum: ba2ccb37311ff192290f7a8e49d370aff62b6a8cb8ac2ff425860f9721a1bfe724778468e22364154df2b9c3effaea09e4de8bcd520d28952faf3646fd81cc4a +"@rocket.chat/fuselage-toastbar@npm:next": + version: 0.32.0-dev.9 + resolution: "@rocket.chat/fuselage-toastbar@npm:0.32.0-dev.9" + dependencies: + "@rocket.chat/fuselage-hooks": ~0.31.12-dev.10 + "@rocket.chat/styled": ~0.31.12-dev.10 + peerDependencies: + "@rocket.chat/fuselage": "*" + "@rocket.chat/fuselage-polyfills": "*" + react: ^17.0.2 + react-dom: ^17.0.2 + checksum: 2e3b37d10125d529e099941e974222ce48b3fdbfb384dfc2d11db0ebe5f4299edd314edcc93b92d857b47e38baf4fe8dedcdd02de46ad455f06de251d7f5499d languageName: node linkType: hard -"@rocket.chat/fuselage-tokens@npm:^0.31.13": +"@rocket.chat/fuselage-tokens@npm:^0.31.13, @rocket.chat/fuselage-tokens@npm:~0.31.12": version: 0.31.13 resolution: "@rocket.chat/fuselage-tokens@npm:0.31.13" checksum: 848a800e6bd8c8fc46d473f18152168515e54ecf5b5a51ec4920d5478a28797e9d5bad652708b988305484cf646b3a167d0bff4bf0689bfbc7999f50f4bbf3ef @@ -4504,24 +4554,24 @@ __metadata: linkType: hard "@rocket.chat/fuselage-ui-kit@npm:~0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/fuselage-ui-kit@npm:0.31.12" + version: 0.31.13 + resolution: "@rocket.chat/fuselage-ui-kit@npm:0.31.13" dependencies: - "@rocket.chat/fuselage": ^0.31.12 - "@rocket.chat/fuselage-hooks": ^0.31.12 - "@rocket.chat/styled": ^0.31.12 - "@rocket.chat/ui-kit": ^0.31.12 + "@rocket.chat/fuselage": ^0.31.13 + "@rocket.chat/fuselage-hooks": ^0.31.13 + "@rocket.chat/styled": ^0.31.13 + "@rocket.chat/ui-kit": ^0.31.13 tslib: ^2.3.1 peerDependencies: "@rocket.chat/fuselage-polyfills": "*" "@rocket.chat/icons": "*" react: ^17.0.2 react-dom: ^17.0.2 - checksum: ae14aca1c93cf6aad70c9455ae72db6980b2157f89678ea8412cea953a437e0138f3a086b86812863b77757af4bc4f844e6ab64ff7eab520b33802011cff3863 + checksum: 5c42d76f07ba1e1a5638e76bb23fefc8a4e28a940c121cc8468c89dc9e3f06d87c7da87b867818364b42dad73c409fbf095d63065452d817d85d00317792e774 languageName: node linkType: hard -"@rocket.chat/fuselage@npm:0.31.13": +"@rocket.chat/fuselage@npm:0.31.13, @rocket.chat/fuselage@npm:^0.31.13": version: 0.31.13 resolution: "@rocket.chat/fuselage@npm:0.31.13" dependencies: @@ -4543,28 +4593,6 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage@npm:^0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/fuselage@npm:0.31.12" - dependencies: - "@rocket.chat/css-in-js": ^0.31.12 - "@rocket.chat/css-supports": ^0.31.12 - "@rocket.chat/fuselage-tokens": ^0.31.12 - "@rocket.chat/memo": ^0.31.12 - "@rocket.chat/styled": ^0.31.12 - invariant: ^2.2.4 - react-keyed-flatten-children: ^1.3.0 - peerDependencies: - "@rocket.chat/fuselage-hooks": "*" - "@rocket.chat/fuselage-polyfills": "*" - "@rocket.chat/icons": "*" - react: ^17.0.2 - react-dom: ^17.0.2 - react-virtuoso: 1.2.4 - checksum: 451dadcf2584e9eb1765b2fbbfa825d518a60cde6f185596ede138096c31a40e33b3e1b14ef84f5aaee1f271789eda01c18c9782e74c7d2998114c61e7bf0aac - languageName: node - linkType: hard - "@rocket.chat/icons@npm:^0.31.0, @rocket.chat/icons@npm:^0.31.9": version: 0.31.9 resolution: "@rocket.chat/icons@npm:0.31.9" @@ -4572,10 +4600,10 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/icons@npm:^0.31.12, @rocket.chat/icons@npm:~0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/icons@npm:0.31.12" - checksum: 101f9147f8eca1e83469c9c76ad9665682382507fe52af4bbebf08715e1f4968dcadabfc5e2d715bfdef4b2c38b3562d8495ab49dd38b9c204bee7fec0caf9ca +"@rocket.chat/icons@npm:^0.31.13, @rocket.chat/icons@npm:~0.31.12": + version: 0.31.13 + resolution: "@rocket.chat/icons@npm:0.31.13" + checksum: 17b9a6555a2bdfefdcbaabdbfcad766eb24f66ee5e02ac1e11b75918d8bf4256fdf42a082b1b56f4d748eb6171a7d54ff544e740e9e00702c79822710f9dc51f languageName: node linkType: hard @@ -4675,17 +4703,17 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/logo@npm:^0.31.12, @rocket.chat/logo@npm:~0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/logo@npm:0.31.12" +"@rocket.chat/logo@npm:^0.31.13, @rocket.chat/logo@npm:~0.31.12": + version: 0.31.13 + resolution: "@rocket.chat/logo@npm:0.31.13" dependencies: - "@rocket.chat/fuselage-hooks": ^0.31.12 - "@rocket.chat/styled": ^0.31.12 + "@rocket.chat/fuselage-hooks": ^0.31.13 + "@rocket.chat/styled": ^0.31.13 tslib: ^2.3.1 peerDependencies: react: 17.0.2 react-dom: 17.0.2 - checksum: d69e82f403606471a64a31825f5ab46f73a09c35121b2f9fc6db47c918dc9959eef8784e7197beca583ef6b56de26f85839e5630488ec22ed1ab91f00bf1a671 + checksum: 15e93ab6c5de9320fa2833dbe7d71b7247d084de8c006a6f22fe784046e7a3360b0911acfc9b5cebdac434e8a121fbb354f5000467b3392cbacf14f361636003 languageName: node linkType: hard @@ -4696,31 +4724,31 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/memo@npm:^0.31.12, @rocket.chat/memo@npm:~0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/memo@npm:0.31.12" - checksum: ad500775c6ab7a77e147a20a5ee478c42403286a2229d04a787341020d37ae0fefec62ae8f027b7f2113b123e7ea743380e30b715de5459f4a67c45983440da5 - languageName: node - linkType: hard - -"@rocket.chat/memo@npm:^0.31.13": +"@rocket.chat/memo@npm:^0.31.13, @rocket.chat/memo@npm:~0.31.12": version: 0.31.13 resolution: "@rocket.chat/memo@npm:0.31.13" checksum: ade0d9b637c3a31bdc7c02a3bbb7fc873e34ed165f43c3189eeed643c97cb5c3734a6696d68271910834b7203a96cc984c209ed90c2a9e8b71cac16c1dad6cc4 languageName: node linkType: hard -"@rocket.chat/message-parser@npm:next": +"@rocket.chat/memo@npm:~0.31.12-dev.10": version: 0.31.12-dev.10 - resolution: "@rocket.chat/message-parser@npm:0.31.12-dev.10" - checksum: 5d302792ab2988750b3b4c7ce4cf0aa42ec18bded9eb1179ec3ab8d9aa26d0ed0a1817cf2d60990dc61941b0ba39ba53396daa1fc36b1323e43110910ca40a62 + resolution: "@rocket.chat/memo@npm:0.31.12-dev.10" + checksum: 2f6da36ecc8117d32de5a664b3f2e32b6c37b63622d9a0b3f75b47db69aa677a3103cf871b72245a73b074341c3d5bfbb22b3cc1629d06f9617e1b690f84ae1a + languageName: node + linkType: hard + +"@rocket.chat/message-parser@npm:next": + version: 0.31.14-dev.1 + resolution: "@rocket.chat/message-parser@npm:0.31.14-dev.1" + checksum: c3b7e7da0881e39d980725a3b64f0ab16c01a522d2be82688abc8bf7011900b4dc01e778e0ead4beb5b84abe4e08acf073c97fab5d57cfe6b4eed38cfbf99b01 languageName: node linkType: hard "@rocket.chat/message-parser@npm:~0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/message-parser@npm:0.31.12" - checksum: cd7ba4aca924ea291135d087207212431160ae6a8d265b4551c0295ed0e922930b7623ae027b80bf760982057f521aaf9dee1bd42f37c7ac3141f69c66ad344c + version: 0.31.13 + resolution: "@rocket.chat/message-parser@npm:0.31.13" + checksum: f670170464c148eaff2218749b76423c17ffe02d325ca99fab39abe8dae5edb7ba1f36871b4a045e589d2af5fc607681bf45d4a544cb958136c13e83f18dc193 languageName: node linkType: hard @@ -4754,6 +4782,7 @@ __metadata: "@rocket.chat/fuselage": 0.31.13 "@rocket.chat/fuselage-hooks": ~0.31.12 "@rocket.chat/fuselage-polyfills": ~0.31.12 + "@rocket.chat/fuselage-toastbar": next "@rocket.chat/fuselage-tokens": ~0.31.12 "@rocket.chat/fuselage-ui-kit": ~0.31.12 "@rocket.chat/icons": ~0.31.12 @@ -5033,14 +5062,14 @@ __metadata: linkType: hard "@rocket.chat/onboarding-ui@npm:~0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/onboarding-ui@npm:0.31.12" - dependencies: - "@rocket.chat/fuselage": ^0.31.12 - "@rocket.chat/fuselage-hooks": ^0.31.12 - "@rocket.chat/icons": ^0.31.12 - "@rocket.chat/logo": ^0.31.12 - "@rocket.chat/styled": ^0.31.12 + version: 0.31.13 + resolution: "@rocket.chat/onboarding-ui@npm:0.31.13" + dependencies: + "@rocket.chat/fuselage": ^0.31.13 + "@rocket.chat/fuselage-hooks": ^0.31.13 + "@rocket.chat/icons": ^0.31.13 + "@rocket.chat/logo": ^0.31.13 + "@rocket.chat/styled": ^0.31.13 i18next: ~21.6.11 react-hook-form: ~7.27.0 tslib: ~2.3.1 @@ -5049,7 +5078,7 @@ __metadata: react: 17.0.2 react-dom: 17.0.2 react-i18next: ~11.15.4 - checksum: dc39a01e3dd32e00427b15a2aeae0e3772b6963e3504e1bce971e29170ffccc91483f11ddc6956c6f075418f95715832ef03debdf6cc4d9221548dd6be4f1848 + checksum: 55fa3a754683fc0b267cdeb30eef113d927d7bb2a91d19a11a06f9161de371171a9ad4d3b416cdcf2b22807b7d4bfe78caf8911d8003ac9cf23c6901eac60f79 languageName: node linkType: hard @@ -5086,11 +5115,11 @@ __metadata: linkType: hard "@rocket.chat/string-helpers@npm:~0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/string-helpers@npm:0.31.12" + version: 0.31.13 + resolution: "@rocket.chat/string-helpers@npm:0.31.13" dependencies: tslib: ^2.3.1 - checksum: b252ed3ad266ac6542fc4c0f3d57ee3af5cd236f48b4a32eb2980e8257c5723f240b60d185a6844c540777d21be953a6eb9ae5054ad2e24416881bb81ec41db5 + checksum: 3c1b28fc613948d014b79bd31e29d1cf064b27e3746e83683f6e29dea765ed20564ccd2b804a97dd8486ad2e937c2f01a78d8aeba2c6dbfb69fdc3d862272c35 languageName: node linkType: hard @@ -5113,16 +5142,6 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/styled@npm:^0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/styled@npm:0.31.12" - dependencies: - "@rocket.chat/css-in-js": ^0.31.12 - tslib: ^2.3.1 - checksum: 53b65682914872a34a58980a1df0aecea7c5b94e0075ea5a01c2340e6f89d2d0f745a36f301f5ea4a0f36294cbf9461b42a4ee856d2f96a599d04d84d60e5ec5 - languageName: node - linkType: hard - "@rocket.chat/styled@npm:^0.31.13": version: 0.31.13 resolution: "@rocket.chat/styled@npm:0.31.13" @@ -5133,6 +5152,16 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/styled@npm:~0.31.12-dev.10": + version: 0.31.12-dev.10 + resolution: "@rocket.chat/styled@npm:0.31.12-dev.10" + dependencies: + "@rocket.chat/css-in-js": ~0.31.12-dev.10 + tslib: ^2.3.1 + checksum: 17b35ffdde4bb2a61a1f5e54e34f720e197fbbe863f917a827f923e74cefce5b5514140bbf7a5b14c1687d841c99aacac27712297d859fe9697b70bcdc075786 + languageName: node + linkType: hard + "@rocket.chat/stylis-logical-props-middleware@npm:^0.31.11": version: 0.31.11 resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.11" @@ -5145,27 +5174,27 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/stylis-logical-props-middleware@npm:^0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.12" +"@rocket.chat/stylis-logical-props-middleware@npm:^0.31.13": + version: 0.31.13 + resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.13" dependencies: - "@rocket.chat/css-supports": ^0.31.12 + "@rocket.chat/css-supports": ^0.31.13 tslib: ^2.3.1 peerDependencies: stylis: 4.0.10 - checksum: 378b8cbfd5e9ebb21f05450afc67e692270b7527895ef70b63c5f72c03aeba7df4fe79d7306f8fa070bf803bc9b09f04424af1a0e67f7c40f3fefc1d5112989c + checksum: 343dd4dd31154c77512cdeacf709d5ba681c2ccfdba852bb1ef94c28cf3ea6cde409d2122d538b9af8e8f9a37ca2a2927eeef182ac25f47a18bb67512cbb8b5c languageName: node linkType: hard -"@rocket.chat/stylis-logical-props-middleware@npm:^0.31.13": - version: 0.31.13 - resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.13" +"@rocket.chat/stylis-logical-props-middleware@npm:~0.31.12-dev.10": + version: 0.31.12-dev.10 + resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.12-dev.10" dependencies: - "@rocket.chat/css-supports": ^0.31.13 + "@rocket.chat/css-supports": ~0.31.12-dev.10 tslib: ^2.3.1 peerDependencies: stylis: 4.0.10 - checksum: 343dd4dd31154c77512cdeacf709d5ba681c2ccfdba852bb1ef94c28cf3ea6cde409d2122d538b9af8e8f9a37ca2a2927eeef182ac25f47a18bb67512cbb8b5c + checksum: 792bbe7b221855f084f6121b3d00c80abf8d7a4daabcb2363d83e54f7ab86eec477c391cdb51ac5178d26e52747bea97e67d8a1b261d1d63e8403920a9926f5d languageName: node linkType: hard @@ -5204,10 +5233,10 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/ui-kit@npm:^0.31.12, @rocket.chat/ui-kit@npm:~0.31.12": - version: 0.31.12 - resolution: "@rocket.chat/ui-kit@npm:0.31.12" - checksum: 923a562a42e454d177ecbaaf3c4ca3b341da9d9e1fa573fcd7659d2788ad47f7d094ffe5faca2448c4488685d720859b7b7cff3501fcee2a47d5d7505871c8fa +"@rocket.chat/ui-kit@npm:^0.31.13, @rocket.chat/ui-kit@npm:~0.31.12": + version: 0.31.13 + resolution: "@rocket.chat/ui-kit@npm:0.31.13" + checksum: 94926dee4687e69b2a51a3570d09ba051b0638b3364499daed864e36480b56c538a20e3b66b02563492c7c2fe15b101ab03850d86bdac46f3fab442a9f93ef10 languageName: node linkType: hard @@ -6423,8 +6452,6 @@ __metadata: version: 6.4.22 resolution: "@storybook/postinstall@npm:6.4.22" dependencies: - "@types/npmlog": ^4.1.2 - chalk: ^4.1.0 core-js: ^3.8.2 checksum: d406b2fdfc4350d52bcfe2f2447e90f66ae82bddaa659456e8d8211c335a3bf18968b2701ec7aafc0a3640d816c61f0081f143438b75be9bd4da2c515f534fc5 languageName: node @@ -7538,9 +7565,9 @@ __metadata: linkType: hard "@types/node@npm:>=12.0.0, @types/node@npm:>=8.9.0": - version: 17.0.35 - resolution: "@types/node@npm:17.0.35" - checksum: 7a24946ae7fd20267ed92466384f594e448bfb151081158d565cc635d406ecb29ea8fb85fcd2a1f71efccf26fb5bd3c6f509bde56077eb8b832b847a6664bc62 + version: 17.0.40 + resolution: "@types/node@npm:17.0.40" + checksum: e3b2fe876672fbe4be84ce17773944eb2f5eaba50e2c6c0536bdf6d4972ed6488581580581f154183fdc8f2d56fa42a42e3d6e83b9b71ee25adea16a84765e92 languageName: node linkType: hard @@ -7552,9 +7579,9 @@ __metadata: linkType: hard "@types/node@npm:^14.18.15": - version: 14.18.18 - resolution: "@types/node@npm:14.18.18" - checksum: a165225cd2603f6e62af8407449e4a4407305e03b41c1adf6b186fdf546e1a03c8214217659b5b36c556947c0c06234993ac880d4db6378136a7a810d47e0742 + version: 14.18.20 + resolution: "@types/node@npm:14.18.20" + checksum: 999d8ef25c983c9ca7c3365d6878a7f9387a90e10fdec9b6fc26764ae0b52768b8ca4f7e3fcab6056326dcc613b730f2a58b59acacdc5d5863fa4b7a09c1f85f languageName: node linkType: hard @@ -8695,7 +8722,7 @@ __metadata: human-interval: ~1.0.0 moment-timezone: ~0.5.27 mongodb: ~3.5.0 - checksum: acb4ebb7e7356f6e53e810d821eb6aa3d88bbfb9e85183e707517bee6d1eea1f189f38bdf0dd2b91360492ab7643134d510c320d2523d86596498ab98e59735b + checksum: f5f68008298f9482631f1f494e392cd6b8ba7971a3b0ece81ae2abe60f53d67973ff4476156fa5c9c41b8b58c4ccd284e95c545e0523996dfd05f9a80b843e07 languageName: node linkType: hard @@ -9666,8 +9693,8 @@ __metadata: linkType: hard "aws-sdk@npm:^2.1121.0": - version: 2.1140.0 - resolution: "aws-sdk@npm:2.1140.0" + version: 2.1149.0 + resolution: "aws-sdk@npm:2.1149.0" dependencies: buffer: 4.9.2 events: 1.1.1 @@ -9676,9 +9703,9 @@ __metadata: querystring: 0.2.0 sax: 1.2.1 url: 0.10.3 - uuid: 3.3.2 + uuid: 8.0.0 xml2js: 0.4.19 - checksum: 5c81ff6486e818b21b56b117bc3aede6b74f2844c9485a25963b0b960e998bf5348a48f0dfc2ed0ad1d9479c8e3ca793a1eb5a30e360944ce3cfec47a5631ee5 + checksum: 2c9efa7acbeab10da6c39d3151a6e2885add7266924f6b50e5aa139e87c80ae50fb22e1bfe9c588e7c1fa0f439fdbe354c74e3ed3ce69582ebaad5a948b5881c languageName: node linkType: hard @@ -11552,9 +11579,9 @@ __metadata: linkType: hard "chart.js@npm:^3.7.1": - version: 3.7.1 - resolution: "chart.js@npm:3.7.1" - checksum: f9d118d3b7dd3c36b6da7a8d71ac9e5d9673b81095cc66c3f61ff91674e20020c6700f8c9c6f93713fa8474eb471ded106114346ccc6afa88b4a7d0eb73dcea4 + version: 3.8.0 + resolution: "chart.js@npm:3.8.0" + checksum: 0360212bf5d534effc8475a1aa705ba7257a315af368b5e8eecf476f0bd57a6bb80ad740e870d552f2d11e134dd1497ccef02950d7e626c21a400ff00ecf59af languageName: node linkType: hard @@ -12530,12 +12557,12 @@ __metadata: linkType: hard "core-js-compat@npm:^3.22.1": - version: 3.22.6 - resolution: "core-js-compat@npm:3.22.6" + version: 3.22.8 + resolution: "core-js-compat@npm:3.22.8" dependencies: browserslist: ^4.20.3 semver: 7.0.0 - checksum: 6b83b87abeb04c08b54bdc6a6756ad6d1503aeadebc598a162bfe1044a31183864bb3016f16c839d40235545c009dc8aea6421ada0d2e18b5fdf93c6059eb380 + checksum: 0c82d9110dcb267c2f5547c61b62f8043793d203523048169176b8badf0b73f3792624342b85d9c923df8eb8971b4aa468b160abb81a023d183c5951e4f05a66 languageName: node linkType: hard @@ -13199,9 +13226,9 @@ __metadata: linkType: hard "csv-parse@npm:^5.0.4": - version: 5.0.4 - resolution: "csv-parse@npm:5.0.4" - checksum: e38b4fb8dec7a64b891d78ab3cf7f59b721e4027e3c773a2b0311dc538a82681f4f01aaa03c209ac47eb37e26c00a1134bf3844ade1685bf53ce85ffb17a63e3 + version: 5.1.0 + resolution: "csv-parse@npm:5.1.0" + checksum: c64dec952823a25508fdbbc40036bbf832dbc9df559abbc7cdc62e94d4733f97e2a3a896e2fb6bb028b997a766da4a1cb15521b8754a0d7db3dfcb6d764d9a23 languageName: node linkType: hard @@ -15138,13 +15165,13 @@ __metadata: linkType: hard "eslint-plugin-testing-library@npm:^5.3.1": - version: 5.5.0 - resolution: "eslint-plugin-testing-library@npm:5.5.0" + version: 5.5.1 + resolution: "eslint-plugin-testing-library@npm:5.5.1" dependencies: "@typescript-eslint/utils": ^5.13.0 peerDependencies: eslint: ^7.5.0 || ^8.0.0 - checksum: 7f42e2af84a0b5d1bba86fe9838abdbfa1c4f5dae66db215f592a216f82a2164a23181841228872d7116f38398bbc671e60a1e3b9840f0a8da1972c7aac8bdcd + checksum: 558994da12e6a9ff0c4f71c2e63a23746b6323d171062032843591e0fca6ce3811f979cf82e11db003c8b4f1d9842cb75301bfaa9e88d1a399b11ea6686aadcc languageName: node linkType: hard @@ -15371,8 +15398,8 @@ __metadata: linkType: hard "eslint@npm:^8.14.0": - version: 8.16.0 - resolution: "eslint@npm:8.16.0" + version: 8.17.0 + resolution: "eslint@npm:8.17.0" dependencies: "@eslint/eslintrc": ^1.3.0 "@humanwhocodes/config-array": ^0.9.2 @@ -15411,7 +15438,7 @@ __metadata: v8-compile-cache: ^2.0.3 bin: eslint: bin/eslint.js - checksum: 654a0200b49dc07280673fee13cdfb04326466790e031dfa9660b69fba3b1cf766a51504328f9de56bd18e6b5eb7578985cf29dc7f016c5ec851220ff9db95eb + checksum: b484c96681c6b19f5b437f664623f1cd310d3ee9be88400d8450e086e664cd968a9dc202f0b0678578fd50e7a445b92586efe8c787de5073ff2f83213b00bb7b languageName: node linkType: hard @@ -16623,12 +16650,12 @@ __metadata: linkType: hard "follow-redirects@npm:^1.14.9": - version: 1.15.0 - resolution: "follow-redirects@npm:1.15.0" + version: 1.15.1 + resolution: "follow-redirects@npm:1.15.1" peerDependenciesMeta: debug: optional: true - checksum: eaec81c3e0ae57aae2422e38ad3539d0e7279b3a63f9681eeea319bb683dea67502c4e097136b8ce9721542b4e236e092b6b49e34e326cdd7733c274f0a3f378 + checksum: 6aa4e3e3cdfa3b9314801a1cd192ba756a53479d9d8cca65bf4db3a3e8834e62139245cd2f9566147c8dfe2efff1700d3e6aefd103de4004a7b99985e71dd533 languageName: node linkType: hard @@ -21352,9 +21379,9 @@ __metadata: linkType: hard "jsrsasign@npm:^10.5.19": - version: 10.5.22 - resolution: "jsrsasign@npm:10.5.22" - checksum: c41fe2b4adf88d4fab1e90e3f0a38a0b034afdc100f2bd1828a5d003968e80a866117f7cc249e531998f78130460882728010eeeeb043c50b9dd27bb27a0cbb3 + version: 10.5.24 + resolution: "jsrsasign@npm:10.5.24" + checksum: 745d7945fe72d15ef4310bf77c3537439f53f355febd481d978aa4a2e677206e7eb40df018f756498a3532aeb4232eabb8c9491fe0dd35c8a4de4f5e648313d8 languageName: node linkType: hard @@ -23222,7 +23249,7 @@ __metadata: languageName: node linkType: hard -"mime@npm:^2.2.0, mime@npm:^2.4.4, mime@npm:^2.4.6, mime@npm:^2.5.0": +"mime@npm:2.6.0, mime@npm:^2.2.0, mime@npm:^2.4.4, mime@npm:^2.4.6": version: 2.6.0 resolution: "mime@npm:2.6.0" bin: @@ -24017,11 +24044,11 @@ __metadata: linkType: hard "nats@npm:^2.6.1": - version: 2.7.0 - resolution: "nats@npm:2.7.0" + version: 2.7.1 + resolution: "nats@npm:2.7.1" dependencies: nkeys.js: ^1.0.0-9 - checksum: cf9a68cbcbc8566094a3f891eebc10127ab1e71c4615184647355e129d1b3730364267b27c23c5b356f2c0dc3e71c2a33ddc30f6dfb4b9f51b259a7a5711b2c1 + checksum: 6022d8b924d947f51d58e25f7790f3b532c26b1dd1ffddf1139c1ac8ab54c2c41ad5423127809f5bbbce54df63adb808a3a1cf5715a4d75f807fafc63ca294ac languageName: node linkType: hard @@ -27567,7 +27594,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.10.3, qs@npm:^6.10.0, qs@npm:^6.10.3, qs@npm:^6.5.1, qs@npm:^6.9.4, qs@npm:^6.9.6": +"qs@npm:6.10.3, qs@npm:^6.10.0, qs@npm:^6.5.1, qs@npm:^6.9.4, qs@npm:^6.9.6": version: 6.10.3 resolution: "qs@npm:6.10.3" dependencies: @@ -27590,6 +27617,15 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.10.3": + version: 6.10.4 + resolution: "qs@npm:6.10.4" + dependencies: + side-channel: ^1.0.4 + checksum: 31e4fedd759d01eae52dde6692abab175f9af3e639993c5caaa513a2a3607b34d8058d3ae52ceeccf37c3025f22ed5e90e9ddd6c2537e19c0562ddd10dc5b1eb + languageName: node + linkType: hard + "qs@npm:~6.5.2": version: 6.5.3 resolution: "qs@npm:6.5.3" @@ -31490,8 +31526,8 @@ __metadata: linkType: hard "superagent@npm:^7.1.3": - version: 7.1.3 - resolution: "superagent@npm:7.1.3" + version: 7.1.6 + resolution: "superagent@npm:7.1.6" dependencies: component-emitter: ^1.3.0 cookiejar: ^2.1.3 @@ -31500,11 +31536,11 @@ __metadata: form-data: ^4.0.0 formidable: ^2.0.1 methods: ^1.1.2 - mime: ^2.5.0 + mime: 2.6.0 qs: ^6.10.3 readable-stream: ^3.6.0 semver: ^7.3.7 - checksum: 436045d555d35c282de7bcba85102b1421470bdc80781c9a0b7ab7c639675b4eca026a71301974935f3de0d33782a0392274e24f3915335b81a78a04b48eeee5 + checksum: b73316836003219f1a4886a6d77dd28551a6784c30e871009fb7bad699fae772b20370d39d2ccb5a543c9335ce12b43a76b959a3ca983f1d6365cb4b5682c08f languageName: node linkType: hard @@ -32979,9 +33015,9 @@ __metadata: linkType: hard "underscore@npm:^1.13.3": - version: 1.13.3 - resolution: "underscore@npm:1.13.3" - checksum: 1ea0b333ee20fdb3dcf20883d505817bf9de3dc67f0a674c1c49428cec5e480178ce5f6b7c9f72c3a8fe05a1e344dcec5c918a7f89aa1661632aa378612a2264 + version: 1.13.4 + resolution: "underscore@npm:1.13.4" + checksum: 6b04f66cd454e8793a552dc49c71e24e5208a29b9d9c0af988a96948af79103399c36fb15db43f3629bfed152f8b1fe94f44e1249e9d196069c0fc7edfadb636 languageName: node linkType: hard @@ -33634,6 +33670,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:8.0.0": + version: 8.0.0 + resolution: "uuid@npm:8.0.0" + bin: + uuid: dist/bin/uuid + checksum: 56d4e23aa7ac26fa2db6bd1778db34cb8c9f5a10df1770a27167874bf6705fc8f14a4ac414af58a0d96c7653b2bd4848510b29d1c2ef8c91ccb17429c1872b5e + languageName: node + linkType: hard + "uuid@npm:^3.0.1, uuid@npm:^3.1.0, uuid@npm:^3.2.1, uuid@npm:^3.3.2": version: 3.4.0 resolution: "uuid@npm:3.4.0" From 05374116d289be4cbfba0c3879c08d7c3fdb8dc5 Mon Sep 17 00:00:00 2001 From: gabriellsh <40830821+gabriellsh@users.noreply.github.com> Date: Tue, 7 Jun 2022 00:58:27 -0300 Subject: [PATCH 7/8] [FIX] getUserMentionsByChannel method room permission (#25748) --- .../mentions/server/methods/getUserMentionsByChannel.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/mentions/server/methods/getUserMentionsByChannel.js b/apps/meteor/app/mentions/server/methods/getUserMentionsByChannel.js index d554fe1b32a6..9c622e3d6581 100644 --- a/apps/meteor/app/mentions/server/methods/getUserMentionsByChannel.js +++ b/apps/meteor/app/mentions/server/methods/getUserMentionsByChannel.js @@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import { Rooms, Users, Messages } from '../../../models'; +import { canAccessRoom } from '../../../authorization/server'; Meteor.methods({ getUserMentionsByChannel({ roomId, options }) { @@ -13,16 +14,16 @@ Meteor.methods({ }); } + const user = Users.findOneById(Meteor.userId()); + const room = Rooms.findOneById(roomId); - if (!room) { + if (!room || !canAccessRoom(room, user)) { throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'getUserMentionsByChannel', }); } - const user = Users.findOneById(Meteor.userId()); - return Messages.findVisibleByMentionAndRoomId(user.username, roomId, options).fetch(); }, }); From 440dfaaf754cd39a8ce6bed2df0bf82a1e6d8906 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista <tasso.evangelista@rocket.chat> Date: Tue, 7 Jun 2022 10:06:24 -0300 Subject: [PATCH 8/8] [BREAK] Remove Blockstack authentication (#25649) --- apps/meteor/app/api/server/v1/misc.js | 2 - apps/meteor/app/blockstack/client/index.js | 53 -- apps/meteor/app/blockstack/client/routes.js | 46 -- apps/meteor/app/blockstack/server/index.js | 3 - apps/meteor/app/blockstack/server/logger.js | 3 - .../app/blockstack/server/loginHandler.js | 56 -- apps/meteor/app/blockstack/server/routes.js | 31 -- apps/meteor/app/blockstack/server/settings.js | 70 --- .../app/blockstack/server/tokenHandler.js | 63 --- .../app/blockstack/server/userHandler.js | 81 --- .../functions/getAvatarSuggestionForUser.js | 9 - .../lib/server/methods/refreshOAuthService.ts | 2 +- .../client/models/CachedCollection.js | 2 +- .../server/functions/getDefaultUserFields.ts | 1 - apps/meteor/client/importPackages.ts | 1 - apps/meteor/package.json | 1 - .../rocketchat-i18n/i18n/en.i18n.json | 5 - apps/meteor/server/importPackages.ts | 1 - .../meteor/server/startup/migrations/index.ts | 1 + apps/meteor/server/startup/migrations/v267.ts | 27 + yarn.lock | 506 +----------------- 21 files changed, 47 insertions(+), 917 deletions(-) delete mode 100644 apps/meteor/app/blockstack/client/index.js delete mode 100644 apps/meteor/app/blockstack/client/routes.js delete mode 100644 apps/meteor/app/blockstack/server/index.js delete mode 100644 apps/meteor/app/blockstack/server/logger.js delete mode 100644 apps/meteor/app/blockstack/server/loginHandler.js delete mode 100644 apps/meteor/app/blockstack/server/routes.js delete mode 100644 apps/meteor/app/blockstack/server/settings.js delete mode 100644 apps/meteor/app/blockstack/server/tokenHandler.js delete mode 100644 apps/meteor/app/blockstack/server/userHandler.js create mode 100644 apps/meteor/server/startup/migrations/v267.ts diff --git a/apps/meteor/app/api/server/v1/misc.js b/apps/meteor/app/api/server/v1/misc.js index 77b8519c3818..6d6d94438c6c 100644 --- a/apps/meteor/app/api/server/v1/misc.js +++ b/apps/meteor/app/api/server/v1/misc.js @@ -97,8 +97,6 @@ import { SystemLogger } from '../../../../server/lib/logger/system'; * type: object * tokenpass: * type: object - * blockstack: - * type: object * password: * type: object * properties: diff --git a/apps/meteor/app/blockstack/client/index.js b/apps/meteor/app/blockstack/client/index.js deleted file mode 100644 index 23420dbe4d70..000000000000 --- a/apps/meteor/app/blockstack/client/index.js +++ /dev/null @@ -1,53 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { ServiceConfiguration } from 'meteor/service-configuration'; -import { check, Match } from 'meteor/check'; -import { Session } from 'meteor/session'; -import './routes'; - -const handleError = (error) => error && Session.set('errorMessage', error.reason || 'Unknown error'); - -// TODO: allow serviceConfig.loginStyle == popup -Meteor.loginWithBlockstack = (options, callback = handleError) => { - if (!options || !options.redirectURI) { - options = ServiceConfiguration.configurations.findOne({ - service: 'blockstack', - }); - - options.blockstackIDHost = Meteor.Device.isDesktop() ? 'http://localhost:8888/auth' : 'https://blockstack.org/auth'; - - options.scopes = ['store_write']; - } - - try { - check( - options, - Match.ObjectIncluding({ - blockstackIDHost: String, - redirectURI: String, - manifestURI: String, - }), - ); - - import('blockstack/dist/blockstack').then(({ redirectToSignIn }) => - redirectToSignIn(options.redirectURI, options.manifestURI, options.scopes), - ); - } catch (err) { - callback.call(Meteor, err); - } -}; - -const meteorLogout = Meteor.logout; -Meteor.logout = (...args) => { - const serviceConfig = ServiceConfiguration.configurations.findOne({ - service: 'blockstack', - }); - - const blockstackAuth = Session.get('blockstack_auth'); - - if (serviceConfig && blockstackAuth) { - Session.delete('blockstack_auth'); - import('blockstack/dist/blockstack').then(({ signUserOut }) => signUserOut(window.location.href)); - } - - return meteorLogout(...args); -}; diff --git a/apps/meteor/app/blockstack/client/routes.js b/apps/meteor/app/blockstack/client/routes.js deleted file mode 100644 index 1f0e340c5eda..000000000000 --- a/apps/meteor/app/blockstack/client/routes.js +++ /dev/null @@ -1,46 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Accounts } from 'meteor/accounts-base'; -import { FlowRouter } from 'meteor/kadira:flow-router'; - -const blockstackLogin = (authResponse, userData = {}) => { - Accounts.callLoginMethod({ - methodArguments: [ - { - blockstack: true, - authResponse, - userData, - }, - ], - userCallback() { - FlowRouter.go('home'); - }, - }); -}; - -FlowRouter.route('/_blockstack/validate', { - name: 'blockstackValidate', - async action(params, queryParams) { - const blockstack = await import('blockstack/dist/blockstack'); - - if (Meteor.userId()) { - console.log('Blockstack Auth requested when already logged in. Reloading.'); - return FlowRouter.go('home'); - } - - if (queryParams.authResponse == null) { - throw new Meteor.Error('Blockstack: Auth request without response param.'); - } - - let userData; - - if (blockstack.isUserSignedIn()) { - userData = blockstack.loadUserData(); - } - - if (blockstack.isSignInPending()) { - userData = await blockstack.handlePendingSignIn(); - } - - blockstackLogin(queryParams.authResponse, userData); - }, -}); diff --git a/apps/meteor/app/blockstack/server/index.js b/apps/meteor/app/blockstack/server/index.js deleted file mode 100644 index f0cf809aaf0e..000000000000 --- a/apps/meteor/app/blockstack/server/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import './routes.js'; -import './settings.js'; -import './loginHandler.js'; diff --git a/apps/meteor/app/blockstack/server/logger.js b/apps/meteor/app/blockstack/server/logger.js deleted file mode 100644 index e88f4df9bf1c..000000000000 --- a/apps/meteor/app/blockstack/server/logger.js +++ /dev/null @@ -1,3 +0,0 @@ -import { Logger } from '../../logger'; - -export const logger = new Logger('Blockstack'); diff --git a/apps/meteor/app/blockstack/server/loginHandler.js b/apps/meteor/app/blockstack/server/loginHandler.js deleted file mode 100644 index c1f50416d5c8..000000000000 --- a/apps/meteor/app/blockstack/server/loginHandler.js +++ /dev/null @@ -1,56 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Accounts } from 'meteor/accounts-base'; - -import { updateOrCreateUser } from './userHandler'; -import { handleAccessToken } from './tokenHandler'; -import { logger } from './logger'; -import { settings } from '../../settings/server'; -import { Users } from '../../models'; -import { setUserAvatar } from '../../lib'; - -// Blockstack login handler, triggered by a blockstack authResponse in route -Accounts.registerLoginHandler('blockstack', (loginRequest) => { - if (!loginRequest.blockstack || !loginRequest.authResponse) { - return; - } - - if (!settings.get('Blockstack_Enable')) { - return; - } - - logger.debug('Processing login request', loginRequest); - - const auth = handleAccessToken(loginRequest); - - // TODO: Fix #9484 and re-instate usage of accounts helper - // const result = Accounts.updateOrCreateUserFromExternalService('blockstack', auth.serviceData, auth.options) - const result = updateOrCreateUser(auth.serviceData, auth.options); - logger.debug('User create/update result', result); - - // Ensure processing succeeded - if (result === undefined || result.userId === undefined) { - return { - type: 'blockstack', - error: new Meteor.Error(Accounts.LoginCancelledError.numericError, 'User creation failed from Blockstack response token'), - }; - } - - if (result.isNew) { - try { - const user = Users.findOneById(result.userId, { - fields: { 'services.blockstack.image': 1, 'username': 1 }, - }); - if (user && user.services && user.services.blockstack && user.services.blockstack.image) { - Meteor.runAsUser(user._id, () => { - setUserAvatar(user, user.services.blockstack.image, undefined, 'url'); - }); - } - } catch (e) { - logger.error(e); - } - } - - delete result.isNew; - - return result; -}); diff --git a/apps/meteor/app/blockstack/server/routes.js b/apps/meteor/app/blockstack/server/routes.js deleted file mode 100644 index 9f6bc061f787..000000000000 --- a/apps/meteor/app/blockstack/server/routes.js +++ /dev/null @@ -1,31 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { WebApp } from 'meteor/webapp'; - -import { settings } from '../../settings/server'; -import { RocketChatAssets } from '../../assets/server'; - -WebApp.connectHandlers.use( - '/_blockstack/manifest', - Meteor.bindEnvironment(function (req, res) { - const name = settings.get('Site_Name'); - const startUrl = Meteor.absoluteUrl(); - const description = settings.get('Blockstack_Auth_Description'); - const iconUrl = RocketChatAssets.getURL('Assets_favicon_192'); - - res.writeHead(200, { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', - }); - - res.end(`{ - "name": "${name}", - "start_url": "${startUrl}", - "description": "${description}", - "icons": [{ - "src": "${iconUrl}", - "sizes": "192x192", - "type": "image/png" - }] - }`); - }), -); diff --git a/apps/meteor/app/blockstack/server/settings.js b/apps/meteor/app/blockstack/server/settings.js deleted file mode 100644 index 640e35187a6b..000000000000 --- a/apps/meteor/app/blockstack/server/settings.js +++ /dev/null @@ -1,70 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { ServiceConfiguration } from 'meteor/service-configuration'; - -import { logger } from './logger'; -import { settings, settingsRegistry } from '../../settings/server'; - -const defaults = { - enable: false, - loginStyle: 'redirect', - generateUsername: false, - manifestURI: Meteor.absoluteUrl('_blockstack/manifest'), - redirectURI: Meteor.absoluteUrl('_blockstack/validate'), - authDescription: 'Rocket.Chat login', - buttonLabelText: 'Blockstack', - buttonColor: '#271132', - buttonLabelColor: '#ffffff', -}; - -Meteor.startup(() => { - settingsRegistry.addGroup('Blockstack', function () { - this.add('Blockstack_Enable', defaults.enable, { - type: 'boolean', - i18nLabel: 'Enable', - }); - this.add('Blockstack_Auth_Description', defaults.authDescription, { - type: 'string', - }); - this.add('Blockstack_ButtonLabelText', defaults.buttonLabelText, { - type: 'string', - }); - this.add('Blockstack_Generate_Username', defaults.generateUsername, { - type: 'boolean', - }); - }); -}); - -// Helper to return all Blockstack settings -const getSettings = () => - Object.assign({}, defaults, { - enable: settings.get('Blockstack_Enable'), - authDescription: settings.get('Blockstack_Auth_Description'), - buttonLabelText: settings.get('Blockstack_ButtonLabelText'), - generateUsername: settings.get('Blockstack_Generate_Username'), - }); - -// Add settings to auth provider configs on startup -settings.watchMultiple( - ['Blockstack_Enable', 'Blockstack_Auth_Description', 'Blockstack_ButtonLabelText', 'Blockstack_Generate_Username'], - () => { - const serviceConfig = getSettings(); - - if (!serviceConfig.enable) { - logger.debug('Blockstack not enabled', serviceConfig); - return ServiceConfiguration.configurations.remove({ - service: 'blockstack', - }); - } - - ServiceConfiguration.configurations.upsert( - { - service: 'blockstack', - }, - { - $set: serviceConfig, - }, - ); - - logger.debug('Init Blockstack auth', serviceConfig); - }, -); diff --git a/apps/meteor/app/blockstack/server/tokenHandler.js b/apps/meteor/app/blockstack/server/tokenHandler.js deleted file mode 100644 index 29079e3eb0a3..000000000000 --- a/apps/meteor/app/blockstack/server/tokenHandler.js +++ /dev/null @@ -1,63 +0,0 @@ -import { decodeToken } from 'blockstack'; -import { Meteor } from 'meteor/meteor'; -import { Accounts } from 'meteor/accounts-base'; -import { Match, check } from 'meteor/check'; - -import { logger } from './logger'; - -// Handler extracts data from JSON and tokenised reponse. -// Reflects OAuth token service, with some slight modifications for Blockstack. -// -// Uses 'iss' (issuer) as unique key (decentralised ID) for user. -// The 'did' final portion of the blockstack decentralised ID, is displayed as -// your profile ID in the service. This isn't used yet, but could be useful -// to link accounts if identity providers other than btc address are added. -export const handleAccessToken = (loginRequest) => { - logger.debug('Login request received', loginRequest); - - check( - loginRequest, - Match.ObjectIncluding({ - authResponse: String, - userData: Object, - }), - ); - - // Decode auth response for user attributes - const { username, profile } = loginRequest.userData; - const decodedToken = decodeToken(loginRequest.authResponse).payload; - - profile.username = username; - - logger.debug('User data', loginRequest.userData); - logger.debug('Login decoded', decodedToken); - - const { iss, iat, exp } = decodedToken; - - if (!iss) { - return { - type: 'blockstack', - error: new Meteor.Error(Accounts.LoginCancelledError.numericError, 'Insufficient data in auth response token'), - }; - } - - // Collect basic auth provider details - const serviceData = { - id: iss, - did: iss.split(':').pop(), - issuedAt: new Date(iat * 1000), - expiresAt: new Date(exp * 1000), - }; - - // Add Avatar image source to use for auth service suggestions - if (Array.isArray(profile.image) && profile.image.length) { - serviceData.image = profile.image[0].contentUrl; - } - - logger.debug('Login data', serviceData, profile); - - return { - serviceData, - options: { profile }, - }; -}; diff --git a/apps/meteor/app/blockstack/server/userHandler.js b/apps/meteor/app/blockstack/server/userHandler.js deleted file mode 100644 index 393129f74a99..000000000000 --- a/apps/meteor/app/blockstack/server/userHandler.js +++ /dev/null @@ -1,81 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Accounts } from 'meteor/accounts-base'; -import { ServiceConfiguration } from 'meteor/service-configuration'; - -import { logger } from './logger'; -import { settings } from '../../settings/server'; -import { generateUsernameSuggestion } from '../../lib'; - -// Updates or creates a user after we authenticate with Blockstack -// Clones Accounts.updateOrCreateUserFromExternalService with some modifications -export const updateOrCreateUser = (serviceData, options) => { - const serviceConfig = ServiceConfiguration.configurations.findOne({ service: 'blockstack' }); - logger.debug('Auth config', serviceConfig); - - // Extract user data from service / token - const { id, did } = serviceData; - const { profile } = options; - - // Look for existing Blockstack user - const user = Meteor.users.findOne({ 'services.blockstack.id': id }); - let userId; - let isNew = false; - - // Use found or create a user - if (user) { - logger.info(`User login with Blockstack ID ${id}`); - userId = user._id; - } else { - isNew = true; - let emails = []; - if (!Array.isArray(profile.emails)) { - // Fix absense of emails by adding placeholder address using decentralised - // ID at blockstack.email - a holding domain only, no MX record, does not - // process email, may be used in future to provide decentralised email via - // gaia, encrypting mail for DID user only. @TODO: document this approach. - emails.push({ address: `${did}@blockstack.email`, verified: false }); - } else { - const verified = settings.get('Accounts_Verify_Email_For_External_Accounts'); - // Reformat array of emails into expected format if they exist - emails = profile.emails.map((address) => ({ address, verified })); - } - - const newUser = { - name: profile.name, - active: true, - emails, - services: { blockstack: serviceData }, - }; - - // Set username same as in blockstack, or suggest if none - if (profile.name) { - newUser.name = profile.name; - } - - // Take profile username if exists, or generate one if enabled - if (profile.username && profile.username !== '') { - newUser.username = profile.username; - } else if (serviceConfig.generateUsername === true) { - newUser.username = generateUsernameSuggestion(newUser); - } - // If no username at this point it will suggest one from the name - - // Create and get created user to make a couple more mods before returning - logger.info(`Creating user for Blockstack ID ${id}`); - userId = Accounts.insertUserDoc({}, newUser); - logger.debug('New user ${ userId }', newUser); - } - - // Add login token for blockstack auth session (take expiration from response) - // TODO: Regquired method result format ignores `.when` - const { token } = Accounts._generateStampedLoginToken(); - const tokenExpires = serviceData.expiresAt; - - return { - type: 'blockstack', - userId, - token, - tokenExpires, - isNew, - }; -}; diff --git a/apps/meteor/app/lib/server/functions/getAvatarSuggestionForUser.js b/apps/meteor/app/lib/server/functions/getAvatarSuggestionForUser.js index 57d788d70649..e7bf5755f362 100644 --- a/apps/meteor/app/lib/server/functions/getAvatarSuggestionForUser.js +++ b/apps/meteor/app/lib/server/functions/getAvatarSuggestionForUser.js @@ -74,15 +74,6 @@ const avatarProviders = { } }, - blockstack(user) { - if (user.services && user.services.blockstack && user.services.blockstack.image && settings.get('Blockstack_Enable')) { - return { - service: 'blockstack', - url: user.services.blockstack.image, - }; - } - }, - customOAuth(user) { const avatars = []; for (const service in user.services) { diff --git a/apps/meteor/app/lib/server/methods/refreshOAuthService.ts b/apps/meteor/app/lib/server/methods/refreshOAuthService.ts index 1373cd10d436..466316f4ba76 100644 --- a/apps/meteor/app/lib/server/methods/refreshOAuthService.ts +++ b/apps/meteor/app/lib/server/methods/refreshOAuthService.ts @@ -23,6 +23,6 @@ Meteor.methods({ ServiceConfiguration.configurations.remove({}); - await Settings.update({ _id: /^(Accounts_OAuth_|SAML_|CAS_|Blockstack_).+/ }, { $set: { _updatedAt: new Date() } }, { multi: true }); + await Settings.update({ _id: /^(Accounts_OAuth_|SAML_|CAS_).+/ }, { $set: { _updatedAt: new Date() } }, { multi: true }); }, }); diff --git a/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.js b/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.js index e0d9de196656..3da924f71675 100644 --- a/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.js +++ b/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.js @@ -129,7 +129,7 @@ export class CachedCollection extends Emitter { userRelated = true, listenChangesForLoggedUsersOnly = false, useSync = true, - version = 16, + version = 17, maxCacheTime = 60 * 60 * 24 * 30, onSyncData = (/* action, record */) => {}, }) { diff --git a/apps/meteor/app/utils/server/functions/getDefaultUserFields.ts b/apps/meteor/app/utils/server/functions/getDefaultUserFields.ts index 574d118198b4..2f0dce22cd89 100644 --- a/apps/meteor/app/utils/server/functions/getDefaultUserFields.ts +++ b/apps/meteor/app/utils/server/functions/getDefaultUserFields.ts @@ -27,7 +27,6 @@ export const getDefaultUserFields = (): DefaultUserFields => ({ 'services.github': 1, 'services.gitlab': 1, 'services.tokenpass': 1, - 'services.blockstack': 1, 'services.password.bcrypt': 1, 'services.totp.enabled': 1, 'services.email2fa.enabled': 1, diff --git a/apps/meteor/client/importPackages.ts b/apps/meteor/client/importPackages.ts index 4ee4fc8b4688..554c3e280396 100644 --- a/apps/meteor/client/importPackages.ts +++ b/apps/meteor/client/importPackages.ts @@ -69,7 +69,6 @@ import '../app/wordpress/client'; import '../app/nrr/client'; import '../app/meteor-accounts-saml/client'; import '../app/e2e/client'; -import '../app/blockstack/client'; import '../app/version-check/client'; import '../app/search/client'; import '../app/chatpal-search/client'; diff --git a/apps/meteor/package.json b/apps/meteor/package.json index b11d6ed451f1..d04f5bea4569 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -227,7 +227,6 @@ "aws-sdk": "^2.1121.0", "bad-words": "^3.0.4", "bcrypt": "^5.0.1", - "blockstack": "19.3.0", "body-parser": "1.20.0", "bson": "^4.6.3", "busboy": "^1.6.0", diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index 3b2158bfdbdb..3a8be97ee912 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -683,11 +683,6 @@ "Block_Multiple_Failed_Logins_Notify_Failed_Channel_Desc": "This is where notifications will be received. Make sure the channel exists. The channel name should not include # symbol", "Block_User": "Block User", "Blockchain": "Blockchain", - "Blockstack": "Blockstack", - "Blockstack_Description": "Give workspace members the ability to sign in without relying on any third parties or remote servers.", - "Blockstack_Auth_Description": "Auth description", - "Blockstack_ButtonLabelText": "Button label text", - "Blockstack_Generate_Username": "Generate username", "Body": "Body", "bold": "bold", "bot_request": "Bot request", diff --git a/apps/meteor/server/importPackages.ts b/apps/meteor/server/importPackages.ts index ab98b032ac0b..1cfec8eabf13 100644 --- a/apps/meteor/server/importPackages.ts +++ b/apps/meteor/server/importPackages.ts @@ -92,7 +92,6 @@ import '../app/webrtc/server'; import '../app/wordpress/server'; import '../app/meteor-accounts-saml/server'; import '../app/e2e/server'; -import '../app/blockstack/server'; import '../app/version-check/server'; import '../app/search/server'; import '../app/chatpal-search/server'; diff --git a/apps/meteor/server/startup/migrations/index.ts b/apps/meteor/server/startup/migrations/index.ts index f293f36a4640..440d32220fdf 100644 --- a/apps/meteor/server/startup/migrations/index.ts +++ b/apps/meteor/server/startup/migrations/index.ts @@ -90,4 +90,5 @@ import './v263'; import './v264'; import './v265'; import './v266'; +import './v267'; import './xrun'; diff --git a/apps/meteor/server/startup/migrations/v267.ts b/apps/meteor/server/startup/migrations/v267.ts new file mode 100644 index 000000000000..65f07177df4b --- /dev/null +++ b/apps/meteor/server/startup/migrations/v267.ts @@ -0,0 +1,27 @@ +import { MongoInternals } from 'meteor/mongo'; +import { ServiceConfiguration } from 'meteor/service-configuration'; + +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 267, + async up() { + ServiceConfiguration.configurations.remove({ + service: 'blockstack', + }); + + const { mongo } = MongoInternals.defaultRemoteCollectionDriver(); + const settings = mongo.db.collection('rocketchat_settings'); + await settings.deleteMany({ + _id: { + $in: [ + 'Blockstack', + 'Blockstack_Enable', + 'Blockstack_Auth_Description', + 'Blockstack_ButtonLabelText', + 'Blockstack_Generate_Username', + ], + }, + }); + }, +}); diff --git a/yarn.lock b/yarn.lock index 32a8008b8c31..1fa0b0a4209e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4883,7 +4883,6 @@ __metadata: babel-plugin-array-includes: ^2.0.3 bad-words: ^3.0.4 bcrypt: ^5.0.1 - blockstack: 19.3.0 body-parser: 1.20.0 bson: ^4.6.3 busboy: ^1.6.0 @@ -6962,24 +6961,6 @@ __metadata: languageName: node linkType: hard -"@types/bn.js@npm:*": - version: 5.1.0 - resolution: "@types/bn.js@npm:5.1.0" - dependencies: - "@types/node": "*" - checksum: 1dc1cbbd7a1e8bf3614752e9602f558762a901031f499f3055828b5e3e2bba16e5b88c27b3c4152ad795248fbe4086c731a5c4b0f29bb243f1875beeeabee59c - languageName: node - linkType: hard - -"@types/bn.js@npm:^4.11.5": - version: 4.11.6 - resolution: "@types/bn.js@npm:4.11.6" - dependencies: - "@types/node": "*" - checksum: 7f66f2c7b7b9303b3205a57184261974b114495736b77853af5b18d857c0b33e82ce7146911e86e87a87837de8acae28986716fd381ac7c301fd6e8d8b6c811f - languageName: node - linkType: hard - "@types/body-parser@npm:*, @types/body-parser@npm:^1.19.0": version: 1.19.2 resolution: "@types/body-parser@npm:1.19.2" @@ -7113,15 +7094,6 @@ __metadata: languageName: node linkType: hard -"@types/elliptic@npm:^6.4.9": - version: 6.4.14 - resolution: "@types/elliptic@npm:6.4.14" - dependencies: - "@types/bn.js": "*" - checksum: d5a64f540e0ed4b74a12dfa5cc88c0aa7b531eab3b7a9fab17948ffbfc6e01814230e63d7417ce1b607dbd8b5d70e1b64f5afac632deabf96e44875aaac0ae1b - languageName: node - linkType: hard - "@types/eslint-visitor-keys@npm:^1.0.0": version: 1.0.0 resolution: "@types/eslint-visitor-keys@npm:1.0.0" @@ -7550,20 +7522,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:10.12.18": - version: 10.12.18 - resolution: "@types/node@npm:10.12.18" - checksum: 333cedae77961347d44329d4042ab0b04569366c4659923bbc3434252d01d63a660375b4e64681336e1caf805d2ab141f08ced39b9bd2d01e30608385f46d8c1 - languageName: node - linkType: hard - -"@types/node@npm:11.11.6": - version: 11.11.6 - resolution: "@types/node@npm:11.11.6" - checksum: 075f1c011cf568e49701419acbcb55c24906b3bb5a34d9412a3b88f228a7a78401a5ad4d3e1cd6855c99aaea5ef96e37fc86ca097e50f06da92cf822befc1fff - languageName: node - linkType: hard - "@types/node@npm:>=12.0.0, @types/node@npm:>=8.9.0": version: 17.0.40 resolution: "@types/node@npm:17.0.40" @@ -8820,16 +8778,6 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^4.11.5": - version: 4.11.8 - resolution: "ajv@npm:4.11.8" - dependencies: - co: ^4.6.0 - json-stable-stringify: ^1.0.1 - checksum: 1a4fb38ebccc2ff3ab507d5507b133705d056f9db28cb00a59f0753a5f11e809d959b732edcd52c02fed628638ffb9486ee6bd13bf027400b5c9acf9c33e25f2 - languageName: node - linkType: hard - "ajv@npm:^6.1.0, ajv@npm:^6.10.0, ajv@npm:^6.10.2, ajv@npm:^6.12.2, ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5": version: 6.12.6 resolution: "ajv@npm:6.12.6" @@ -9431,7 +9379,7 @@ __metadata: languageName: node linkType: hard -"asn1.js@npm:^5.0.1, asn1.js@npm:^5.2.0": +"asn1.js@npm:^5.2.0": version: 5.4.1 resolution: "asn1.js@npm:5.4.1" dependencies: @@ -9599,7 +9547,7 @@ __metadata: languageName: node linkType: hard -"async@npm:^2.6.2, async@npm:^2.6.3, async@npm:~2.6.1, async@npm:~2.6.3": +"async@npm:^2.6.2, async@npm:^2.6.3, async@npm:~2.6.1": version: 2.6.4 resolution: "async@npm:2.6.4" dependencies: @@ -10160,13 +10108,6 @@ __metadata: languageName: node linkType: hard -"base64url@npm:^3.0.1": - version: 3.0.1 - resolution: "base64url@npm:3.0.1" - checksum: a77b2a3a526b3343e25be424de3ae0aa937d78f6af7c813ef9020ef98001c0f4e2323afcd7d8b2d2978996bf8c42445c3e9f60c218c622593e5fdfd54a3d6e18 - languageName: node - linkType: hard - "base@npm:^0.11.1": version: 0.11.2 resolution: "base@npm:0.11.2" @@ -10224,13 +10165,6 @@ __metadata: languageName: node linkType: hard -"bech32@npm:^1.1.2": - version: 1.1.4 - resolution: "bech32@npm:1.1.4" - checksum: 0e98db619191548390d6f09ff68b0253ba7ae6a55db93dfdbb070ba234c1fd3308c0606fbcc95fad50437227b10011e2698b89f0181f6e7f845c499bd14d0f4b - languageName: node - linkType: hard - "better-opn@npm:^2.1.1": version: 2.1.1 resolution: "better-opn@npm:2.1.1" @@ -10349,7 +10283,7 @@ __metadata: languageName: node linkType: hard -"bindings@npm:^1.3.0, bindings@npm:^1.5.0": +"bindings@npm:^1.5.0": version: 1.5.0 resolution: "bindings@npm:1.5.0" dependencies: @@ -10365,79 +10299,6 @@ __metadata: languageName: node linkType: hard -"bip174@npm:^2.0.1": - version: 2.0.1 - resolution: "bip174@npm:2.0.1" - checksum: 1611f74dd9b973449c55412e0a6e6cd24bf55110fde581d508fddc60d0f2d54eb6a8e69ddc6eb60810a0e44c755b3577d563bf3e3a2b6513d3dee0569342e463 - languageName: node - linkType: hard - -"bip32@npm:^2.0.4": - version: 2.0.6 - resolution: "bip32@npm:2.0.6" - dependencies: - "@types/node": 10.12.18 - bs58check: ^2.1.1 - create-hash: ^1.2.0 - create-hmac: ^1.1.7 - tiny-secp256k1: ^1.1.3 - typeforce: ^1.11.5 - wif: ^2.0.6 - checksum: 1c654a93836d8ed0bf5aa18a9b7b8dc3fe65e6a607a736d2acdb7927276c03db4bf8068324b9907e362759f9307d8b2b61c2547c282a2bc5198305f5654ed554 - languageName: node - linkType: hard - -"bip39@npm:^3.0.2": - version: 3.0.4 - resolution: "bip39@npm:3.0.4" - dependencies: - "@types/node": 11.11.6 - create-hash: ^1.1.0 - pbkdf2: ^3.0.9 - randombytes: ^2.0.1 - checksum: 79ce1600a03d1ba5053bdd4e6323f9463ec340764c7e52918b6c6b9dca81221940f2d9a65656447f108f9bc2c8d9ae8df319cca83bbd1dad63f53ef2768d9bae - languageName: node - linkType: hard - -"bip66@npm:^1.1.0": - version: 1.1.5 - resolution: "bip66@npm:1.1.5" - dependencies: - safe-buffer: ^5.0.1 - checksum: 956cff6e51d7206571ef8ce875bc5fa61b5c181589790b9155799b7edcae4b20dbb3eed43b188ff3eec27cdbe98e0b7e0ec9f1cb2e4f5370c119028b248ad859 - languageName: node - linkType: hard - -"bitcoin-ops@npm:^1.3.0, bitcoin-ops@npm:^1.4.0": - version: 1.4.1 - resolution: "bitcoin-ops@npm:1.4.1" - checksum: 3daa3303d6af49c0727041b5d7801a20c5806d00f1cc1afa2d53099974e30a7b1e7e9e578723dd25f5e120903f2725c595c0205d5d99a6578ad65213d74d806d - languageName: node - linkType: hard - -"bitcoinjs-lib@npm:^5.1.2": - version: 5.2.0 - resolution: "bitcoinjs-lib@npm:5.2.0" - dependencies: - bech32: ^1.1.2 - bip174: ^2.0.1 - bip32: ^2.0.4 - bip66: ^1.1.0 - bitcoin-ops: ^1.4.0 - bs58check: ^2.0.0 - create-hash: ^1.1.0 - create-hmac: ^1.1.3 - merkle-lib: ^2.0.10 - pushdata-bitcoin: ^1.0.1 - randombytes: ^2.0.1 - tiny-secp256k1: ^1.1.1 - typeforce: ^1.11.3 - varuint-bitcoin: ^1.0.4 - wif: ^2.0.1 - checksum: 947a9a65694b8d469cb643c0ef2793d807a2b948c6e54c96bbd586b48b2de81afeda40e6ae389e031be03c390804d54ed4805b2d90af713842ebe7f85a08a502 - languageName: node - linkType: hard - "bl@npm:^1.0.0": version: 1.2.3 resolution: "bl@npm:1.2.3" @@ -10487,32 +10348,6 @@ __metadata: languageName: node linkType: hard -"blockstack@npm:19.3.0": - version: 19.3.0 - resolution: "blockstack@npm:19.3.0" - dependencies: - "@types/bn.js": ^4.11.5 - "@types/elliptic": ^6.4.9 - ajv: ^4.11.5 - bip39: ^3.0.2 - bitcoinjs-lib: ^5.1.2 - bn.js: ^4.11.8 - cheerio: ^0.22.0 - cross-fetch: ^2.2.2 - elliptic: ^6.4.1 - form-data: ^2.3.3 - jsontokens: ^2.0.2 - query-string: ^6.3.0 - request: ^2.88.0 - ripemd160: ^2.0.2 - schema-inspector: ^1.6.8 - triplesec: ^3.0.26 - uuid: ^3.3.2 - zone-file: ^1.0.0 - checksum: 0cf8ab7fa8d320319e7fd3b2f8fb2ec40e8822af666916dd049739e38e374f7604a741a3403d0b1a5de703ace449337ba12e601b889c662d3ab0badc3f5c5773 - languageName: node - linkType: hard - "bluebird@npm:^3.1.5, bluebird@npm:^3.3.5, bluebird@npm:^3.5.0, bluebird@npm:^3.5.5": version: 3.7.2 resolution: "bluebird@npm:3.7.2" @@ -10520,7 +10355,7 @@ __metadata: languageName: node linkType: hard -"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.8, bn.js@npm:^4.11.9": +"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.9": version: 4.12.0 resolution: "bn.js@npm:4.12.0" checksum: 39afb4f15f4ea537b55eaf1446c896af28ac948fdcf47171961475724d1bb65118cca49fa6e3d67706e4790955ec0e74de584e45c8f1ef89f46c812bee5b5a12 @@ -10827,7 +10662,7 @@ __metadata: languageName: node linkType: hard -"bs58@npm:^4.0.0, bs58@npm:^4.0.1": +"bs58@npm:^4.0.1": version: 4.0.1 resolution: "bs58@npm:4.0.1" dependencies: @@ -10836,17 +10671,6 @@ __metadata: languageName: node linkType: hard -"bs58check@npm:<3.0.0, bs58check@npm:^2.0.0, bs58check@npm:^2.1.1": - version: 2.1.2 - resolution: "bs58check@npm:2.1.2" - dependencies: - bs58: ^4.0.0 - create-hash: ^1.1.0 - safe-buffer: ^5.1.2 - checksum: 43bdf08a5dd04581b78f040bc4169480e17008da482ffe2a6507327bbc4fc5c28de0501f7faf22901cfe57fbca79cbb202ca529003fedb4cb8dccd265b38e54d - languageName: node - linkType: hard - "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -11618,30 +11442,6 @@ __metadata: languageName: node linkType: hard -"cheerio@npm:^0.22.0": - version: 0.22.0 - resolution: "cheerio@npm:0.22.0" - dependencies: - css-select: ~1.2.0 - dom-serializer: ~0.1.0 - entities: ~1.1.1 - htmlparser2: ^3.9.1 - lodash.assignin: ^4.0.9 - lodash.bind: ^4.1.4 - lodash.defaults: ^4.0.1 - lodash.filter: ^4.4.0 - lodash.flatten: ^4.2.0 - lodash.foreach: ^4.3.0 - lodash.map: ^4.4.0 - lodash.merge: ^4.4.0 - lodash.pick: ^4.2.1 - lodash.reduce: ^4.4.0 - lodash.reject: ^4.4.0 - lodash.some: ^4.4.0 - checksum: b0a6cfa61eb7ae96e4cb8cfeeb14eb45bb790fa40098509268629c4cecca5b99124aabe6daa1154c497ac8def47bc3f9706cef5f0e8a6177a0c137d4bdaaf8b7 - languageName: node - linkType: hard - "cheerio@npm:^1.0.0-rc.3": version: 1.0.0-rc.10 resolution: "cheerio@npm:1.0.0-rc.10" @@ -12736,7 +12536,7 @@ __metadata: languageName: node linkType: hard -"create-hmac@npm:^1.1.0, create-hmac@npm:^1.1.3, create-hmac@npm:^1.1.4, create-hmac@npm:^1.1.7": +"create-hmac@npm:^1.1.0, create-hmac@npm:^1.1.4, create-hmac@npm:^1.1.7": version: 1.1.7 resolution: "create-hmac@npm:1.1.7" dependencies: @@ -12794,16 +12594,6 @@ __metadata: languageName: node linkType: hard -"cross-fetch@npm:^2.2.2": - version: 2.2.5 - resolution: "cross-fetch@npm:2.2.5" - dependencies: - node-fetch: 2.6.1 - whatwg-fetch: 2.0.4 - checksum: dc48ded8c8836db74107aee77c6e8eb751151beb8545ce0e044eb5befc4cb687d996cecb62fc12b19be80924fd72bcc851d90e785cfd3d6056cd45f43507a66d - languageName: node - linkType: hard - "cross-spawn@npm:^4.0.0": version: 4.0.2 resolution: "cross-spawn@npm:4.0.2" @@ -13011,18 +12801,6 @@ __metadata: languageName: node linkType: hard -"css-select@npm:~1.2.0": - version: 1.2.0 - resolution: "css-select@npm:1.2.0" - dependencies: - boolbase: ~1.0.0 - css-what: 2.1 - domutils: 1.5.1 - nth-check: ~1.0.1 - checksum: 607cca60d2f5c56701fe5f800bbe668b114395c503d4e4808edbbbe70b8be3c96a6407428dc0227fcbdf335b20468e6a9e7fd689185edfb57d402e1e4837c9b7 - languageName: node - linkType: hard - "css-tree@npm:1.0.0-alpha.37": version: 1.0.0-alpha.37 resolution: "css-tree@npm:1.0.0-alpha.37" @@ -13060,13 +12838,6 @@ __metadata: languageName: node linkType: hard -"css-what@npm:2.1": - version: 2.1.3 - resolution: "css-what@npm:2.1.3" - checksum: a52d56c591a7e1c37506d0d8c4fdef72537fb8eb4cb68711485997a88d76b5a3342b73a7c79176268f95b428596c447ad7fa3488224a6b8b532e2f1f2ee8545c - languageName: node - linkType: hard - "css-what@npm:^3.2.1": version: 3.4.2 resolution: "css-what@npm:3.4.2" @@ -14281,7 +14052,7 @@ __metadata: languageName: node linkType: hard -"domutils@npm:1.5, domutils@npm:1.5.1": +"domutils@npm:1.5": version: 1.5.1 resolution: "domutils@npm:1.5.1" dependencies: @@ -14511,7 +14282,7 @@ __metadata: languageName: node linkType: hard -"elliptic@npm:^6.4.0, elliptic@npm:^6.4.1, elliptic@npm:^6.5.3, elliptic@npm:^6.5.4": +"elliptic@npm:^6.5.3, elliptic@npm:^6.5.4": version: 6.5.4 resolution: "elliptic@npm:6.5.4" dependencies: @@ -18524,7 +18295,7 @@ __metadata: languageName: node linkType: hard -"htmlparser2@npm:^3.10.0, htmlparser2@npm:^3.9.1": +"htmlparser2@npm:^3.10.0": version: 3.10.1 resolution: "htmlparser2@npm:3.10.1" dependencies: @@ -18821,29 +18592,6 @@ __metadata: languageName: node linkType: hard -"iced-error@npm:>=0.0.9": - version: 0.0.13 - resolution: "iced-error@npm:0.0.13" - checksum: b5829a0c6810dd8963fd5e6d3b5c152affb6c42eb10b4de8e466be757dc1ce8642dbe7a7321b1bb8ce34147b6170b2b1ea426cbd1b3dd9c79ad1bf918ff6b148 - languageName: node - linkType: hard - -"iced-lock@npm:^1.0.1": - version: 1.1.0 - resolution: "iced-lock@npm:1.1.0" - dependencies: - iced-runtime: ^1.0.0 - checksum: f642b53a5a66c6c507220c333cbd68d602679ad0f6a31f28cf2125f383f143585b86fbb69a77066f3b7ea023a23576e0942f4428fec8d80bce048fbf74d6d383 - languageName: node - linkType: hard - -"iced-runtime@npm:>=0.0.1, iced-runtime@npm:^1.0.0, iced-runtime@npm:^1.0.2": - version: 1.0.4 - resolution: "iced-runtime@npm:1.0.4" - checksum: b9497c4c6e23a8e161d26f7fb4bc071d1ddedfcf6edcb55cba5d05bb30375d27054d7ff0bfda53c9e559a15671f4941593e47e36087265c19e483f04cff774cc - languageName: node - linkType: hard - "iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24, iconv-lite@npm:^0.4.4": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" @@ -21238,15 +20986,6 @@ __metadata: languageName: node linkType: hard -"json-stable-stringify@npm:^1.0.1": - version: 1.0.1 - resolution: "json-stable-stringify@npm:1.0.1" - dependencies: - jsonify: ~0.0.0 - checksum: 65d6cbf0fca72a4136999f65f4401cf39a129f7aeff0fdd987ac3d3423a2113659294045fb8377e6e20d865cac32b1b8d70f3d87346c9786adcee60661d96ca5 - languageName: node - linkType: hard - "json-stringify-safe@npm:^5.0.1, json-stringify-safe@npm:~5.0.1": version: 5.0.1 resolution: "json-stringify-safe@npm:5.0.1" @@ -21320,13 +21059,6 @@ __metadata: languageName: node linkType: hard -"jsonify@npm:~0.0.0": - version: 0.0.0 - resolution: "jsonify@npm:0.0.0" - checksum: d8d4ed476c116e6987a460dcb82f22284686caae9f498ac87b0502c1765ac1522f4f450a4cad4cc368d202fd3b27a3860735140a82867fc6d558f5f199c38bce - languageName: node - linkType: hard - "jsonpointer@npm:^5.0.0": version: 5.0.0 resolution: "jsonpointer@npm:5.0.0" @@ -21334,20 +21066,6 @@ __metadata: languageName: node linkType: hard -"jsontokens@npm:^2.0.2": - version: 2.0.2 - resolution: "jsontokens@npm:2.0.2" - dependencies: - "@types/elliptic": ^6.4.9 - asn1.js: ^5.0.1 - base64url: ^3.0.1 - ecdsa-sig-formatter: ^1.0.11 - elliptic: ^6.4.1 - key-encoder: ^2.0.2 - checksum: 7339c58a5fb50467e90d6a8759128ec41ebe922e7d900d65cc5ae8741ff7940dfe431f5afc8ebe2175846b4563083319c2144d4fd5d88fac30c2670fb32c9ac3 - languageName: node - linkType: hard - "jsonwebtoken@npm:^8.1.0, jsonwebtoken@npm:^8.5.1": version: 8.5.1 resolution: "jsonwebtoken@npm:8.5.1" @@ -21477,18 +21195,6 @@ __metadata: languageName: node linkType: hard -"key-encoder@npm:^2.0.2": - version: 2.0.3 - resolution: "key-encoder@npm:2.0.3" - dependencies: - "@types/elliptic": ^6.4.9 - asn1.js: ^5.0.1 - bn.js: ^4.11.8 - elliptic: ^6.4.1 - checksum: da0b297965224c814a72d6a0850c9d05a43f85515a6a9047b24379c91eb1a510ee6d373c76b985f99ac189f0ea7ac17d223df00718d7201220c83347b7e58f58 - languageName: node - linkType: hard - "keyv@npm:3.0.0": version: 3.0.0 resolution: "keyv@npm:3.0.0" @@ -21991,20 +21697,6 @@ __metadata: languageName: node linkType: hard -"lodash.assignin@npm:^4.0.9": - version: 4.2.0 - resolution: "lodash.assignin@npm:4.2.0" - checksum: 4b55bc1d65ccd7648fdba8a4316d10546929bf0beb5950830d86c559948cf170f0e65b77c95e66b45b511b85a31161714de8b2008d2537627ef3c7759afe36a6 - languageName: node - linkType: hard - -"lodash.bind@npm:^4.1.4": - version: 4.2.1 - resolution: "lodash.bind@npm:4.2.1" - checksum: cf0e41de2fca7704fc0adadc00f7fc871f8cf428990972f072136e4cd153c4d42d88c1418218121380914021c5547be05e4252e61f6280c736a2195cc8b6f4e5 - languageName: node - linkType: hard - "lodash.clonedeep@npm:^4.5.0": version: 4.5.0 resolution: "lodash.clonedeep@npm:4.5.0" @@ -22019,7 +21711,7 @@ __metadata: languageName: node linkType: hard -"lodash.defaults@npm:^4.0.1, lodash.defaults@npm:^4.2.0": +"lodash.defaults@npm:^4.2.0": version: 4.2.0 resolution: "lodash.defaults@npm:4.2.0" checksum: 84923258235592c8886e29de5491946ff8c2ae5c82a7ac5cddd2e3cb697e6fbdfbbb6efcca015795c86eec2bb953a5a2ee4016e3735a3f02720428a40efbb8f1 @@ -22033,27 +21725,13 @@ __metadata: languageName: node linkType: hard -"lodash.filter@npm:^4.4.0": - version: 4.6.0 - resolution: "lodash.filter@npm:4.6.0" - checksum: f21d245d24818e15b560cb6cadc8404a1bf98bd87d037e5e51858aad57ca2b9db64d87e450a23c8f72dd2c66968efd09b034055ce86d93eef4a4eb6f1bbaf100 - languageName: node - linkType: hard - -"lodash.flatten@npm:^4.2.0, lodash.flatten@npm:^4.4.0": +"lodash.flatten@npm:^4.4.0": version: 4.4.0 resolution: "lodash.flatten@npm:4.4.0" checksum: 0ac34a393d4b795d4b7421153d27c13ae67e08786c9cbb60ff5b732210d46f833598eee3fb3844bb10070e8488efe390ea53bb567377e0cb47e9e630bf0811cb languageName: node linkType: hard -"lodash.foreach@npm:^4.3.0": - version: 4.5.0 - resolution: "lodash.foreach@npm:4.5.0" - checksum: a940386b158ca0d62994db41fc16529eb8ae67138f29ced38e91f912cb5435d1b0ed34b18e6f7b9ddfc32ab676afc6dfec60d1e22633d8e3e4b33413402ab4ad - languageName: node - linkType: hard - "lodash.get@npm:^4.4.2": version: 4.4.2 resolution: "lodash.get@npm:4.4.2" @@ -22110,13 +21788,6 @@ __metadata: languageName: node linkType: hard -"lodash.map@npm:^4.4.0": - version: 4.6.0 - resolution: "lodash.map@npm:4.6.0" - checksum: 7369a41d7d24d15ce3bbd02a7faa3a90f6266c38184e64932571b9b21b758bd10c04ffd117d1859be1a44156f29b94df5045eff172bf8a97fddf68bf1002d12f - languageName: node - linkType: hard - "lodash.memoize@npm:4.x, lodash.memoize@npm:^4.1.2": version: 4.1.2 resolution: "lodash.memoize@npm:4.1.2" @@ -22124,7 +21795,7 @@ __metadata: languageName: node linkType: hard -"lodash.merge@npm:^4.4.0, lodash.merge@npm:^4.6.2": +"lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" checksum: ad580b4bdbb7ca1f7abf7e1bce63a9a0b98e370cf40194b03380a46b4ed799c9573029599caebc1b14e3f24b111aef72b96674a56cfa105e0f5ac70546cdc005 @@ -22138,34 +21809,6 @@ __metadata: languageName: node linkType: hard -"lodash.pick@npm:^4.2.1": - version: 4.4.0 - resolution: "lodash.pick@npm:4.4.0" - checksum: 2c36cab7da6b999a20bd3373b40e31a3ef81fa264f34a6979c852c5bc8ac039379686b27380f0cb8e3781610844fafec6949c6fbbebc059c98f8fa8570e3675f - languageName: node - linkType: hard - -"lodash.reduce@npm:^4.4.0": - version: 4.6.0 - resolution: "lodash.reduce@npm:4.6.0" - checksum: 81f2a1045440554f8427f895ef479f1de5c141edd7852dde85a894879312801efae0295116e5cf830c531c1a51cdab8f3628c3ad39fa21a9874bb9158d9ea075 - languageName: node - linkType: hard - -"lodash.reject@npm:^4.4.0": - version: 4.6.0 - resolution: "lodash.reject@npm:4.6.0" - checksum: 730acc78d29ab0a60e0f3cd87bbfe9071625a835791ef66daac7a405c43ec21209fd795fdf9b7485aecead4869f645801bd65c27b9acadce80dee26393793111 - languageName: node - linkType: hard - -"lodash.some@npm:^4.4.0": - version: 4.6.0 - resolution: "lodash.some@npm:4.6.0" - checksum: 4469e76a389446d1166a29f844fb21398c36060d00258ce799710e046c55ed3c1af150c31b4856504e252bc813ba3fdcb6f255c490d9846738dd363a44665322 - languageName: node - linkType: hard - "lodash.throttle@npm:^4.1.1": version: 4.1.1 resolution: "lodash.throttle@npm:4.1.1" @@ -23038,13 +22681,6 @@ __metadata: languageName: node linkType: hard -"merkle-lib@npm:^2.0.10": - version: 2.0.10 - resolution: "merkle-lib@npm:2.0.10" - checksum: 057331fe5e15a12a35c94c40bcc956c6b2d38cf6a3c69858b05a55434a9ac7be552595be5955491d3c380c7a91c522e429c7d13db7b676463b729de05c07b9e1 - languageName: node - linkType: hard - "meteor-blaze-tools@npm:^1.2.0, meteor-blaze-tools@npm:^1.2.4": version: 1.5.0 resolution: "meteor-blaze-tools@npm:1.5.0" @@ -23848,15 +23484,6 @@ __metadata: languageName: node linkType: hard -"more-entropy@npm:>=0.0.7": - version: 0.0.7 - resolution: "more-entropy@npm:0.0.7" - dependencies: - iced-runtime: ">=0.0.1" - checksum: c3fc40748199bf95885878cbf49be5d331047d51ecb65f7e636a5c586d519bec004133c9b3c9851ae3da70d59efc2c541970fa6b6d3c3dae31199b4e8193ec17 - languageName: node - linkType: hard - "morgan@npm:^1.10.0": version: 1.10.0 resolution: "morgan@npm:1.10.0" @@ -24250,13 +23877,6 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:2.6.1": - version: 2.6.1 - resolution: "node-fetch@npm:2.6.1" - checksum: 91075bedd57879117e310fbcc36983ad5d699e522edb1ebcdc4ee5294c982843982652925c3532729fdc86b2d64a8a827797a745f332040d91823c8752ee4d7c - languageName: node - linkType: hard - "node-fetch@npm:2.6.7, node-fetch@npm:^2.3.0, node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7": version: 2.6.7 resolution: "node-fetch@npm:2.6.7" @@ -24683,7 +24303,7 @@ __metadata: languageName: node linkType: hard -"nth-check@npm:^1.0.2, nth-check@npm:~1.0.0, nth-check@npm:~1.0.1": +"nth-check@npm:^1.0.2, nth-check@npm:~1.0.0": version: 1.0.2 resolution: "nth-check@npm:1.0.2" dependencies: @@ -25936,7 +25556,7 @@ __metadata: languageName: node linkType: hard -"pbkdf2@npm:^3.0.3, pbkdf2@npm:^3.0.9": +"pbkdf2@npm:^3.0.3": version: 3.1.2 resolution: "pbkdf2@npm:3.1.2" dependencies: @@ -27281,13 +26901,6 @@ __metadata: languageName: node linkType: hard -"progress@npm:~1.1.2": - version: 1.1.8 - resolution: "progress@npm:1.1.8" - checksum: 789c824156e03a7353b190fc63da46bc42b210250cebaa2dca447a6a740f5469f34ed30f768cdef088ca720a8c3c42dd743958e8bc6a35b2d2a1a83171ad2c56 - languageName: node - linkType: hard - "prom-client@npm:^14.0.0, prom-client@npm:^14.0.1": version: 14.0.1 resolution: "prom-client@npm:14.0.1" @@ -27567,15 +27180,6 @@ __metadata: languageName: node linkType: hard -"pushdata-bitcoin@npm:^1.0.1": - version: 1.0.1 - resolution: "pushdata-bitcoin@npm:1.0.1" - dependencies: - bitcoin-ops: ^1.3.0 - checksum: 8452106d4b39ea1b335cd8f319c5e704b19aca72afb8bda4eee3df4602e3ad14cb9746d228712cdf0dcfec68e055dad4fc51cddf14cf072caf698c6ba4b34be2 - languageName: node - linkType: hard - "q@npm:2.0.x": version: 2.0.3 resolution: "q@npm:2.0.3" @@ -27654,7 +27258,7 @@ __metadata: languageName: node linkType: hard -"query-string@npm:^6.13.1, query-string@npm:^6.13.8, query-string@npm:^6.3.0": +"query-string@npm:^6.13.1, query-string@npm:^6.13.8": version: 6.14.1 resolution: "query-string@npm:6.14.1" dependencies: @@ -29378,7 +28982,7 @@ __metadata: languageName: node linkType: hard -"ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1, ripemd160@npm:^2.0.2": +"ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1": version: 2.0.2 resolution: "ripemd160@npm:2.0.2" dependencies: @@ -29685,15 +29289,6 @@ __metadata: languageName: node linkType: hard -"schema-inspector@npm:^1.6.8": - version: 1.7.0 - resolution: "schema-inspector@npm:1.7.0" - dependencies: - async: ~2.6.3 - checksum: e21eef460ad1666f8de44d843ce122114d09e5a5bdf930b3cf2af970ccc1b1933b79c2af9c1c508a3c5399d1660196ba61d5201493f9c4b559af78905ab78087 - languageName: node - linkType: hard - "schema-utils@npm:2.7.0": version: 2.7.0 resolution: "schema-utils@npm:2.7.0" @@ -32097,20 +31692,6 @@ __metadata: languageName: node linkType: hard -"tiny-secp256k1@npm:^1.1.1, tiny-secp256k1@npm:^1.1.3": - version: 1.1.6 - resolution: "tiny-secp256k1@npm:1.1.6" - dependencies: - bindings: ^1.3.0 - bn.js: ^4.11.8 - create-hmac: ^1.1.7 - elliptic: ^6.4.0 - nan: ^2.13.2 - node-gyp: latest - checksum: f8f705f8a76dc9ccc9aa46f7bc353c00be63940c0a1198175fd77c9b85bdf24eb6db3d72c4756d24af320900290313c580c07695cda645d98410822f94ee01f5 - languageName: node - linkType: hard - "tinykeys@npm:^1.4.0": version: 1.4.0 resolution: "tinykeys@npm:1.4.0" @@ -32380,20 +31961,6 @@ __metadata: languageName: node linkType: hard -"triplesec@npm:^3.0.26": - version: 3.0.27 - resolution: "triplesec@npm:3.0.27" - dependencies: - iced-error: ">=0.0.9" - iced-lock: ^1.0.1 - iced-runtime: ^1.0.2 - more-entropy: ">=0.0.7" - progress: ~1.1.2 - uglify-js: ^3.1.9 - checksum: 5995e1f5fdbefa8744c1760a0c93557589a0028744d167b914e5b641456f77f3192ab5961e23887644c4a9c334ce008403fe147d9c844071c0091dcdd92d5908 - languageName: node - linkType: hard - "trough@npm:^1.0.0": version: 1.0.5 resolution: "trough@npm:1.0.5" @@ -32886,13 +32453,6 @@ __metadata: languageName: node linkType: hard -"typeforce@npm:^1.11.3, typeforce@npm:^1.11.5": - version: 1.18.0 - resolution: "typeforce@npm:1.18.0" - checksum: e3b21e27e76cb05f32285bef7c30a29760e79c622cfe4aa3c179ce49d1c7895b7154c8deedb9fe4599b1fd0428d35860d43e0776da1c04861168f3ad7ed99c70 - languageName: node - linkType: hard - "typescript@npm:^4.6.3": version: 4.6.3 resolution: "typescript@npm:4.6.3" @@ -32954,7 +32514,7 @@ __metadata: languageName: node linkType: hard -"uglify-js@npm:^3.1.4, uglify-js@npm:^3.1.9": +"uglify-js@npm:^3.1.4": version: 3.15.3 resolution: "uglify-js@npm:3.15.3" bin: @@ -33748,15 +33308,6 @@ __metadata: languageName: node linkType: hard -"varuint-bitcoin@npm:^1.0.4": - version: 1.1.2 - resolution: "varuint-bitcoin@npm:1.1.2" - dependencies: - safe-buffer: ^5.1.1 - checksum: 1c900bf08f2408ae33a6094dc5d809bdb6673eaf6039062d88c230155873e51e29c760053611f93ccd024854d04ebd92ed95c744720e94a79ca4e1150fcce071 - languageName: node - linkType: hard - "vary@npm:^1, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -34250,13 +33801,6 @@ __metadata: languageName: node linkType: hard -"whatwg-fetch@npm:2.0.4": - version: 2.0.4 - resolution: "whatwg-fetch@npm:2.0.4" - checksum: de7c65a68d7d62e2f144a6b30293370b3ad82b65ebcd68f2ac8e8bbe7ede90febd98ba9486b78c1cbc950e0e8838fa5c2727f939899ab3fc7b71a04be52d33a5 - languageName: node - linkType: hard - "whatwg-fetch@npm:>=0.10.0, whatwg-fetch@npm:^3.4.0": version: 3.6.2 resolution: "whatwg-fetch@npm:3.6.2" @@ -34392,15 +33936,6 @@ __metadata: languageName: node linkType: hard -"wif@npm:^2.0.1, wif@npm:^2.0.6": - version: 2.0.6 - resolution: "wif@npm:2.0.6" - dependencies: - bs58check: <3.0.0 - checksum: 8c3147ef98d56f394d66f0477f699fba7fc18dd0d1c2c5d0f8408be41acffed589fa82447d80eae5afc9a3cbd943bc3eebb337b9f114955adeaad02a244f4f9a - languageName: node - linkType: hard - "winston-daily-rotate-file@npm:^4.5.1": version: 4.6.1 resolution: "winston-daily-rotate-file@npm:4.6.1" @@ -35051,13 +34586,6 @@ __metadata: languageName: node linkType: hard -"zone-file@npm:^1.0.0": - version: 1.0.0 - resolution: "zone-file@npm:1.0.0" - checksum: f7b56525f3e9a01bc896e9c39bbda089d2ba4b89ce6ea5f26548e75e2c53f1dc2ff1fc28bde9ce00dc0284411eb5097f60e4dc795047952687a1f66ec17bb6d5 - languageName: node - linkType: hard - "zwitch@npm:^1.0.0": version: 1.0.5 resolution: "zwitch@npm:1.0.5"