RocketChat/Rocket.Chat

View on GitHub
apps/meteor/server/services/federation/domain/FederatedRoom.ts

Summary

Maintainability
B
6 hrs
Test Coverage
import { RoomType } from '@rocket.chat/apps-engine/definition/rooms';
import type { IDirectMessageRoom, IMessage, IRoom } from '@rocket.chat/core-typings';
import { ObjectId } from 'mongodb'; // This should not be in the domain layer, but its a known "problem"

import type { FederatedUser } from './FederatedUser';

export const isAnInternalIdentifier = (fromOriginName: string, localOriginName: string): boolean => {
    return fromOriginName === localOriginName;
};

export abstract class AbstractFederatedRoom {
    protected externalId: string;

    protected internalId: string;

    protected internalReference: Partial<IRoom>;

    protected constructor({ externalId, internalReference }: { externalId: string; internalReference: Partial<IRoom> }) {
        this.externalId = externalId;
        this.internalReference = internalReference;
        this.internalId = internalReference._id || new ObjectId().toHexString();
    }

    protected static generateTemporaryName(normalizedExternalId: string): string {
        return `Federation-${normalizedExternalId}`;
    }

    public abstract isDirectMessage(): boolean;

    public getExternalId(): string {
        return this.externalId;
    }

    public getRoomType(): RoomType {
        return this.internalReference.t as RoomType;
    }

    public getInternalId(): string {
        return this.internalId;
    }

    public getDisplayName(): string | undefined {
        return this.internalReference.fname;
    }

    public getName(): string | undefined {
        return this.internalReference.name;
    }

    public getTopic(): string | undefined {
        return this.internalReference.topic;
    }

    public static isOriginalFromTheProxyServer(fromOriginName: string, proxyServerOriginName: string): boolean {
        return isAnInternalIdentifier(fromOriginName, proxyServerOriginName);
    }

    public getInternalReference(): Readonly<Partial<IRoom>> {
        return Object.freeze({
            ...this.internalReference,
            _id: this.internalId,
        });
    }

    public getCreatorUsername(): string | undefined {
        return this.internalReference.u?.username;
    }

    public isTheCreator(userId: string): boolean {
        return this.internalReference.u?._id === userId;
    }

    public getCreatorId(): string | undefined {
        return this.internalReference.u?._id;
    }

    public changeRoomType(type: RoomType): void {
        if (this.isDirectMessage()) {
            throw new Error('Its not possible to change a direct message type');
        }
        this.internalReference.t = type;
    }

    public changeDisplayRoomName(displayName: string): void {
        if (this.isDirectMessage()) {
            throw new Error('Its not possible to change a direct message name');
        }
        this.internalReference.fname = displayName;
    }

    public shouldUpdateRoomName(aRoomName: string): boolean {
        return this.internalReference?.name !== aRoomName && !this.isDirectMessage();
    }

    public changeRoomName(name: string): void {
        if (this.isDirectMessage()) {
            throw new Error('Its not possible to change a direct message name');
        }
        this.internalReference.name = name;
    }

    public changeRoomTopic(topic: string): void {
        if (this.isDirectMessage()) {
            throw new Error('Its not possible to change a direct message topic');
        }
        this.internalReference.topic = topic;
    }

    public shouldUpdateDisplayRoomName(aRoomName: string): boolean {
        return this.internalReference?.fname !== aRoomName && !this.isDirectMessage();
    }

    public shouldUpdateRoomTopic(aRoomTopic: string): boolean {
        return this.internalReference?.topic !== aRoomTopic && !this.isDirectMessage();
    }

    public static shouldUpdateMessage(newMessageText: string, originalMessage: IMessage): boolean {
        return originalMessage.msg !== newMessageText;
    }
}

export class FederatedRoom extends AbstractFederatedRoom {
    protected constructor({ externalId, internalReference }: { externalId: string; internalReference: Partial<IRoom> }) {
        super({ externalId, internalReference });
    }

    public static createInstance(
        externalId: string,
        normalizedExternalId: string,
        creator: FederatedUser,
        type: RoomType,
        displayName?: string,
        name?: string,
    ): FederatedRoom {
        if (type === RoomType.DIRECT_MESSAGE) {
            throw new Error('For DMs please use the specific class');
        }
        const roomName = displayName || FederatedRoom.generateTemporaryName(normalizedExternalId);
        return new FederatedRoom({
            externalId,
            internalReference: {
                t: type,
                fname: roomName,
                name,
                u: creator.getInternalReference(),
            },
        });
    }

    public static createWithInternalReference(externalId: string, internalReference: IRoom): FederatedRoom {
        if (internalReference.t === RoomType.DIRECT_MESSAGE) {
            throw new Error('For DMs please use the specific class');
        }
        return new FederatedRoom({
            externalId,
            internalReference,
        });
    }

    public isDirectMessage(): boolean {
        return false;
    }
}

export class DirectMessageFederatedRoom extends AbstractFederatedRoom {
    public members: FederatedUser[];

    protected constructor({
        externalId,
        internalReference,
        members,
    }: {
        externalId: string;
        internalReference: Partial<IRoom>;
        members: FederatedUser[];
    }) {
        super({ externalId, internalReference });
        this.members = members;
    }

    public static createInstance(externalId: string, creator: FederatedUser, members: FederatedUser[]): DirectMessageFederatedRoom {
        return new DirectMessageFederatedRoom({
            externalId,
            members,
            internalReference: {
                t: RoomType.DIRECT_MESSAGE,
                u: creator.getInternalReference(),
            },
        });
    }

    public static createWithInternalReference<T extends IRoom | IDirectMessageRoom>(
        externalId: string,
        internalReference: T,
        members: FederatedUser[],
    ): DirectMessageFederatedRoom {
        return new DirectMessageFederatedRoom({
            externalId,
            internalReference,
            members,
        });
    }

    public getMembersUsernames(): string[] {
        return this.members.map((user) => user.getUsername() || '').filter(Boolean);
    }

    public getMembers(): FederatedUser[] {
        return this.members;
    }

    public addMember(member: FederatedUser): void {
        this.members.push(member);
    }

    public isDirectMessage(): boolean {
        return true;
    }

    public isUserPartOfTheRoom(federatedUser: FederatedUser): boolean {
        if (!federatedUser.getUsername()) {
            return false;
        }
        if (!this.internalReference?.usernames) {
            return false;
        }
        return this.internalReference.usernames.includes(federatedUser.getUsername() as string);
    }
}