Skip to content

Commit

Permalink
[NEW] Federation events coverage expansion (#27119)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcosSpessatto authored Nov 18, 2022
1 parent 2c829b1 commit fb32bdf
Show file tree
Hide file tree
Showing 47 changed files with 1,401 additions and 218 deletions.
3 changes: 2 additions & 1 deletion apps/meteor/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,5 @@ coverage
tests/e2e/test-failures/
out.txt
dist
*-session.json
*-session.json
matrix-federation-config/*
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export abstract class FederationService {
this.internalHomeServerDomain = this.internalSettingsAdapter.getHomeServerDomain();
}

protected async createFederatedUser(
protected async createFederatedUserInternallyOnly(
externalUserId: string,
username: string,
existsOnlyOnProxyServer = false,
Expand All @@ -42,33 +42,44 @@ export abstract class FederationService {
if (!insertedUser) {
return;
}
await this.updateUserAvatarInternally(insertedUser);
await this.updateUserAvatarInternally(insertedUser, externalUserProfileInformation?.avatarUrl);
await this.updateUserDisplayNameInternally(insertedUser, externalUserProfileInformation?.displayName);
}

protected async updateUserAvatarInternally(federatedUser: FederatedUser): Promise<void> {
const externalUserProfileInformation = await this.bridge.getUserProfileInformation(federatedUser.getExternalId());
if (!externalUserProfileInformation?.avatarUrl) {
protected async updateUserAvatarInternally(federatedUser: FederatedUser, avatarUrl?: string): Promise<void> {
if (!avatarUrl) {
return;
}
if (!federatedUser.isRemote() || !federatedUser.shouldUpdateFederationAvatar(externalUserProfileInformation.avatarUrl)) {
if (!federatedUser.isRemote()) {
return;
}
await this.internalUserAdapter.setAvatar(
federatedUser,
this.bridge.convertMatrixUrlToHttp(federatedUser.getExternalId(), externalUserProfileInformation.avatarUrl),
);
await this.internalUserAdapter.updateFederationAvatar(federatedUser.getInternalId(), externalUserProfileInformation.avatarUrl);
if (federatedUser.shouldUpdateFederationAvatar(avatarUrl)) {
await this.internalUserAdapter.setAvatar(federatedUser, this.bridge.convertMatrixUrlToHttp(federatedUser.getExternalId(), avatarUrl));
await this.internalUserAdapter.updateFederationAvatar(federatedUser.getInternalId(), avatarUrl);
}
}

protected async updateUserDisplayNameInternally(federatedUser: FederatedUser, displayName?: string): Promise<void> {
if (!displayName) {
return;
}
if (!federatedUser.isRemote()) {
return;
}
if (federatedUser.shouldUpdateDisplayName(displayName)) {
await this.internalUserAdapter.updateRealName(federatedUser.getInternalReference(), displayName);
}
}

protected async createFederatedUserForInviterUsingLocalInformation(internalInviterId: string): Promise<string> {
protected async createFederatedUserIncludingHomeserverUsingLocalInformation(internalInviterId: string): Promise<string> {
const internalUser = await this.internalUserAdapter.getInternalUserById(internalInviterId);
if (!internalUser || !internalUser?.username) {
throw new Error(`Could not find user id for ${internalInviterId}`);
}
const name = internalUser.name || internalUser.username;
const externalInviterId = await this.bridge.createUser(internalUser.username, name, this.internalHomeServerDomain);
const existsOnlyOnProxyServer = true;
await this.createFederatedUser(externalInviterId, internalUser.username, existsOnlyOnProxyServer, name);
await this.createFederatedUserInternallyOnly(externalInviterId, internalUser.username, existsOnlyOnProxyServer, name);
await this.updateUserAvatarExternally(
internalUser,
(await this.internalUserAdapter.getFederatedUserByExternalId(externalInviterId)) as FederatedUser,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type {
import { FederationService } from './AbstractFederationService';
import type { RocketChatFileAdapter } from '../infrastructure/rocket-chat/adapters/File';
import { getRedactMessageHandler } from './RoomRedactionHandlers';
import type { RocketChatNotificationAdapter } from '../infrastructure/rocket-chat/adapters/Notification';

export class FederationRoomServiceListener extends FederationService {
constructor(
Expand All @@ -31,6 +32,7 @@ export class FederationRoomServiceListener extends FederationService {
protected internalMessageAdapter: RocketChatMessageAdapter,
protected internalFileAdapter: RocketChatFileAdapter,
protected internalSettingsAdapter: RocketChatSettingsAdapter,
protected internalNotificationAdapter: RocketChatNotificationAdapter,
protected bridge: IFederationBridge,
) {
super(bridge, internalUserAdapter, internalFileAdapter, internalSettingsAdapter);
Expand Down Expand Up @@ -62,7 +64,7 @@ export class FederationRoomServiceListener extends FederationService {

const creatorUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalInviterId);
if (!creatorUser) {
await this.createFederatedUser(externalInviterId, normalizedInviterId);
await this.createFederatedUserInternallyOnly(externalInviterId, normalizedInviterId);
}
const creator = creatorUser || (await this.internalUserAdapter.getFederatedUserByExternalId(externalInviterId));
if (!creator) {
Expand All @@ -75,7 +77,11 @@ export class FederationRoomServiceListener extends FederationService {
roomType || RoomType.CHANNEL,
externalRoomName,
);
await this.internalRoomAdapter.createFederatedRoom(newFederatedRoom);
const createdInternalRoomId = await this.internalRoomAdapter.createFederatedRoom(newFederatedRoom);
await this.internalNotificationAdapter.subscribeToUserTypingEventsOnFederatedRoomId(
createdInternalRoomId,
this.internalNotificationAdapter.broadcastUserTypingOnRoom.bind(this.internalNotificationAdapter),
);
}

public async onChangeRoomMembership(roomChangeMembershipInput: FederationRoomChangeMembershipDto): Promise<void> {
Expand All @@ -92,15 +98,18 @@ export class FederationRoomServiceListener extends FederationService {
eventOrigin,
roomType,
leave,
userAvatarUrl,
userProfile,
} = roomChangeMembershipInput;
const wasGeneratedOnTheProxyServer = eventOrigin === EVENT_ORIGIN.LOCAL;
const affectedFederatedRoom = await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId);

if (userAvatarUrl) {
if (userProfile?.avatarUrl) {
const federatedUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalInviteeId);
federatedUser && (await this.updateUserAvatarInternally(federatedUser));
return;
federatedUser && (await this.updateUserAvatarInternally(federatedUser, userProfile?.avatarUrl));
}
if (userProfile?.displayName) {
const federatedUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalInviteeId);
federatedUser && (await this.updateUserDisplayNameInternally(federatedUser, userProfile?.displayName));
}

if (wasGeneratedOnTheProxyServer && !affectedFederatedRoom) {
Expand All @@ -120,12 +129,12 @@ export class FederationRoomServiceListener extends FederationService {

const inviterUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalInviterId);
if (!inviterUser) {
await this.createFederatedUser(externalInviterId, inviterUsername, isInviterFromTheSameHomeServer);
await this.createFederatedUserInternallyOnly(externalInviterId, inviterUsername, isInviterFromTheSameHomeServer);
}

const inviteeUser = await this.internalUserAdapter.getFederatedUserByExternalId(externalInviteeId);
if (!inviteeUser) {
await this.createFederatedUser(externalInviteeId, inviteeUsername, isInviteeFromTheSameHomeServer);
await this.createFederatedUserInternallyOnly(externalInviteeId, inviteeUsername, isInviteeFromTheSameHomeServer);
}
const federatedInviteeUser = inviteeUser || (await this.internalUserAdapter.getFederatedUserByExternalId(externalInviteeId));
const federatedInviterUser = inviterUser || (await this.internalUserAdapter.getFederatedUserByExternalId(externalInviterId));
Expand All @@ -141,8 +150,12 @@ export class FederationRoomServiceListener extends FederationService {
if (isDirectMessageRoom({ t: roomType })) {
const members = [federatedInviterUser, federatedInviteeUser];
const newFederatedRoom = DirectMessageFederatedRoom.createInstance(externalRoomId, federatedInviterUser, members);
await this.internalRoomAdapter.createFederatedRoomForDirectMessage(newFederatedRoom);
const createdInternalRoomId = await this.internalRoomAdapter.createFederatedRoomForDirectMessage(newFederatedRoom);
await this.bridge.joinRoom(externalRoomId, externalInviteeId);
await this.internalNotificationAdapter.subscribeToUserTypingEventsOnFederatedRoomId(
createdInternalRoomId,
this.internalNotificationAdapter.broadcastUserTypingOnRoom.bind(this.internalNotificationAdapter),
);
return;
}
const newFederatedRoom = FederatedRoom.createInstance(
Expand All @@ -153,15 +166,27 @@ export class FederationRoomServiceListener extends FederationService {
externalRoomName,
);

await this.internalRoomAdapter.createFederatedRoom(newFederatedRoom);
const createdInternalRoomId = await this.internalRoomAdapter.createFederatedRoom(newFederatedRoom);
await this.bridge.joinRoom(externalRoomId, externalInviteeId);
await this.internalNotificationAdapter.subscribeToUserTypingEventsOnFederatedRoomId(
createdInternalRoomId,
this.internalNotificationAdapter.broadcastUserTypingOnRoom.bind(this.internalNotificationAdapter),
);
}

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

const inviteeAlreadyJoinedTheInternalRoom = await this.internalRoomAdapter.isUserAlreadyJoined(
federatedRoom.getInternalId(),
federatedInviteeUser.getInternalId(),
);
if (!leave && inviteeAlreadyJoinedTheInternalRoom) {
return;
}

if (leave) {
const inviteeAlreadyJoinedTheInternalRoom = await this.internalRoomAdapter.isUserAlreadyJoined(
federatedRoom.getInternalId(),
Expand All @@ -185,7 +210,11 @@ export class FederationRoomServiceListener extends FederationService {
directMessageRoom.getMembers(),
);
await this.internalRoomAdapter.removeDirectMessageRoom(federatedRoom);
await this.internalRoomAdapter.createFederatedRoomForDirectMessage(newFederatedRoom);
const createdInternalRoomId = await this.internalRoomAdapter.createFederatedRoomForDirectMessage(newFederatedRoom);
await this.internalNotificationAdapter.subscribeToUserTypingEventsOnFederatedRoomId(
createdInternalRoomId,
this.internalNotificationAdapter.broadcastUserTypingOnRoom.bind(this.internalNotificationAdapter),
);
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type { IFederationBridge } from '../domain/IFederationBridge';
import type { RocketChatFileAdapter } from '../infrastructure/rocket-chat/adapters/File';
import type { RocketChatNotificationAdapter } from '../infrastructure/rocket-chat/adapters/Notification';
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 { FederationUserTypingStatusEventDto } from './input/UserReceiverDto';

export class FederationUserServiceListener extends FederationService {
private usersTypingByRoomIdCache: Map<string, Record<string, string>[]> = new Map();

constructor(
protected internalRoomAdapter: RocketChatRoomAdapter,
protected internalUserAdapter: RocketChatUserAdapter,
protected internalFileAdapter: RocketChatFileAdapter,
protected internalNotificationAdapter: RocketChatNotificationAdapter,
protected internalSettingsAdapter: RocketChatSettingsAdapter,
protected bridge: IFederationBridge,
) {
super(bridge, internalUserAdapter, internalFileAdapter, internalSettingsAdapter);
}

private handleUsersWhoStoppedTyping(externalRoomId: string, internalRoomId: string, externalUserIdsTyping: string[]): void {
const isTyping = false;
const notTypingAnymore = this.usersTypingByRoomIdCache
.get(externalRoomId)
?.filter((user) => !externalUserIdsTyping.includes(user.externalUserId));

const stillTyping = this.usersTypingByRoomIdCache
.get(externalRoomId)
?.filter((user) => externalUserIdsTyping.includes(user.externalUserId));

notTypingAnymore?.forEach((user) => this.internalNotificationAdapter.notifyUserTypingOnRoom(internalRoomId, user.username, isTyping));
this.usersTypingByRoomIdCache.set(externalRoomId, stillTyping || []);
}

public async onUserTyping(userTypingInput: FederationUserTypingStatusEventDto): Promise<void> {
const { externalUserIdsTyping, externalRoomId } = userTypingInput;
const federatedRoom = await this.internalRoomAdapter.getFederatedRoomByExternalId(externalRoomId);
if (!federatedRoom) {
return;
}
if (this.usersTypingByRoomIdCache.has(externalRoomId)) {
this.handleUsersWhoStoppedTyping(externalRoomId, federatedRoom.getInternalId(), externalUserIdsTyping);
}

if (externalUserIdsTyping.length === 0) {
return;
}

const federatedUsers = await this.internalUserAdapter.getFederatedUsersByExternalIds(externalUserIdsTyping);
if (federatedUsers.length === 0) {
return;
}

const isTyping = true;

this.usersTypingByRoomIdCache.set(
externalRoomId,
federatedUsers.map((federatedUser) => {
this.internalNotificationAdapter.notifyUserTypingOnRoom(
federatedRoom.getInternalId(),
federatedUser.getUsername() as string,
isTyping,
);

return {
externalUserId: federatedUser.getInternalId(),
username: federatedUser.getUsername() as string,
};
}),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ export interface IFederationChangeMembershipInputDto extends IFederationReceiver
leave?: boolean;
roomType?: RoomType;
externalRoomName?: string;
userAvatarUrl?: string;
userProfile?: {
avatarUrl?: string;
displayName?: string;
};
}

export interface IFederationSendInternalMessageInputDto extends IFederationReceiverBaseRoomInputDto {
Expand Down Expand Up @@ -133,8 +136,8 @@ export class FederationRoomChangeMembershipDto extends FederationBaseRoomInputDt
leave,
roomType,
externalRoomName,
userAvatarUrl,
externalEventId,
userProfile,
}: IFederationChangeMembershipInputDto) {
super({ externalRoomId, normalizedRoomId, externalEventId });
this.externalInviterId = externalInviterId;
Expand All @@ -147,7 +150,7 @@ export class FederationRoomChangeMembershipDto extends FederationBaseRoomInputDt
this.leave = leave;
this.roomType = roomType;
this.externalRoomName = externalRoomName;
this.userAvatarUrl = userAvatarUrl;
this.userProfile = userProfile;
}

externalInviterId: string;
Expand All @@ -170,7 +173,7 @@ export class FederationRoomChangeMembershipDto extends FederationBaseRoomInputDt

externalRoomName?: string;

userAvatarUrl?: string;
userProfile?: { avatarUrl?: string; displayName?: string };
}

class ExternalMessageBaseDto extends FederationBaseRoomInputDto {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { IFederationReceiverBaseRoomInputDto } from './RoomReceiverDto';
import { FederationBaseRoomInputDto } from './RoomReceiverDto';

interface IFederationUserTypingStatusInputDto extends IFederationReceiverBaseRoomInputDto {
externalUserIdsTyping: string[];
}

export class FederationUserTypingStatusEventDto extends FederationBaseRoomInputDto {
constructor({ externalRoomId, normalizedRoomId, externalUserIdsTyping }: IFederationUserTypingStatusInputDto) {
super({ externalRoomId, normalizedRoomId, externalEventId: '' });
this.externalRoomId = externalRoomId;
this.normalizedRoomId = normalizedRoomId;
this.externalUserIdsTyping = externalUserIdsTyping;
}

externalUserIdsTyping: string[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { FederatedUser } from '../../domain/FederatedUser';
import type { IFederationBridge } from '../../domain/IFederationBridge';
import type { RocketChatFileAdapter } from '../../infrastructure/rocket-chat/adapters/File';
import type { RocketChatMessageAdapter } from '../../infrastructure/rocket-chat/adapters/Message';
import type { RocketChatNotificationAdapter } from '../../infrastructure/rocket-chat/adapters/Notification';
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';
Expand All @@ -25,6 +26,7 @@ export class FederationRoomServiceSender extends FederationService {
protected internalFileAdapter: RocketChatFileAdapter,
protected internalMessageAdapter: RocketChatMessageAdapter,
protected internalSettingsAdapter: RocketChatSettingsAdapter,
protected internalNotificationAdapter: RocketChatNotificationAdapter,
protected bridge: IFederationBridge,
) {
super(bridge, internalUserAdapter, internalFileAdapter, internalSettingsAdapter);
Expand All @@ -35,13 +37,13 @@ export class FederationRoomServiceSender extends FederationService {

const internalInviterUser = await this.internalUserAdapter.getFederatedUserByInternalId(internalInviterId);
if (!internalInviterUser) {
await this.createFederatedUserForInviterUsingLocalInformation(internalInviterId);
await this.createFederatedUserIncludingHomeserverUsingLocalInformation(internalInviterId);
}

const internalInviteeUser = await this.internalUserAdapter.getFederatedUserByInternalId(normalizedInviteeId);
if (!internalInviteeUser) {
const existsOnlyOnProxyServer = false;
await this.createFederatedUser(rawInviteeId, normalizedInviteeId, existsOnlyOnProxyServer);
await this.createFederatedUserInternallyOnly(rawInviteeId, normalizedInviteeId, existsOnlyOnProxyServer);
}

const federatedInviterUser = internalInviterUser || (await this.internalUserAdapter.getFederatedUserByInternalId(internalInviterId));
Expand Down Expand Up @@ -71,7 +73,11 @@ export class FederationRoomServiceSender extends FederationService {
federatedInviterUser,
federatedInviteeUser,
]);
await this.internalRoomAdapter.createFederatedRoomForDirectMessage(newFederatedRoom);
const createdInternalRoomId = await this.internalRoomAdapter.createFederatedRoomForDirectMessage(newFederatedRoom);
await this.internalNotificationAdapter.subscribeToUserTypingEventsOnFederatedRoomId(
createdInternalRoomId,
this.internalNotificationAdapter.broadcastUserTypingOnRoom.bind(this.internalNotificationAdapter),
);
}

const federatedRoom =
Expand Down
Loading

0 comments on commit fb32bdf

Please sign in to comment.