Skip to content

Commit

Permalink
[NEW] Alpha Matrix Federation (#23688)
Browse files Browse the repository at this point in the history
Implement alpha support for Matrix Federation

Co-authored-by: Aaron Ogle <aaron@geekgonecrazy.com>
Co-authored-by: Marcos Defendi <marcos.defendi@ulbra.inf.br>
Co-authored-by: Rodrigo Nascimento <rodrigoknascimento@gmail.com>
  • Loading branch information
4 people committed Apr 22, 2022
1 parent cb72d3c commit 774f526
Show file tree
Hide file tree
Showing 49 changed files with 1,760 additions and 44 deletions.
63 changes: 63 additions & 0 deletions apps/meteor/app/federation-v2/server/bridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Bridge, AppServiceRegistration } from 'matrix-appservice-bridge';

import { IMatrixEvent } from './definitions/IMatrixEvent';
import { MatrixEventType } from './definitions/MatrixEventType';
import { addToQueue } from './queue';
import { config } from './config';

/* eslint-disable @typescript-eslint/camelcase */
const registrationConfig = AppServiceRegistration.fromObject({
id: config.id,
hs_token: config.hsToken,
as_token: config.asToken,
url: config.bridgeUrl,
sender_localpart: config.bridgeLocalpart,
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 */

export const matrixBridge = new Bridge({
homeserverUrl: config.homeserverUrl,
domain: config.homeserverDomain,
registration: registrationConfig,
disableStores: true,
controller: {
onAliasQuery: (alias, matrixRoomId): void => {
console.log('onAliasQuery', alias, matrixRoomId);
},
onEvent: async (request /* , context*/): Promise<void> => {
// Get the event
const event = request.getData() as unknown as IMatrixEvent<MatrixEventType>;

addToQueue(event);
},
onLog: async (line, isError): Promise<void> => {
console.log(line, isError);
},
},
});
32 changes: 32 additions & 0 deletions apps/meteor/app/federation-v2/server/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { settings } from '../../settings/server';

type bridgeUrlString = `${string}://${string}:${string}`;
export type bridgeUrlTuple = [string, string, number];

interface IBridgeConfig {
id: string;
hsToken: string;
asToken: string;
homeserverUrl: string;
homeserverDomain: string;
bridgeUrl: bridgeUrlString;
bridgeLocalpart: string;
}

function _getConfig(): IBridgeConfig {
return {
id: settings.get('Federation_Matrix_id') as string,
hsToken: settings.get('Federation_Matrix_hs_token') as string,
asToken: settings.get('Federation_Matrix_as_token') as string,
homeserverUrl: settings.get('Federation_Matrix_homeserver_url') as string,
homeserverDomain: settings.get('Federation_Matrix_homeserver_domain') as string,
bridgeUrl: settings.get('Federation_Matrix_bridge_url') as bridgeUrlString,
bridgeLocalpart: settings.get('Federation_Matrix_bridge_localpart') as string,
} as IBridgeConfig;
}

export let config: IBridgeConfig = _getConfig();

export function getConfig() {
config = _getConfig();
}
9 changes: 9 additions & 0 deletions apps/meteor/app/federation-v2/server/data-interface/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
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,
};
17 changes: 17 additions & 0 deletions apps/meteor/app/federation-v2/server/data-interface/message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IMessage, IUser } from '@rocket.chat/core-typings';

import { dataInterface } from '.';

interface INormalizedMessage extends IMessage {
u: Required<Pick<IUser, '_id' | 'username' | 'name'>>;
}

export const normalize = async (message: IMessage): Promise<INormalizedMessage> => {
// 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<Pick<IUser, '_id' | 'username' | 'name'>>;

return normalizedMessage;
};
8 changes: 8 additions & 0 deletions apps/meteor/app/federation-v2/server/data-interface/room.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IRoom } from '@rocket.chat/core-typings';

import { Rooms } from '../../../models/server';

export const normalize = async (roomId: string): Promise<IRoom> => {
// Normalize the user
return Rooms.findOneById(roomId);
};
8 changes: 8 additions & 0 deletions apps/meteor/app/federation-v2/server/data-interface/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IUser } from '@rocket.chat/core-typings';

import { Users } from '../../../models/server';

export const normalize = async (userId: string): Promise<IUser> => {
// Normalize the user
return Users.findOneById(userId);
};
16 changes: 16 additions & 0 deletions apps/meteor/app/federation-v2/server/definitions/IMatrixEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MatrixEventType } from './MatrixEventType';
import { EventContent } from './IMatrixEventContent';

export interface IMatrixEvent<T extends MatrixEventType> {
age: number;
content: EventContent[T];
invite_room_state?: IMatrixEvent<MatrixEventType>[];
event_id: string;
origin_server_ts: number;
room_id: string;
sender: string;
state_key: string;
type: T;
unsigned: { age: number };
user_id: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export enum AddMemberToRoomMembership {
JOIN = 'join',
INVITE = 'invite',
LEAVE = 'leave',
}

export interface IMatrixEventContentAddMemberToRoom {
displayname: string;
membership: AddMemberToRoomMembership;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface IMatrixEventContentCreateRoom {
creator: string;
room_version: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export enum MatrixSendMessageType {
'm.text',
}

export interface IMatrixEventContentSendMessage {
body: string;
msgtype: MatrixSendMessageType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export enum SetRoomJoinRules {
JOIN = 'public',
INVITE = 'invite',
}

export interface IMatrixEventContentSetRoomJoinRules {
join_rule: SetRoomJoinRules;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface IMatrixEventContentSetRoomName {
name: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface IMatrixEventContentSetRoomTopic {
topic: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MatrixEventType } from '../MatrixEventType';
import { IMatrixEventContentCreateRoom } from './IMatrixEventContentCreateRoom';
import { IMatrixEventContentAddMemberToRoom } from './IMatrixEventContentAddMemberToRoom';
import { IMatrixEventContentSendMessage } from './IMatrixEventContentSendMessage';
import { IMatrixEventContentSetRoomJoinRules } from './IMatrixEventContentSetRoomJoinRules';
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;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export enum MatrixEventType {
CREATE_ROOM = 'm.room.create',
ROOM_MEMBERSHIP = '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',
// 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',
}
50 changes: 50 additions & 0 deletions apps/meteor/app/federation-v2/server/eventHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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<MatrixEventType>): Promise<void> => {
console.log(`Processing ${event.type}...`, JSON.stringify(event, null, 2));

switch (event.type) {
case MatrixEventType.CREATE_ROOM: {
await handleCreateRoom(event as IMatrixEvent<MatrixEventType.CREATE_ROOM>);

break;
}
case MatrixEventType.ROOM_MEMBERSHIP: {
await handleRoomMembership(event as IMatrixEvent<MatrixEventType.ROOM_MEMBERSHIP>);

break;
}
case MatrixEventType.SET_ROOM_JOIN_RULES: {
await setRoomJoinRules(event as IMatrixEvent<MatrixEventType.SET_ROOM_JOIN_RULES>);

break;
}
case MatrixEventType.SET_ROOM_NAME: {
await setRoomName(event as IMatrixEvent<MatrixEventType.SET_ROOM_NAME>);

break;
}
case MatrixEventType.SET_ROOM_TOPIC: {
await setRoomTopic(event as IMatrixEvent<MatrixEventType.SET_ROOM_TOPIC>);

break;
}
case MatrixEventType.SEND_MESSAGE: {
await handleSendMessage(event as IMatrixEvent<MatrixEventType.SEND_MESSAGE>);

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);
}
};
44 changes: 44 additions & 0 deletions apps/meteor/app/federation-v2/server/events/createRoom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { MatrixBridgedRoom, MatrixBridgedUser, Users } from '../../../models/server';
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';

export const handleCreateRoom = async (event: IMatrixEvent<MatrixEventType.CREATE_ROOM>): Promise<void> => {
const { room_id: matrixRoomId, sender } = event;

return new Promise((resolve) => {
setTimeout(async () => {
// Check if the room already exists and if so, ignore
const roomExists = await checkBridgedRoomExists(matrixRoomId);

if (roomExists) {
return resolve();
}

// Find the bridged user id
const bridgedUserId = await MatrixBridgedUser.getId(sender);
let user;

// Create the user if necessary
if (!bridgedUserId) {
const { uid } = await matrixClient.user.createLocal(sender);

user = Users.findOneById(uid);
} else {
user = await Users.findOneById(bridgedUserId);
}

// Create temp room name
const roomName = `Federation-${matrixRoomId.split(':')[0].replace('!', '')}`;

// @ts-ignore TODO: typing of legacy functions
const { rid: roomId } = createRoom('c', roomName, user.username);

MatrixBridgedRoom.insert({ rid: roomId, mri: matrixRoomId });

resolve();
}, 500);
});
};
6 changes: 6 additions & 0 deletions apps/meteor/app/federation-v2/server/events/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './createRoom';
export * from './roomMembership';
export * from './sendMessage';
export * from './setRoomJoinRules';
export * from './setRoomName';
export * from './setRoomTopic';
Loading

0 comments on commit 774f526

Please sign in to comment.