Skip to content
Merged
9 changes: 0 additions & 9 deletions apps/meteor/app/api/server/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { metricsMiddleware } from './middlewares/metrics';
import { remoteAddressMiddleware } from './middlewares/remoteAddressMiddleware';
import { tracerSpanMiddleware } from './middlewares/tracer';
import { type APIActionHandler, RocketChatAPIRouter } from './router';
import { isRunningMs } from '../../../server/lib/isRunningMs';
import { metrics } from '../../metrics/server';
import { settings } from '../../settings/server';

Expand Down Expand Up @@ -107,14 +106,6 @@ settings.watch<number>('API_Enable_Rate_Limiter_Limit_Calls_Default', (value) =>
});

export const startRestAPI = () => {
// Register federation routes at root level if enabled and not running in MS mode
if (settings.get('Federation_Service_Enabled') && !isRunningMs()) {
(WebApp.rawConnectHandlers as unknown as ReturnType<typeof express>)
.use(API._matrix.router)
.use(API.wellKnown.router)
.use(API.matrixInternal.router);
}

// Register main API routes under /api prefix
(WebApp.rawConnectHandlers as unknown as ReturnType<typeof express>).use(
API.api
Expand Down
125 changes: 4 additions & 121 deletions apps/meteor/ee/server/api/federation.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import type { IFederationMatrixService } from '@rocket.chat/core-services';
import { Logger } from '@rocket.chat/logger';
import type express from 'express';
import { WebApp } from 'meteor/webapp';

import { API } from '../../../app/api/server';
import { isRunningMs } from '../../../server/lib/isRunningMs';

interface IExtendedContext {
urlParams?: Record<string, string>;
queryParams?: Record<string, any>;
bodyParams?: Record<string, any>;
request?: Request;
_statusCode?: number;
_headers?: Record<string, string>;
}

const logger = new Logger('FederationRoutes');

export async function registerFederationRoutes(federationService: IFederationMatrixService): Promise<void> {
Expand All @@ -22,118 +14,9 @@ export async function registerFederationRoutes(federationService: IFederationMat

try {
const routes = federationService.getAllRoutes();
(WebApp.rawConnectHandlers as unknown as ReturnType<typeof express>).use(routes.matrix.router).use(routes.wellKnown.router);

for (const route of routes) {
const method = route.method.toLowerCase() as 'get' | 'post' | 'put' | 'delete' | 'patch';

let router: any;
if (route.path.startsWith('/_matrix')) {
router = API._matrix;
} else if (route.path.startsWith('/.well-known')) {
router = API.wellKnown;
} else if (route.path.startsWith('/internal')) {
router = API.matrixInternal;
} else {
logger.error(`Unknown route prefix for path: ${route.path}`);
continue;
}

if (method === 'patch') {
if (typeof (router as any).method === 'function') {
const routePath = route.path.replace(/^\/_matrix|^\/\.well-known|^\/internal/, '');
(router as any).method('PATCH', routePath || '/', { response: {} }, async function (this: IExtendedContext) {
try {
const context = {
params: this.urlParams || {},
query: this.queryParams || {},
body: this.bodyParams || {},
headers: this.request?.headers ? Object.fromEntries(this.request.headers.entries()) : {},
setStatus: (code: number) => {
this._statusCode = code;
},
setHeader: (key: string, value: string) => {
if (!this._headers) {
this._headers = {};
}
this._headers[key] = value;
},
};

const response = await route.handler(context);

const result: any = {
statusCode: this._statusCode || 200,
body: response,
};

if (this._headers) {
result.headers = this._headers;
}

return result;
} catch (error) {
logger.error(`Error handling route: ${route.path}`, error);
return {
statusCode: 500,
body: { error: 'Internal server error' },
};
}
});
continue;
} else {
logger.error(`Cannot register PATCH method for route ${route.path} - method() function not available`);
continue;
}
}

if (typeof (router as any)[method] !== 'function') {
logger.error(`Method ${method} not found on router for path: ${route.path}`);
continue;
}

const routePath = route.path.replace(/^\/_matrix|^\/\.well-known|^\/internal/, '');

(router as any)[method](routePath || '/', { response: {} }, async function (this: IExtendedContext) {
try {
const context = {
params: this.urlParams || {},
query: this.queryParams || {},
body: this.bodyParams || {},
headers: this.request?.headers ? Object.fromEntries(this.request.headers.entries()) : {},
setStatus: (code: number) => {
this._statusCode = code;
},
setHeader: (key: string, value: string) => {
if (!this._headers) {
this._headers = {};
}
this._headers[key] = value;
},
};

const response = await route.handler(context);

const result: any = {
statusCode: this._statusCode || 200,
body: response,
};

if (this._headers) {
result.headers = this._headers;
}

return result;
} catch (error) {
logger.error(`Error handling route: ${route.path}`, error);
return {
statusCode: 500,
body: { error: 'Internal server error' },
};
}
});
}

logger.log('[Federation] Registered', routes.length, 'federation routes');
logger.log('[Federation] Registered federation routes');
} catch (error) {
logger.error('[Federation] Failed to register routes:', error);
throw error;
Expand Down
31 changes: 5 additions & 26 deletions ee/apps/federation-service/src/service.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,12 @@
import 'reflect-metadata';
import { serve } from '@hono/node-server';
import { api, getConnection, getTrashCollection } from '@rocket.chat/core-services';
// import type { RouteDefinition, RouteContext } from '@hs/federation-sdk';
import { registerServiceModels } from '@rocket.chat/models';
import { startBroker } from '@rocket.chat/network-broker';
import { Hono } from 'hono';

import { config } from './config';

// export function handleFederationRoutesRegistration(app: Hono, homeserverRoutes: RouteDefinition[]): Hono {
// // console.info(`Registering ${homeserverRoutes.length} homeserver routes`);
// // for (const route of homeserverRoutes) {
// // const method = route.method.toLowerCase() as 'get' | 'post' | 'put' | 'delete';
// // app[method](route.path, async (c) => {
// // try {
// // const context = {
// // req: c.req,
// // res: c.res,
// // params: c.req.param(),
// // query: c.req.query(),
// // body: await c.req.json().catch(() => ({})),
// // };
// // const result = await route.handler(context as unknown as RouteContext);
// // return c.json(result);
// // } catch (error) {
// // console.error(`Error handling route ${method.toUpperCase()} ${route.path}:`, error);
// // return c.json({ error: 'Internal server error' }, 500);
// // }
// // });
// // }
// // return app;
// }

function handleHealthCheck(app: Hono) {
app.get('/health', async (c) => {
try {
Expand All @@ -56,7 +31,11 @@ function handleHealthCheck(app: Hono) {
api.registerService(federationMatrix);

const app = new Hono();
// handleFederationRoutesRegistration(app, federationMatrix.getAllRoutes());
const { matrix, wellKnown } = federationMatrix.getAllRoutes();

app.mount('/_matrix', matrix.getHonoRouter().fetch);
app.mount('/.well-known', wellKnown.getHonoRouter().fetch);

handleHealthCheck(app);

serve({
Expand Down
4 changes: 2 additions & 2 deletions ee/packages/federation-matrix/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
"@babel/core": "~7.26.0",
"@babel/preset-env": "~7.26.0",
"@babel/preset-typescript": "~7.26.0",
"@rocket.chat/apps-engine": "workspace:^",
"@rocket.chat/eslint-config": "workspace:^",
"@rocket.chat/rest-typings": "workspace:^",
"@types/node": "~22.14.0",
"babel-jest": "~30.0.0",
"eslint": "~8.45.0",
Expand Down Expand Up @@ -38,8 +36,10 @@
"@rocket.chat/core-services": "workspace:^",
"@rocket.chat/core-typings": "workspace:^",
"@rocket.chat/emitter": "^0.31.25",
"@rocket.chat/http-router": "workspace:^",
"@rocket.chat/models": "workspace:^",
"@rocket.chat/network-broker": "workspace:^",
"@rocket.chat/rest-typings": "workspace:^",
"mongodb": "6.10.0",
"pino": "8.21.0",
"reflect-metadata": "^0.2.2"
Expand Down
3 changes: 2 additions & 1 deletion ee/packages/federation-matrix/src/FederationMatrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Emitter } from '@rocket.chat/emitter';
import { Logger } from '@rocket.chat/logger';
import { MatrixBridgedUser, MatrixBridgedRoom, Users } from '@rocket.chat/models';

import { getAllMatrixRoutes } from './api/api';
import { registerEvents } from './events';
import { setup } from './setupContainers';

Expand Down Expand Up @@ -62,7 +63,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS
}

getAllRoutes() {
return [];
return getAllMatrixRoutes();
}

async createRoom(room: IRoom, owner: IUser, members: string[]): Promise<void> {
Expand Down
43 changes: 43 additions & 0 deletions ee/packages/federation-matrix/src/api/.well-known/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Router } from "@rocket.chat/http-router";
import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv';
import { getAllServicesFromFederationSDK } from '../../setupContainers';
import { createHash } from 'node:crypto';

const WellKnownServerResponseSchema = {
type: 'object',
properties: {
'm.server': {
type: 'string',
description: 'Matrix server address with port'
}
},
required: ['m.server']
};

const isWellKnownServerResponseProps = ajv.compile(WellKnownServerResponseSchema);

export const getWellKnownRoutes = (router: Router<'/.well-known'>) => {
const { wellKnown } = getAllServicesFromFederationSDK();

return router.get('/matrix/server', {
response: {
200: isWellKnownServerResponseProps
},
tags: ['Well-Known'],
license: ['federation']
}, async (c) => {
const responseData = wellKnown.getWellKnownHostData();

const etag = createHash('md5')
.update(JSON.stringify(responseData))
.digest('hex');

c.header('ETag', etag);
c.header('Content-Type', 'application/json');

return {
body: responseData,
statusCode: 200,
};
});
};
Loading
Loading