Skip to content

Commit

Permalink
Merge branch 'develop' into add-setting-translation
Browse files Browse the repository at this point in the history
  • Loading branch information
dougfabris committed Sep 26, 2022
2 parents 26b0e44 + ca43434 commit 0dd4dbc
Show file tree
Hide file tree
Showing 312 changed files with 6,702 additions and 3,657 deletions.
222 changes: 126 additions & 96 deletions .github/workflows/build_and_test.yml

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion apps/meteor/app/discussion/client/discussionFromMessageBox.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
import { isRoomFederated } from '@rocket.chat/core-typings';

import { messageBox } from '../../ui-utils/client';
import { settings } from '../../settings/client';
import { hasPermission } from '../../authorization/client';
import { imperativeModal } from '../../../client/lib/imperativeModal';
import CreateDiscussion from '../../../client/components/CreateDiscussion/CreateDiscussion';
import { Rooms } from '../../models/client';

Meteor.startup(function () {
Tracker.autorun(() => {
Expand All @@ -15,7 +18,13 @@ Meteor.startup(function () {
messageBox.actions.add('Create_new', 'Discussion', {
id: 'start-discussion',
icon: 'discussion',
condition: () => hasPermission('start-discussion') || hasPermission('start-discussion-other-user'),
condition: () => {
const room = Rooms.findOne(Session.get('openedRoom'));
if (!room) {
return false;
}
return (hasPermission('start-discussion') || hasPermission('start-discussion-other-user')) && !isRoomFederated(room);
},
action(data) {
imperativeModal.open({
component: CreateDiscussion,
Expand Down
9 changes: 9 additions & 0 deletions apps/meteor/app/federation-v2/server/Federation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { IRoom, ValueOf } from '@rocket.chat/core-typings';
import { isDirectMessageRoom } from '@rocket.chat/core-typings';

import { RoomMemberActions } from '../../../definition/IRoomTypeConfig';
import { escapeExternalFederationEventId, unescapeExternalFederationEventId } from './infrastructure/rocket-chat/adapters/MessageConverter';

const allowedActionsInFederatedRooms: ValueOf<typeof RoomMemberActions>[] = [
RoomMemberActions.REMOVE_USER,
Expand All @@ -18,4 +19,12 @@ export class Federation {
public static isAFederatedUsername(username: string): boolean {
return username.includes('@') && username.includes(':');
}

public static escapeExternalFederationEventId(externalEventId: string): string {
return escapeExternalFederationEventId(externalEventId);
}

public static unescapeExternalFederationEventId(externalEventId: string): string {
return unescapeExternalFederationEventId(externalEventId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,19 @@ export abstract class FederationService {
existsOnlyOnProxyServer = false,
providedName?: string,
): Promise<void> {
const internalUser = await this.internalUserAdapter.getInternalUserByUsername(username);
const externalUserProfileInformation = await this.bridge.getUserProfileInformation(externalUserId);
const name = externalUserProfileInformation?.displayName || providedName || username;
const federatedUser = FederatedUser.createInstance(externalUserId, {
name,
username,
existsOnlyOnProxyServer,
});

let federatedUser;
if (internalUser) {
federatedUser = FederatedUser.createWithInternalReference(externalUserId, existsOnlyOnProxyServer, internalUser);
} else {
const name = externalUserProfileInformation?.displayName || providedName || username;
federatedUser = FederatedUser.createInstance(externalUserId, {
name,
username,
existsOnlyOnProxyServer,
});
}
await this.internalUserAdapter.createFederatedUser(federatedUser);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { isMessageFromMatrixFederation } from '@rocket.chat/core-typings';

import type { IFederationBridge } from '../domain/IFederationBridge';
import type { RocketChatMessageAdapter } from '../infrastructure/rocket-chat/adapters/Message';
import type { RocketChatRoomAdapter } from '../infrastructure/rocket-chat/adapters/Room';
import type { RocketChatSettingsAdapter } from '../infrastructure/rocket-chat/adapters/Settings';
import type { RocketChatUserAdapter } from '../infrastructure/rocket-chat/adapters/User';
import { FederationService } from './AbstractFederationService';
import type { FederationMessageReactionEventDto } from './input/MessageReceiverDto';

export class FederationMessageServiceListener extends FederationService {
constructor(
protected internalRoomAdapter: RocketChatRoomAdapter,
protected internalUserAdapter: RocketChatUserAdapter,
protected internalMessageAdapter: RocketChatMessageAdapter,
protected internalSettingsAdapter: RocketChatSettingsAdapter,
protected bridge: IFederationBridge,
) {
super(bridge, internalUserAdapter, internalSettingsAdapter);
}

public async onMessageReaction(messageReactionEventInput: FederationMessageReactionEventDto): Promise<void> {
const {
externalRoomId,
emoji,
externalSenderId,
externalEventId: externalReactionEventId,
externalReactedEventId: externalMessageId,
} = messageReactionEventInput;

const federatedRoom = await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId);
if (!federatedRoom) {
return;
}

const federatedUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalSenderId);
if (!federatedUser) {
return;
}
const message = await this.internalMessageAdapter.getMessageByFederationId(externalMessageId);
if (!message) {
return;
}
if (!isMessageFromMatrixFederation(message)) {
return;
}
// TODO: move this to a Message entity in the domain layer
const userAlreadyReacted = Boolean(
federatedUser.getUsername() && message.reactions?.[emoji]?.usernames?.includes(federatedUser.getUsername() as string),
);
if (userAlreadyReacted) {
return;
}

await this.internalMessageAdapter.reactToMessage(federatedUser, message, emoji, externalReactionEventId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { IMessage } from '@rocket.chat/core-typings';

import type { FederatedUser } from '../domain/FederatedUser';
import { Federation } from '../Federation';
import type { RocketChatMessageAdapter } from '../infrastructure/rocket-chat/adapters/Message';

export interface IRoomRedactionHandlers {
handle(): Promise<void>;
}

class DeleteMessageHandler implements IRoomRedactionHandlers {
constructor(
private readonly internalMessageAdapter: RocketChatMessageAdapter,
private readonly message: IMessage,
private readonly federatedUser: FederatedUser,
) {}

public async handle(): Promise<void> {
await this.internalMessageAdapter.deleteMessage(this.message, this.federatedUser);
}
}

class UnreactToMessageHandler implements IRoomRedactionHandlers {
constructor(
private readonly internalMessageAdapter: RocketChatMessageAdapter,
private readonly message: IMessage,
private readonly federatedUser: FederatedUser,
private readonly redactsEvents: string,
) {}

public async handle(): Promise<void> {
const normalizedEventId = Federation.escapeExternalFederationEventId(this.redactsEvents);
const reaction = Object.keys(this.message.reactions || {}).find(
(key) =>
this.message.reactions?.[key]?.federationReactionEventIds?.[normalizedEventId] === this.federatedUser.getUsername() &&
this.message.reactions?.[key]?.usernames?.includes(this.federatedUser.getUsername() || ''),
);
if (!reaction) {
return;
}
await this.internalMessageAdapter.unreactToMessage(this.federatedUser, this.message, reaction, this.redactsEvents);
}
}

export const getRedactMessageHandler = async (
internalMessageAdapter: RocketChatMessageAdapter,
redactsEvent: string,
federatedUser: FederatedUser,
): Promise<IRoomRedactionHandlers | undefined> => {
const message = await internalMessageAdapter.getMessageByFederationId(redactsEvent);
const messageWithReaction = await internalMessageAdapter.findOneByFederationIdOnReactions(redactsEvent, federatedUser);
if (!message && !messageWithReaction) {
return;
}
if (messageWithReaction) {
return new UnreactToMessageHandler(internalMessageAdapter, messageWithReaction, federatedUser, redactsEvent);
}
if (message) {
return new DeleteMessageHandler(internalMessageAdapter, message, federatedUser);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,21 @@ import type {
FederationRoomChangeJoinRulesDto,
FederationRoomChangeNameDto,
FederationRoomChangeTopicDto,
FederationRoomReceiveExternalFileMessageDto,
FederationRoomRedactEventDto,
FederationRoomEditExternalMessageDto,
} from './input/RoomReceiverDto';
import { FederationService } from './AbstractFederationService';
import type { RocketChatFileAdapter } from '../infrastructure/rocket-chat/adapters/File';
import { getRedactMessageHandler } from './RoomRedactionHandlers';

export class FederationRoomServiceListener extends FederationService {
constructor(
protected internalRoomAdapter: RocketChatRoomAdapter,
protected internalUserAdapter: RocketChatUserAdapter,
protected internalMessageAdapter: RocketChatMessageAdapter,
protected internalSettingsAdapter: RocketChatSettingsAdapter,
protected internalFileAdapter: RocketChatFileAdapter,
protected bridge: IFederationBridge,
) {
super(bridge, internalUserAdapter, internalSettingsAdapter);
Expand Down Expand Up @@ -180,7 +186,7 @@ export class FederationRoomServiceListener extends FederationService {
}

public async onExternalMessageReceived(roomReceiveExternalMessageInput: FederationRoomReceiveExternalMessageDto): Promise<void> {
const { externalRoomId, externalSenderId, messageText } = roomReceiveExternalMessageInput;
const { externalRoomId, externalSenderId, messageText, externalEventId } = roomReceiveExternalMessageInput;

const federatedRoom = await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId);
if (!federatedRoom) {
Expand All @@ -192,7 +198,62 @@ export class FederationRoomServiceListener extends FederationService {
return;
}

await this.internalMessageAdapter.sendMessage(senderUser, federatedRoom, messageText);
await this.internalMessageAdapter.sendMessage(senderUser, federatedRoom, messageText, externalEventId);
}

public async onExternalMessageEditedReceived(roomEditExternalMessageInput: FederationRoomEditExternalMessageDto): Promise<void> {
const { externalRoomId, externalSenderId, editsEvent, newMessageText } = roomEditExternalMessageInput;

const federatedRoom = await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId);
if (!federatedRoom) {
return;
}

const senderUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalSenderId);
if (!senderUser) {
return;
}

const message = await this.internalMessageAdapter.getMessageByFederationId(editsEvent);
if (!message) {
return;
}
// TODO: create an entity to abstract all the message logic
if (!FederatedRoom.shouldUpdateMessage(newMessageText, message)) {
return;
}

await this.internalMessageAdapter.editMessage(senderUser, newMessageText, message);
}

public async onExternalFileMessageReceived(roomReceiveExternalMessageInput: FederationRoomReceiveExternalFileMessageDto): Promise<void> {
const { externalRoomId, externalSenderId, messageBody, externalEventId } = roomReceiveExternalMessageInput;

const federatedRoom = await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId);
if (!federatedRoom) {
return;
}

const senderUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalSenderId);
if (!senderUser) {
return;
}
const fileDetails = {
name: messageBody.filename,
size: messageBody.size,
type: messageBody.mimetype,
rid: federatedRoom.getInternalId(),
userId: senderUser.getInternalId(),
};
const readableStream = await this.bridge.getReadStreamForFileFromUrl(senderUser.getExternalId(), messageBody.url);
const { files = [], attachments } = await this.internalFileAdapter.uploadFile(
readableStream,
federatedRoom.getInternalId(),
senderUser.getInternalReference(),
fileDetails,
);

await this.internalMessageAdapter.sendFileMessage(senderUser, federatedRoom, files, attachments, externalEventId);
}

public async onChangeJoinRules(roomJoinRulesChangeInput: FederationRoomChangeJoinRulesDto): Promise<void> {
Expand Down Expand Up @@ -255,4 +316,23 @@ export class FederationRoomServiceListener extends FederationService {

await this.internalRoomAdapter.updateRoomTopic(federatedRoom, federatedUser);
}

public async onRedactEvent(roomRedactEventInput: FederationRoomRedactEventDto): Promise<void> {
const { externalRoomId, redactsEvent, externalSenderId } = roomRedactEventInput;

const federatedRoom = await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId);
if (!federatedRoom) {
return;
}

const federatedUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalSenderId);
if (!federatedUser) {
return;
}
const handler = await getRedactMessageHandler(this.internalMessageAdapter, redactsEvent, federatedUser);
if (!handler) {
return;
}
await handler.handle();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { IFederationReceiverBaseRoomInputDto } from './RoomReceiverDto';
import { FederationBaseRoomInputDto } from './RoomReceiverDto';

interface IFederationRoomMessageReactionInputDto extends IFederationReceiverBaseRoomInputDto {
externalSenderId: string;
externalEventId: string;
externalReactedEventId: string;
emoji: string;
}

export class FederationMessageReactionEventDto extends FederationBaseRoomInputDto {
constructor({
externalRoomId,
normalizedRoomId,
externalEventId,
externalReactedEventId,
emoji,
externalSenderId,
}: IFederationRoomMessageReactionInputDto) {
super({ externalRoomId, normalizedRoomId, externalEventId });
this.emoji = emoji;
this.externalSenderId = externalSenderId;
this.externalReactedEventId = externalReactedEventId;
}

emoji: string;

externalSenderId: string;

externalReactedEventId: string;
}
Loading

0 comments on commit 0dd4dbc

Please sign in to comment.