diff --git a/apps/meteor/ee/server/startup/federation.ts b/apps/meteor/ee/server/startup/federation.ts index d2e20f9ead1e7..2102431defcff 100644 --- a/apps/meteor/ee/server/startup/federation.ts +++ b/apps/meteor/ee/server/startup/federation.ts @@ -1,35 +1,75 @@ import { api, FederationMatrix as FederationMatrixService } from '@rocket.chat/core-services'; -import { FederationMatrix, setupFederationMatrix } from '@rocket.chat/federation-matrix'; +import { FederationMatrix, configureFederationMatrixSettings, setupFederationMatrix } from '@rocket.chat/federation-matrix'; import { InstanceStatus } from '@rocket.chat/instance-status'; +import { License } from '@rocket.chat/license'; import { Logger } from '@rocket.chat/logger'; +import { settings } from '../../../app/settings/server'; import { StreamerCentral } from '../../../server/modules/streamer/streamer.module'; import { registerFederationRoutes } from '../api/federation'; const logger = new Logger('Federation'); -export const startFederationService = async (): Promise => { +let serviceEnabled = false; + +const configureFederation = async () => { + // only registers the typing listener if the service is enabled + serviceEnabled = (await License.hasModule('federation')) && settings.get('Federation_Service_Enabled'); + if (!serviceEnabled) { + return; + } + try { - const isEnabled = await setupFederationMatrix(InstanceStatus.id()); + configureFederationMatrixSettings({ + instanceId: InstanceStatus.id(), + domain: settings.get('Federation_Service_Domain'), + signingKey: settings.get('Federation_Service_Matrix_Signing_Key'), + signingAlgorithm: settings.get('Federation_Service_Matrix_Signing_Algorithm'), + signingVersion: settings.get('Federation_Service_Matrix_Signing_Version'), + allowedEncryptedRooms: settings.get('Federation_Service_Join_Encrypted_Rooms'), + allowedNonPrivateRooms: settings.get('Federation_Service_Join_Non_Private_Rooms'), + processEDUTyping: settings.get('Federation_Service_EDU_Process_Typing'), + processEDUPresence: settings.get('Federation_Service_EDU_Process_Presence'), + }); + } catch (error) { + logger.error('Failed to start federation-matrix service:', error); + } +}; - api.registerService(new FederationMatrix()); +export const startFederationService = async (): Promise => { + api.registerService(new FederationMatrix()); - await registerFederationRoutes(); + await registerFederationRoutes(); - // only registers the typing listener if the service is enabled - if (!isEnabled) { + // TODO move to service/setup? + StreamerCentral.on('broadcast', (name, eventName, args) => { + if (!serviceEnabled) { return; } - // TODO move to service/setup? - StreamerCentral.on('broadcast', (name, eventName, args) => { - if (name === 'notify-room' && eventName.endsWith('user-activity')) { - const [rid] = eventName.split('/'); - const [user, activity] = args; - void FederationMatrixService.notifyUserTyping(rid, user, activity.includes('user-typing')); - } - }); - } catch (error) { - logger.error('Failed to start federation-matrix service:', error); - } + if (name === 'notify-room' && eventName.endsWith('user-activity')) { + const [rid] = eventName.split('/'); + const [user, activity] = args; + void FederationMatrixService.notifyUserTyping(rid, user, activity.includes('user-typing')); + } + }); + + await setupFederationMatrix(); + + settings.watchMultiple( + [ + 'Federation_Service_Enabled', + 'Federation_Service_Domain', + 'Federation_Service_EDU_Process_Typing', + 'Federation_Service_EDU_Process_Presence', + 'Federation_Service_Matrix_Signing_Key', + 'Federation_Service_Matrix_Signing_Algorithm', + 'Federation_Service_Matrix_Signing_Version', + 'Federation_Service_Join_Encrypted_Rooms', + 'Federation_Service_Join_Non_Private_Rooms', + ], + async () => { + await configureFederation(); + }, + ); }; diff --git a/apps/meteor/package.json b/apps/meteor/package.json index c2d47cdc40001..33d3ec29fad23 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -252,7 +252,7 @@ "@rocket.chat/emitter": "~0.31.25", "@rocket.chat/favicon": "workspace:^", "@rocket.chat/federation-matrix": "workspace:^", - "@rocket.chat/federation-sdk": "0.2.0", + "@rocket.chat/federation-sdk": "0.3.0", "@rocket.chat/freeswitch": "workspace:^", "@rocket.chat/fuselage": "~0.66.4", "@rocket.chat/fuselage-forms": "~0.1.0", diff --git a/ee/packages/federation-matrix/package.json b/ee/packages/federation-matrix/package.json index 3c070ef81e310..ace17a6ab4f39 100644 --- a/ee/packages/federation-matrix/package.json +++ b/ee/packages/federation-matrix/package.json @@ -38,7 +38,7 @@ "@rocket.chat/core-services": "workspace:^", "@rocket.chat/core-typings": "workspace:^", "@rocket.chat/emitter": "^0.31.25", - "@rocket.chat/federation-sdk": "0.2.0", + "@rocket.chat/federation-sdk": "0.3.0", "@rocket.chat/http-router": "workspace:^", "@rocket.chat/license": "workspace:^", "@rocket.chat/models": "workspace:^", @@ -47,7 +47,7 @@ "emojione": "^4.5.0", "marked": "^16.1.2", "mongodb": "6.16.0", - "pino": "^9.11.0", + "pino": "^8.21.0", "reflect-metadata": "^0.2.2", "sanitize-html": "~2.17.0", "tsyringe": "^4.10.0", diff --git a/ee/packages/federation-matrix/src/FederationMatrix.ts b/ee/packages/federation-matrix/src/FederationMatrix.ts index 5a3ef9e78fc1f..b3fbb472d64df 100644 --- a/ee/packages/federation-matrix/src/FederationMatrix.ts +++ b/ee/packages/federation-matrix/src/FederationMatrix.ts @@ -8,8 +8,8 @@ import { UserStatus, } from '@rocket.chat/core-typings'; import type { MessageQuoteAttachment, IMessage, IRoom, IUser, IRoomNativeFederated } from '@rocket.chat/core-typings'; -import { eventIdSchema, getAllServices, roomIdSchema, userIdSchema } from '@rocket.chat/federation-sdk'; -import type { EventID, UserID, HomeserverServices, FileMessageType, PresenceState, PduForType } from '@rocket.chat/federation-sdk'; +import { eventIdSchema, roomIdSchema, userIdSchema, federationSDK } from '@rocket.chat/federation-sdk'; +import type { EventID, UserID, FileMessageType, PresenceState } from '@rocket.chat/federation-sdk'; import { Logger } from '@rocket.chat/logger'; import { Users, Subscriptions, Messages, Rooms, Settings } from '@rocket.chat/models'; import emojione from 'emojione'; @@ -143,8 +143,6 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS private processEDUPresence: boolean; - private homeserverServices: HomeserverServices; - private readonly logger = new Logger(this.name); async created(): Promise { @@ -192,7 +190,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS [UserStatus.BUSY]: 'unavailable', [UserStatus.DISABLED]: 'offline', }; - void this.homeserverServices.edu.sendPresenceUpdateToRooms( + void federationSDK.sendPresenceUpdateToRooms( [ { user_id: localUser.federation.mui, @@ -207,22 +205,9 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS this.serverName = (await Settings.getValueById('Federation_Service_Domain')) || ''; this.processEDUTyping = (await Settings.getValueById('Federation_Service_EDU_Process_Typing')) || false; this.processEDUPresence = (await Settings.getValueById('Federation_Service_EDU_Process_Presence')) || false; - - try { - this.homeserverServices = getAllServices(); - - MatrixMediaService.setHomeserverServices(this.homeserverServices); - } catch (err) { - this.logger.warn({ msg: 'Homeserver module not available, running in limited mode', err }); - } } async createRoom(room: IRoom, owner: IUser, members: string[]): Promise<{ room_id: string; event_id: string }> { - if (!this.homeserverServices) { - this.logger.warn('Homeserver services not available, skipping room creation'); - throw new Error('Homeserver services not available'); - } - if (room.t !== 'c' && room.t !== 'p') { throw new Error('Room is not a public or private room'); } @@ -232,7 +217,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS const roomName = room.name || room.fname || 'Untitled Room'; // canonical alias computed from name - const matrixRoomResult = await this.homeserverServices.room.createRoom(matrixUserId, roomName, room.t === 'c' ? 'public' : 'invite'); + const matrixRoomResult = await federationSDK.createRoom(matrixUserId, roomName, room.t === 'c' ? 'public' : 'invite'); this.logger.debug('Matrix room created:', matrixRoomResult); @@ -284,11 +269,6 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS try { this.logger.debug('Creating direct message room in Matrix', { roomId: room._id, memberCount: members.length }); - if (!this.homeserverServices) { - this.logger.warn('Homeserver services not available, skipping DM room creation'); - return; - } - const creator = await Users.findOneById(creatorId); if (!creator) { throw new Error('Creator not found in members list'); @@ -305,7 +285,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS if (!isUserNativeFederated(otherMember)) { throw new Error('Other member is not federated'); } - const roomId = await this.homeserverServices.room.createDirectMessageRoom( + const roomId = await federationSDK.createDirectMessageRoom( userIdSchema.parse(actualMatrixUserId), userIdSchema.parse(otherMember.username), ); @@ -313,7 +293,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS } else { // For group DMs (more than 2 members), create a private room const roomName = room.name || room.fname || `Group chat with ${members.length} members`; - matrixRoomResult = await this.homeserverServices.room.createRoom(userIdSchema.parse(actualMatrixUserId), roomName, 'invite'); + matrixRoomResult = await federationSDK.createRoom(userIdSchema.parse(actualMatrixUserId), roomName, 'invite'); for await (const member of members) { if (member._id === creatorId) { @@ -325,7 +305,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS } try { - await this.homeserverServices.invite.inviteUserToRoom( + await federationSDK.inviteUserToRoom( userIdSchema.parse(member.username), roomIdSchema.parse(matrixRoomResult.room_id), userIdSchema.parse(actualMatrixUserId), @@ -387,7 +367,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS }, }; - lastEventId = await this.homeserverServices.message.sendFileMessage( + lastEventId = await federationSDK.sendFileMessage( roomIdSchema.parse(matrixRoomId), fileContent, userIdSchema.parse(matrixUserId), @@ -419,7 +399,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS const replyToMessage = await this.handleThreadedMessage(message, matrixRoomId, matrixUserId, matrixDomain); const quoteMessage = await this.handleQuoteMessage(message, matrixRoomId, matrixUserId, matrixDomain); - return this.homeserverServices.message.sendMessage( + return federationSDK.sendMessage( roomIdSchema.parse(matrixRoomId), message.msg, parsedMessage, @@ -476,11 +456,6 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS async sendMessage(message: IMessage, room: IRoomNativeFederated, user: IUser): Promise { try { - if (!this.homeserverServices) { - this.logger.warn('Homeserver services not available, skipping message send'); - return; - } - const userMui = isUserNativeFederated(user) ? user.federation.mui : `@${user.username}:${this.serverName}`; let result; @@ -549,11 +524,6 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS return; } - if (!this.homeserverServices) { - this.logger.warn('Homeserver services not available, skipping message redaction'); - return; - } - const matrixEventId = message.federation?.eventId; if (!matrixEventId) { throw new Error(`No Matrix event ID mapping found for message ${message._id}`); @@ -561,10 +531,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS // TODO fix branded EventID and remove type casting // TODO message.u?.username is not the user who removed the message - const eventId = await this.homeserverServices.message.redactMessage( - roomIdSchema.parse(matrixRoomId), - eventIdSchema.parse(matrixEventId), - ); + const eventId = await federationSDK.redactMessage(roomIdSchema.parse(matrixRoomId), eventIdSchema.parse(matrixEventId)); this.logger.debug('Message Redaction sent to Matrix successfully:', eventId); } catch (error) { @@ -580,7 +547,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS await Promise.all( matrixUsersUsername.map(async (username) => { if (validateFederatedUsername(username)) { - return this.homeserverServices.invite.inviteUserToRoom( + return federationSDK.inviteUserToRoom( userIdSchema.parse(username), roomIdSchema.parse(room.federation.mrid), userIdSchema.parse(inviterUserId), @@ -594,13 +561,13 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS return; } - const result = await this.homeserverServices.invite.inviteUserToRoom( + const result = await federationSDK.inviteUserToRoom( userIdSchema.parse(`@${username}:${this.serverName}`), roomIdSchema.parse(room.federation.mrid), userIdSchema.parse(inviterUserId), ); - return acceptInvite(result.event, username, this.homeserverServices); + return acceptInvite(result.event, username); }), ); } catch (error) { @@ -630,7 +597,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS const userMui = isUserNativeFederated(user) ? user.federation.mui : `@${user.username}:${this.serverName}`; - const eventId = await this.homeserverServices.message.sendReaction( + const eventId = await federationSDK.sendReaction( roomIdSchema.parse(room.federation.mrid), eventIdSchema.parse(matrixEventId), reactionKey, @@ -680,7 +647,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS continue; } - const redactionEventId = await this.homeserverServices.message.unsetReaction( + const redactionEventId = await federationSDK.unsetReaction( roomIdSchema.parse(room.federation.mrid), eventIdSchema.parse(eventId), reactionKey, @@ -701,7 +668,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS } async getEventById(eventId: EventID) { - return this.homeserverServices.event.getEventById(eventId); + return federationSDK.getEventById(eventId); } async leaveRoom(roomId: string, user: IUser, kicker?: IUser): Promise { @@ -717,14 +684,9 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS return; } - if (!this.homeserverServices) { - this.logger.warn('Homeserver services not available, skipping room leave'); - return; - } - const actualMatrixUserId = isUserNativeFederated(user) ? user.federation.mui : `@${user.username}:${this.serverName}`; - await this.homeserverServices.room.leaveRoom(roomIdSchema.parse(room.federation.mrid), userIdSchema.parse(actualMatrixUserId)); + await federationSDK.leaveRoom(roomIdSchema.parse(room.federation.mrid), userIdSchema.parse(actualMatrixUserId)); this.logger.info(`User ${user.username} left Matrix room ${room.federation.mrid} successfully`); } catch (error) { @@ -734,11 +696,6 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS } async kickUser(room: IRoomNativeFederated, removedUser: IUser, userWhoRemoved: IUser): Promise { - if (!this.homeserverServices) { - this.logger.warn('Homeserver services not available, skipping user kick'); - return; - } - try { const actualKickedMatrixUserId = isUserNativeFederated(removedUser) ? removedUser.federation.mui @@ -748,7 +705,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS ? userWhoRemoved.federation.mui : `@${userWhoRemoved.username}:${this.serverName}`; - await this.homeserverServices.room.kickUser( + await federationSDK.kickUser( roomIdSchema.parse(room.federation.mrid), userIdSchema.parse(actualKickedMatrixUserId), userIdSchema.parse(actualSenderMatrixUserId), @@ -782,7 +739,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS externalRoomId: room.federation.mrid, homeServerDomain: this.serverName, }); - const eventId = await this.homeserverServices.message.updateMessage( + const eventId = await federationSDK.updateMessage( roomIdSchema.parse(room.federation.mrid), message.msg, parsedMessage, @@ -798,11 +755,6 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS } async updateRoomName(rid: string, displayName: string, user: IUser): Promise { - if (!this.homeserverServices) { - this.logger.warn('Homeserver services not available, skipping room name update'); - return; - } - const room = await Rooms.findOneById(rid); if (!room || !isRoomNativeFederated(room)) { throw new Error(`No Matrix room mapping found for room ${rid}`); @@ -815,7 +767,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS const userMui = `@${user.username}:${this.serverName}`; - await this.homeserverServices.room.updateRoomName(roomIdSchema.parse(room.federation.mrid), displayName, userIdSchema.parse(userMui)); + await federationSDK.updateRoomName(roomIdSchema.parse(room.federation.mrid), displayName, userIdSchema.parse(userMui)); } async updateRoomTopic( @@ -823,12 +775,6 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS topic: string, user: Pick, ): Promise { - if (!this.homeserverServices) { - this.logger.warn('Homeserver services not available, skipping room topic update'); - - return; - } - if (isUserNativeFederated(user)) { this.logger.debug('Only local users can change the topic of a room, ignoring action'); return; @@ -836,7 +782,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS const userMui = `@${user.username}:${this.serverName}`; - await this.homeserverServices.room.setRoomTopic(roomIdSchema.parse(room.federation.mrid), userIdSchema.parse(userMui), topic); + await federationSDK.setRoomTopic(roomIdSchema.parse(room.federation.mrid), userIdSchema.parse(userMui), topic); } async addUserRoleRoomScoped( @@ -845,11 +791,6 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS userId: string, role: 'moderator' | 'owner' | 'leader' | 'user', ): Promise { - if (!this.homeserverServices) { - this.logger.warn('Homeserver services not available, skipping user role room scoped'); - return; - } - if (role === 'leader') { throw new Error('Leader role is not supported'); } @@ -878,7 +819,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS } else if (role === 'moderator') { powerLevel = 50; } - await this.homeserverServices.room.setPowerLevelForUser( + await federationSDK.setPowerLevelForUser( roomIdSchema.parse(room.federation.mrid), userIdSchema.parse(senderMui), userIdSchema.parse(userMui), @@ -908,7 +849,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS const userMui = isUserNativeFederated(localUser) ? localUser.federation.mui : `@${localUser.username}:${this.serverName}`; - void this.homeserverServices.edu.sendTypingNotification(room.federation.mrid, userMui, isTyping); + void federationSDK.sendTypingNotification(room.federation.mrid, userMui, isTyping); } async verifyMatrixIds(matrixIds: string[]): Promise<{ [key: string]: string }> { @@ -932,7 +873,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS return [matrixId, 'UNABLE_TO_VERIFY']; } try { - const result = await this.homeserverServices.request.get< + const result = await federationSDK.queryProfileRemote< | { avatar_url: string; displayname: string; @@ -941,7 +882,7 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS errcode: string; error: string; } - >(homeserverUrl, `/_matrix/federation/v1/query/profile`, { user_id: matrixId }); + >({ homeserverUrl, userId }); if ('errcode' in result && result.errcode === 'M_NOT_FOUND') { return [matrixId, 'UNVERIFIED']; @@ -957,21 +898,4 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS return results; } - - async emitJoin(membershipEvent: PduForType<'m.room.member'>, eventId: EventID) { - if (!this.homeserverServices) { - this.logger.warn('Homeserver services not available, skipping user role room scoped'); - return; - } - - this.homeserverServices.emitter.emit('homeserver.matrix.membership', { - event_id: eventId, - event: membershipEvent, - room_id: membershipEvent.room_id, - state_key: membershipEvent.state_key, - content: { membership: 'join' }, - sender: membershipEvent.sender, - origin_server_ts: Date.now(), - }); - } } diff --git a/ee/packages/federation-matrix/src/api/.well-known/server.ts b/ee/packages/federation-matrix/src/api/.well-known/server.ts index c4a8920ff96f3..9bb991ab0d7e9 100644 --- a/ee/packages/federation-matrix/src/api/.well-known/server.ts +++ b/ee/packages/federation-matrix/src/api/.well-known/server.ts @@ -1,46 +1,46 @@ -import { Router } from "@rocket.chat/http-router"; +import { Router } from '@rocket.chat/http-router'; import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv'; import { createHash } from 'node:crypto'; -import type { HomeserverServices } from '@rocket.chat/federation-sdk'; +import { federationSDK } from '@rocket.chat/federation-sdk'; const WellKnownServerResponseSchema = { - type: 'object', - properties: { - 'm.server': { - type: 'string', - description: 'Matrix server address with port' - } - }, - required: ['m.server'] + type: 'object', + properties: { + 'm.server': { + type: 'string', + description: 'Matrix server address with port', + }, + }, + required: ['m.server'], }; const isWellKnownServerResponseProps = ajv.compile(WellKnownServerResponseSchema); // TODO: After changing the domain setting this route is still reporting the old domain until the server is restarted // TODO: this is wrong, is siteurl !== domain this path should return 404. this path is to discover the final address, domain being the "proxy" and siteurl the final destination, if domain is different, well-known should be served there, not here. -export const getWellKnownRoutes = (services: HomeserverServices) => { - const { wellKnown } = services; - - return new Router('/matrix').get('/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, - }; - }); +export const getWellKnownRoutes = () => { + return new Router('/matrix').get( + '/server', + { + response: { + 200: isWellKnownServerResponseProps, + }, + tags: ['Well-Known'], + license: ['federation'], + }, + async (c) => { + const responseData = federationSDK.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, + }; + }, + ); }; diff --git a/ee/packages/federation-matrix/src/api/_matrix/invite.ts b/ee/packages/federation-matrix/src/api/_matrix/invite.ts index 629af5896a6f3..56a0bcd0def39 100644 --- a/ee/packages/federation-matrix/src/api/_matrix/invite.ts +++ b/ee/packages/federation-matrix/src/api/_matrix/invite.ts @@ -1,14 +1,7 @@ -import { FederationMatrix, Room } from '@rocket.chat/core-services'; +import { Room } from '@rocket.chat/core-services'; import { isUserNativeFederated, type IUser } from '@rocket.chat/core-typings'; -import type { - HomeserverServices, - RoomService, - StateService, - PduMembershipEventContent, - PersistentEventBase, - RoomVersion, -} from '@rocket.chat/federation-sdk'; -import { eventIdSchema, roomIdSchema, NotAllowedError } from '@rocket.chat/federation-sdk'; +import type { PduMembershipEventContent, PersistentEventBase, RoomVersion } from '@rocket.chat/federation-sdk'; +import { eventIdSchema, roomIdSchema, NotAllowedError, federationSDK } from '@rocket.chat/federation-sdk'; import { Router } from '@rocket.chat/http-router'; import { Logger } from '@rocket.chat/logger'; import { Rooms, Users } from '@rocket.chat/models'; @@ -157,13 +150,9 @@ async function runWithBackoff(fn: () => Promise, delaySec = 5) { async function joinRoom({ inviteEvent, user, // ours trying to join the room - room, - state, }: { inviteEvent: PersistentEventBase; user: IUser; - room: RoomService; - state: StateService; }) { // from the response we get the event if (!inviteEvent.stateKey) { @@ -171,10 +160,10 @@ async function joinRoom({ } // backoff needed for this call, can fail - await room.joinUser(inviteEvent, inviteEvent.event.state_key); + await federationSDK.joinUser(inviteEvent, inviteEvent.event.state_key); // now we create the room we saved post joining - const matrixRoom = await state.getLatestRoomState2(inviteEvent.roomId); + const matrixRoom = await federationSDK.getLatestRoomState2(inviteEvent.roomId); if (!matrixRoom) { throw new Error('room not found not processing invite'); } @@ -267,10 +256,6 @@ async function joinRoom({ } await Room.addUserToRoom(internalRoomId, { _id: user._id }, { _id: senderUserId, username: inviteEvent.sender }); - - for await (const event of matrixRoom.getMemberJoinEvents()) { - await FederationMatrix.emitJoin(event.event, event.eventId); - } } async function startJoiningRoom(...opts: Parameters) { @@ -278,11 +263,7 @@ async function startJoiningRoom(...opts: Parameters) { } // This is a special case where inside rocket chat we invite users inside rockechat, so if the sender or the invitee are external iw should throw an error -export const acceptInvite = async ( - inviteEvent: PersistentEventBase, - username: string, - services: HomeserverServices, -) => { +export const acceptInvite = async (inviteEvent: PersistentEventBase, username: string) => { if (!inviteEvent.stateKey) { throw new Error('join event has missing state key, unable to determine user to join'); } @@ -292,12 +273,11 @@ export const acceptInvite = async ( throw new Error('room not found not processing invite'); } - const inviter = await Users.findOneByUsername>( - getUsernameServername(inviteEvent.sender, services.config.serverName)[0], - { - projection: { _id: 1, username: 1 }, - }, - ); + const serverName = federationSDK.getConfig('serverName'); + + const inviter = await Users.findOneByUsername>(getUsernameServername(inviteEvent.sender, serverName)[0], { + projection: { _id: 1, username: 1 }, + }); if (!inviter) { throw new Error('Sender user ID not found'); @@ -318,12 +298,10 @@ export const acceptInvite = async ( throw new Error('User is native federated'); } - await services.room.joinUser(inviteEvent, inviteEvent.event.state_key); + await federationSDK.joinUser(inviteEvent, inviteEvent.event.state_key); }; -export const getMatrixInviteRoutes = (services: HomeserverServices) => { - const { invite, state, room, federationAuth } = services; - +export const getMatrixInviteRoutes = () => { const logger = new Logger('matrix-invite'); return new Router('/federation').put( @@ -337,7 +315,7 @@ export const getMatrixInviteRoutes = (services: HomeserverServices) => { tags: ['Federation'], license: ['federation'], }, - isAuthenticatedMiddleware(federationAuth), + isAuthenticatedMiddleware(), async (c) => { const { roomId, eventId } = c.req.param(); const { event, room_version: roomVersion, invite_room_state: strippedStateEvents } = await c.req.json(); @@ -369,7 +347,7 @@ export const getMatrixInviteRoutes = (services: HomeserverServices) => { } try { - const inviteEvent = await invite.processInvite( + const inviteEvent = await federationSDK.processInvite( event, roomIdSchema.parse(roomId), eventIdSchema.parse(eventId), @@ -383,8 +361,6 @@ export const getMatrixInviteRoutes = (services: HomeserverServices) => { void startJoiningRoom({ inviteEvent, user: ourUser, - room, - state, }); }, inviteEvent.event.content.is_direct ? 2000 : 0, diff --git a/ee/packages/federation-matrix/src/api/_matrix/key/server.ts b/ee/packages/federation-matrix/src/api/_matrix/key/server.ts index a2b6ba144151f..89dc236f65606 100644 --- a/ee/packages/federation-matrix/src/api/_matrix/key/server.ts +++ b/ee/packages/federation-matrix/src/api/_matrix/key/server.ts @@ -1,4 +1,4 @@ -import type { HomeserverServices } from '@rocket.chat/federation-sdk'; +import { federationSDK } from '@rocket.chat/federation-sdk'; import { Router } from '@rocket.chat/http-router'; import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv'; @@ -32,9 +32,7 @@ const ServerKeyResponseSchema = { const isServerKeyResponseProps = ajv.compile(ServerKeyResponseSchema); -export const getKeyServerRoutes = (services: HomeserverServices) => { - const { server } = services; - +export const getKeyServerRoutes = () => { return new Router('/key').get( '/v2/server', { @@ -45,7 +43,7 @@ export const getKeyServerRoutes = (services: HomeserverServices) => { license: ['federation'], }, async () => { - const response = await server.getSignedServerKey(); + const response = await federationSDK.getSignedServerKey(); return { body: response, diff --git a/ee/packages/federation-matrix/src/api/_matrix/media.ts b/ee/packages/federation-matrix/src/api/_matrix/media.ts index b357ee1875410..c5eeb88fd3cb9 100644 --- a/ee/packages/federation-matrix/src/api/_matrix/media.ts +++ b/ee/packages/federation-matrix/src/api/_matrix/media.ts @@ -1,7 +1,7 @@ import crypto from 'crypto'; import type { IUpload } from '@rocket.chat/core-typings'; -import type { HomeserverServices } from '@rocket.chat/federation-sdk'; +import { federationSDK } from '@rocket.chat/federation-sdk'; import { Router } from '@rocket.chat/http-router'; import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv'; @@ -73,8 +73,7 @@ async function getMediaFile(mediaId: string, serverName: string): Promise<{ file return { file, buffer }; } -export const getMatrixMediaRoutes = (homeserverServices: HomeserverServices) => { - const { config, federationAuth } = homeserverServices; +export const getMatrixMediaRoutes = () => { return new Router('/federation') .get( '/v1/media/download/:mediaId', @@ -90,11 +89,11 @@ export const getMatrixMediaRoutes = (homeserverServices: HomeserverServices) => }, tags: ['Federation', 'Media'], }, - canAccessResourceMiddleware(federationAuth, 'media'), + canAccessResourceMiddleware('media'), async (c) => { try { const { mediaId } = c.req.param(); - const { serverName } = config; + const serverName = federationSDK.getConfig('serverName'); // TODO: Add file streaming support const result = await getMediaFile(mediaId, serverName); @@ -138,7 +137,7 @@ export const getMatrixMediaRoutes = (homeserverServices: HomeserverServices) => }, tags: ['Federation', 'Media'], }, - canAccessResourceMiddleware(federationAuth, 'media'), + canAccessResourceMiddleware('media'), async (_c) => ({ statusCode: 404, body: { diff --git a/ee/packages/federation-matrix/src/api/_matrix/profiles.ts b/ee/packages/federation-matrix/src/api/_matrix/profiles.ts index f09efc4073bb1..f0338bfc4821a 100644 --- a/ee/packages/federation-matrix/src/api/_matrix/profiles.ts +++ b/ee/packages/federation-matrix/src/api/_matrix/profiles.ts @@ -1,4 +1,4 @@ -import { eventIdSchema, roomIdSchema, userIdSchema, type HomeserverServices, type RoomVersion } from '@rocket.chat/federation-sdk'; +import { eventIdSchema, roomIdSchema, userIdSchema, federationSDK, type RoomVersion } from '@rocket.chat/federation-sdk'; import { Router } from '@rocket.chat/http-router'; import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv'; @@ -352,11 +352,9 @@ const EventAuthResponseSchema = { const isEventAuthResponseProps = ajv.compile(EventAuthResponseSchema); -export const getMatrixProfilesRoutes = (services: HomeserverServices) => { - const { profile, federationAuth } = services; - +export const getMatrixProfilesRoutes = () => { return new Router('/federation') - .use(isAuthenticatedMiddleware(federationAuth)) + .use(isAuthenticatedMiddleware()) .get( '/v1/query/profile', { @@ -370,7 +368,7 @@ export const getMatrixProfilesRoutes = (services: HomeserverServices) => { async (c) => { const { user_id: userId, field } = c.req.query(); - const response = await profile.queryProfile(userId); + const response = await federationSDK.queryProfile(userId); if (field) { return { @@ -400,7 +398,7 @@ export const getMatrixProfilesRoutes = (services: HomeserverServices) => { async (c) => { const body = await c.req.json(); - const response = await profile.queryKeys(body.device_keys); + const response = await federationSDK.queryKeys(body.device_keys); return { body: response, @@ -439,13 +437,13 @@ export const getMatrixProfilesRoutes = (services: HomeserverServices) => { tags: ['Federation'], license: ['federation'], }, - canAccessResourceMiddleware(federationAuth, 'room'), + canAccessResourceMiddleware('room'), async (c) => { const { roomId, userId } = c.req.param(); const url = new URL(c.req.url); const verParams = url.searchParams.getAll('ver'); - const response = await profile.makeJoin( + const response = await federationSDK.makeJoin( roomIdSchema.parse(roomId), userIdSchema.parse(userId), verParams.length > 0 ? (verParams as RoomVersion[]) : ['1'], @@ -471,12 +469,17 @@ export const getMatrixProfilesRoutes = (services: HomeserverServices) => { tags: ['Federation'], license: ['federation'], }, - canAccessResourceMiddleware(federationAuth, 'room'), + canAccessResourceMiddleware('room'), async (c) => { const { roomId } = c.req.param(); const body = await c.req.json(); - const response = await profile.getMissingEvents(roomIdSchema.parse(roomId), body.earliest_events, body.latest_events, body.limit); + const response = await federationSDK.getMissingEvents( + roomIdSchema.parse(roomId), + body.earliest_events, + body.latest_events, + body.limit, + ); return { body: response, @@ -494,11 +497,11 @@ export const getMatrixProfilesRoutes = (services: HomeserverServices) => { tags: ['Federation'], license: ['federation'], }, - canAccessResourceMiddleware(federationAuth, 'room'), + canAccessResourceMiddleware('room'), async (c) => { const { roomId, eventId } = c.req.param(); - const response = await profile.eventAuth(roomIdSchema.parse(roomId), eventIdSchema.parse(eventId)); + const response = await federationSDK.eventAuth(roomIdSchema.parse(roomId), eventIdSchema.parse(eventId)); return { body: response, diff --git a/ee/packages/federation-matrix/src/api/_matrix/rooms.ts b/ee/packages/federation-matrix/src/api/_matrix/rooms.ts index cdb9b146afd10..e8fedb038931f 100644 --- a/ee/packages/federation-matrix/src/api/_matrix/rooms.ts +++ b/ee/packages/federation-matrix/src/api/_matrix/rooms.ts @@ -1,4 +1,4 @@ -import type { HomeserverServices } from '@rocket.chat/federation-sdk'; +import { federationSDK } from '@rocket.chat/federation-sdk'; import { Router } from '@rocket.chat/http-router'; import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv'; @@ -123,11 +123,9 @@ const PublicRoomsPostBodySchema = { const isPublicRoomsPostBodyProps = ajv.compile(PublicRoomsPostBodySchema); -export const getMatrixRoomsRoutes = (services: HomeserverServices) => { - const { state, federationAuth } = services; - +export const getMatrixRoomsRoutes = () => { return new Router('/federation') - .use(isAuthenticatedMiddleware(federationAuth)) + .use(isAuthenticatedMiddleware()) .get( '/v1/publicRooms', { @@ -146,7 +144,7 @@ export const getMatrixRoomsRoutes = (services: HomeserverServices) => { avatar_url: '', // ?? don't have any yet }; - const publicRooms = await state.getAllPublicRoomIdsAndNames(); + const publicRooms = await federationSDK.getAllPublicRoomIdsAndNames(); return { body: { @@ -181,7 +179,7 @@ export const getMatrixRoomsRoutes = (services: HomeserverServices) => { const { filter } = body; - const publicRooms = await state.getAllPublicRoomIdsAndNames(); + const publicRooms = await federationSDK.getAllPublicRoomIdsAndNames(); return { body: { diff --git a/ee/packages/federation-matrix/src/api/_matrix/send-join.ts b/ee/packages/federation-matrix/src/api/_matrix/send-join.ts index 191c070b90754..4e65194498cf7 100644 --- a/ee/packages/federation-matrix/src/api/_matrix/send-join.ts +++ b/ee/packages/federation-matrix/src/api/_matrix/send-join.ts @@ -1,4 +1,5 @@ -import type { HomeserverServices, EventID } from '@rocket.chat/federation-sdk'; +import type { EventID } from '@rocket.chat/federation-sdk'; +import { federationSDK } from '@rocket.chat/federation-sdk'; import { Router } from '@rocket.chat/http-router'; import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv'; @@ -223,9 +224,7 @@ const SendJoinResponseSchema = { // eslint-disable-next-line @typescript-eslint/no-unused-vars const isSendJoinResponseProps = ajv.compile(SendJoinResponseSchema); -export const getMatrixSendJoinRoutes = (services: HomeserverServices) => { - const { sendJoin, federationAuth } = services; - +export const getMatrixSendJoinRoutes = () => { return new Router('/federation').put( '/v2/send_join/:roomId/:stateKey', { @@ -237,12 +236,12 @@ export const getMatrixSendJoinRoutes = (services: HomeserverServices) => { tags: ['Federation'], license: ['federation'], }, - canAccessResourceMiddleware(federationAuth, 'room'), + canAccessResourceMiddleware('room'), async (c) => { const { roomId, stateKey } = c.req.param(); const body = await c.req.json(); - const response = await sendJoin.sendJoin(roomId, stateKey as EventID, body); + const response = await federationSDK.sendJoin(roomId, stateKey as EventID, body); return { body: response, diff --git a/ee/packages/federation-matrix/src/api/_matrix/transactions.ts b/ee/packages/federation-matrix/src/api/_matrix/transactions.ts index 5e12c781797f4..c101ffa10270f 100644 --- a/ee/packages/federation-matrix/src/api/_matrix/transactions.ts +++ b/ee/packages/federation-matrix/src/api/_matrix/transactions.ts @@ -1,4 +1,5 @@ -import type { HomeserverServices, EventID } from '@rocket.chat/federation-sdk'; +import type { EventID } from '@rocket.chat/federation-sdk'; +import { federationSDK } from '@rocket.chat/federation-sdk'; import { Router } from '@rocket.chat/http-router'; import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv'; @@ -314,13 +315,11 @@ const BackfillResponseSchema = { const isBackfillResponseProps = ajv.compile(BackfillResponseSchema); -export const getMatrixTransactionsRoutes = (services: HomeserverServices) => { - const { event, federationAuth } = services; - +export const getMatrixTransactionsRoutes = () => { // PUT /_matrix/federation/v1/send/{txnId} return ( new Router('/federation') - .use(isAuthenticatedMiddleware(federationAuth)) + .use(isAuthenticatedMiddleware()) .put( '/v1/send/:txnId', { @@ -337,7 +336,7 @@ export const getMatrixTransactionsRoutes = (services: HomeserverServices) => { const body = await c.req.json(); try { - await event.processIncomingTransaction(body); + await federationSDK.processIncomingTransaction(body); } catch (error: any) { // TODO custom error types? if (error.message === 'too-many-concurrent-transactions') { @@ -375,7 +374,7 @@ export const getMatrixTransactionsRoutes = (services: HomeserverServices) => { 200: isGetStateIdsResponseProps, }, }, - canAccessResourceMiddleware(federationAuth, 'room'), + canAccessResourceMiddleware('room'), async (c) => { const roomId = c.req.param('roomId'); const eventId = c.req.query('event_id'); @@ -390,7 +389,7 @@ export const getMatrixTransactionsRoutes = (services: HomeserverServices) => { }; } - const stateIds = await event.getStateIds(roomId, eventId as EventID); + const stateIds = await federationSDK.getStateIds(roomId, eventId as EventID); return { body: stateIds, @@ -406,7 +405,7 @@ export const getMatrixTransactionsRoutes = (services: HomeserverServices) => { 200: isGetStateResponseProps, }, }, - canAccessResourceMiddleware(federationAuth, 'room'), + canAccessResourceMiddleware('room'), async (c) => { const roomId = c.req.param('roomId'); const eventId = c.req.query('event_id'); @@ -420,7 +419,7 @@ export const getMatrixTransactionsRoutes = (services: HomeserverServices) => { statusCode: 404, }; } - const state = await event.getState(roomId, eventId as EventID); + const state = await federationSDK.getState(roomId, eventId as EventID); return { statusCode: 200, body: state, @@ -438,9 +437,9 @@ export const getMatrixTransactionsRoutes = (services: HomeserverServices) => { tags: ['Federation'], license: ['federation'], }, - canAccessResourceMiddleware(federationAuth, 'event'), + canAccessResourceMiddleware('event'), async (c) => { - const eventData = await event.getEventById(c.req.param('eventId') as EventID); + const eventData = await federationSDK.getEventById(c.req.param('eventId') as EventID); if (!eventData) { return { body: { @@ -473,7 +472,7 @@ export const getMatrixTransactionsRoutes = (services: HomeserverServices) => { tags: ['Federation'], license: ['federation'], }, - canAccessResourceMiddleware(federationAuth, 'room'), + canAccessResourceMiddleware('room'), async (c) => { const roomId = c.req.param('roomId'); const limit = Number(c.req.query('limit') || 100); @@ -489,7 +488,7 @@ export const getMatrixTransactionsRoutes = (services: HomeserverServices) => { } try { - const result = await event.getBackfillEvents(roomId, eventIds as EventID[], limit); + const result = await federationSDK.getBackfillEvents(roomId, eventIds as EventID[], limit); return { body: result, diff --git a/ee/packages/federation-matrix/src/api/_matrix/versions.ts b/ee/packages/federation-matrix/src/api/_matrix/versions.ts index 684b3c14035aa..5bbb979e921f0 100644 --- a/ee/packages/federation-matrix/src/api/_matrix/versions.ts +++ b/ee/packages/federation-matrix/src/api/_matrix/versions.ts @@ -1,4 +1,4 @@ -import type { HomeserverServices } from '@rocket.chat/federation-sdk'; +import { federationSDK } from '@rocket.chat/federation-sdk'; import { Router } from '@rocket.chat/http-router'; import { ajv } from '@rocket.chat/rest-typings/dist/v1/Ajv'; @@ -25,9 +25,7 @@ const GetVersionsResponseSchema = { const isGetVersionsResponseProps = ajv.compile(GetVersionsResponseSchema); -export const getFederationVersionsRoutes = (services: HomeserverServices) => { - const { config } = services; - +export const getFederationVersionsRoutes = () => { return new Router('/federation').get( '/v1/version', { @@ -38,10 +36,13 @@ export const getFederationVersionsRoutes = (services: HomeserverServices) => { license: ['federation'], }, async () => { + const serverName = federationSDK.getConfig('serverName'); + const version = federationSDK.getConfig('version'); + const response = { server: { - name: config.serverName, - version: config.version, + name: serverName, + version, }, }; diff --git a/ee/packages/federation-matrix/src/api/middlewares/canAccessResource.ts b/ee/packages/federation-matrix/src/api/middlewares/canAccessResource.ts index cc816bc96210e..b1f3e4a0054ac 100644 --- a/ee/packages/federation-matrix/src/api/middlewares/canAccessResource.ts +++ b/ee/packages/federation-matrix/src/api/middlewares/canAccessResource.ts @@ -1,5 +1,4 @@ -import { errCodes } from '@rocket.chat/federation-sdk'; -import type { EventAuthorizationService } from '@rocket.chat/federation-sdk'; +import { errCodes, federationSDK } from '@rocket.chat/federation-sdk'; import { every } from 'hono/combine'; import { createMiddleware } from 'hono/factory'; @@ -24,7 +23,7 @@ function extractEntityId( return null; } -const canAccessResource = (federationAuth: EventAuthorizationService, entityType: 'event' | 'media' | 'room') => +const canAccessResource = (entityType: 'event' | 'media' | 'room') => createMiddleware(async (c, next) => { try { const mediaId = c.req.param('mediaId'); @@ -36,7 +35,7 @@ const canAccessResource = (federationAuth: EventAuthorizationService, entityType return c.json({ errcode: 'M_INVALID_PARAM', error: `Missing required ${entityType} identifier` }, 400); } - const resourceAccess = await federationAuth.canAccessResource(entityType, resourceId, c.get('authenticatedServer')); + const resourceAccess = await federationSDK.canAccessResource(entityType, resourceId, c.get('authenticatedServer')); if (!resourceAccess) { return c.json( { @@ -53,5 +52,5 @@ const canAccessResource = (federationAuth: EventAuthorizationService, entityType } }); -export const canAccessResourceMiddleware = (federationAuth: EventAuthorizationService, entityType: 'event' | 'media' | 'room') => - every(isAuthenticatedMiddleware(federationAuth), canAccessResource(federationAuth, entityType)); +export const canAccessResourceMiddleware = (entityType: 'event' | 'media' | 'room') => + every(isAuthenticatedMiddleware(), canAccessResource(entityType)); diff --git a/ee/packages/federation-matrix/src/api/middlewares/isAuthenticated.ts b/ee/packages/federation-matrix/src/api/middlewares/isAuthenticated.ts index e69ca5c758f82..0b3dd1115d28f 100644 --- a/ee/packages/federation-matrix/src/api/middlewares/isAuthenticated.ts +++ b/ee/packages/federation-matrix/src/api/middlewares/isAuthenticated.ts @@ -1,9 +1,8 @@ -import { errCodes } from '@rocket.chat/federation-sdk'; -import type { EventAuthorizationService } from '@rocket.chat/federation-sdk'; +import { errCodes, federationSDK } from '@rocket.chat/federation-sdk'; import type { Context } from 'hono'; import { createMiddleware } from 'hono/factory'; -export const isAuthenticatedMiddleware = (federationAuth: EventAuthorizationService) => +export const isAuthenticatedMiddleware = () => createMiddleware(async (c: Context, next) => { try { const { method } = c.req; @@ -21,7 +20,7 @@ export const isAuthenticatedMiddleware = (federationAuth: EventAuthorizationServ ); } - const verificationResult = await federationAuth.verifyRequestSignature(authHeader, method, path, body); + const verificationResult = await federationSDK.verifyRequestSignature(authHeader, method, path, body); if (!verificationResult) { return c.json( { diff --git a/ee/packages/federation-matrix/src/api/middlewares/isFederationEnabled.ts b/ee/packages/federation-matrix/src/api/middlewares/isFederationEnabled.ts index 692130bbdc938..e75c3b4047f33 100644 --- a/ee/packages/federation-matrix/src/api/middlewares/isFederationEnabled.ts +++ b/ee/packages/federation-matrix/src/api/middlewares/isFederationEnabled.ts @@ -2,6 +2,7 @@ import { Settings } from '@rocket.chat/core-services'; import { createMiddleware } from 'hono/factory'; export const isFederationEnabledMiddleware = createMiddleware(async (c, next) => { + // TODO use federationSDK to check if federation is enabled if (!(await Settings.get('Federation_Service_Enabled'))) { return c.json({ error: 'Federation is not enabled' }, 403); } diff --git a/ee/packages/federation-matrix/src/api/routes.ts b/ee/packages/federation-matrix/src/api/routes.ts index 96274745bbe6a..ee53fe21eacb0 100644 --- a/ee/packages/federation-matrix/src/api/routes.ts +++ b/ee/packages/federation-matrix/src/api/routes.ts @@ -1,4 +1,3 @@ -import { getAllServices } from '@rocket.chat/federation-sdk'; import { Router } from '@rocket.chat/http-router'; import { getWellKnownRoutes } from './.well-known/server'; @@ -15,25 +14,23 @@ import { isFederationEnabledMiddleware } from './middlewares/isFederationEnabled import { isLicenseEnabledMiddleware } from './middlewares/isLicenseEnabled'; export const getFederationRoutes = (): { matrix: Router<'/_matrix'>; wellKnown: Router<'/.well-known'> } => { - const homeserverServices = getAllServices(); - const matrix = new Router('/_matrix'); const wellKnown = new Router('/.well-known'); matrix .use(isFederationEnabledMiddleware) .use(isLicenseEnabledMiddleware) - .use(getKeyServerRoutes(homeserverServices)) - .use(getFederationVersionsRoutes(homeserverServices)) + .use(getKeyServerRoutes()) + .use(getFederationVersionsRoutes()) .use(isFederationDomainAllowedMiddleware) - .use(getMatrixInviteRoutes(homeserverServices)) - .use(getMatrixProfilesRoutes(homeserverServices)) - .use(getMatrixRoomsRoutes(homeserverServices)) - .use(getMatrixSendJoinRoutes(homeserverServices)) - .use(getMatrixTransactionsRoutes(homeserverServices)) - .use(getMatrixMediaRoutes(homeserverServices)); + .use(getMatrixInviteRoutes()) + .use(getMatrixProfilesRoutes()) + .use(getMatrixRoomsRoutes()) + .use(getMatrixSendJoinRoutes()) + .use(getMatrixTransactionsRoutes()) + .use(getMatrixMediaRoutes()); - wellKnown.use(isFederationEnabledMiddleware).use(isLicenseEnabledMiddleware).use(getWellKnownRoutes(homeserverServices)); + wellKnown.use(isFederationEnabledMiddleware).use(isLicenseEnabledMiddleware).use(getWellKnownRoutes()); return { matrix, wellKnown }; }; diff --git a/ee/packages/federation-matrix/src/events/edu.ts b/ee/packages/federation-matrix/src/events/edu.ts index cdf1354e3fcbb..627e6c04796a0 100644 --- a/ee/packages/federation-matrix/src/events/edu.ts +++ b/ee/packages/federation-matrix/src/events/edu.ts @@ -1,15 +1,16 @@ import { api } from '@rocket.chat/core-services'; import { UserStatus } from '@rocket.chat/core-typings'; import type { Emitter } from '@rocket.chat/emitter'; -import type { HomeserverEventSignatures } from '@rocket.chat/federation-sdk'; +import { federationSDK, type HomeserverEventSignatures } from '@rocket.chat/federation-sdk'; import { Logger } from '@rocket.chat/logger'; import { Rooms, Users } from '@rocket.chat/models'; const logger = new Logger('federation-matrix:edu'); -export const edus = async (emitter: Emitter, eduProcessTypes: { typing: boolean; presence: boolean }) => { +export const edus = async (emitter: Emitter) => { emitter.on('homeserver.matrix.typing', async (data) => { - if (!eduProcessTypes.typing) { + const config = federationSDK.getConfig('edu'); + if (!config.processTyping) { return; } @@ -31,7 +32,8 @@ export const edus = async (emitter: Emitter, eduProce }); emitter.on('homeserver.matrix.presence', async (data) => { - if (!eduProcessTypes.presence) { + const config = federationSDK.getConfig('edu'); + if (!config.processPresence) { return; } diff --git a/ee/packages/federation-matrix/src/events/index.ts b/ee/packages/federation-matrix/src/events/index.ts index bbe04a7fd66d5..2487c388cb194 100644 --- a/ee/packages/federation-matrix/src/events/index.ts +++ b/ee/packages/federation-matrix/src/events/index.ts @@ -1,5 +1,5 @@ import type { Emitter } from '@rocket.chat/emitter'; -import { getAllServices, type HomeserverEventSignatures, type HomeserverServices } from '@rocket.chat/federation-sdk'; +import { type HomeserverEventSignatures } from '@rocket.chat/federation-sdk'; import { edus } from './edu'; import { member } from './member'; @@ -8,16 +8,11 @@ import { ping } from './ping'; import { reaction } from './reaction'; import { room } from './room'; -export function registerEvents( - emitter: Emitter, - serverName: string, - eduProcessTypes: { typing: boolean; presence: boolean }, - services: HomeserverServices = getAllServices(), -) { +export function registerEvents(emitter: Emitter) { ping(emitter); - message(emitter, serverName); + message(emitter); reaction(emitter); - member(emitter, services); - edus(emitter, eduProcessTypes); - room(emitter, services); + member(emitter); + edus(emitter); + room(emitter); } diff --git a/ee/packages/federation-matrix/src/events/member.ts b/ee/packages/federation-matrix/src/events/member.ts index cdd2f4cfe628b..704b1b22e0b4a 100644 --- a/ee/packages/federation-matrix/src/events/member.ts +++ b/ee/packages/federation-matrix/src/events/member.ts @@ -1,6 +1,6 @@ import { Room } from '@rocket.chat/core-services'; import type { Emitter } from '@rocket.chat/emitter'; -import type { HomeserverEventSignatures, HomeserverServices } from '@rocket.chat/federation-sdk'; +import { federationSDK, type HomeserverEventSignatures } from '@rocket.chat/federation-sdk'; import { Logger } from '@rocket.chat/logger'; import { Rooms, Subscriptions, Users } from '@rocket.chat/models'; @@ -8,14 +8,16 @@ import { createOrUpdateFederatedUser, getUsernameServername } from '../Federatio const logger = new Logger('federation-matrix:member'); -async function membershipLeaveAction(data: HomeserverEventSignatures['homeserver.matrix.membership'], services: HomeserverServices) { +async function membershipLeaveAction(data: HomeserverEventSignatures['homeserver.matrix.membership']) { const room = await Rooms.findOne({ 'federation.mrid': data.room_id }, { projection: { _id: 1 } }); if (!room) { logger.warn(`No bridged room found for Matrix room_id: ${data.room_id}`); return; } - const [affectedUsername] = getUsernameServername(data.state_key, services.config.serverName); + const serverName = federationSDK.getConfig('serverName'); + + const [affectedUsername] = getUsernameServername(data.state_key, serverName); // state_key is the user affected by the membership change const affectedUser = await Users.findOneByUsername(affectedUsername); if (!affectedUser) { @@ -31,7 +33,7 @@ async function membershipLeaveAction(data: HomeserverEventSignatures['homeserver } else { // Kick - find who kicked - const [kickerUsername] = getUsernameServername(data.sender, services.config.serverName); + const [kickerUsername] = getUsernameServername(data.sender, serverName); const kickerUser = await Users.findOneByUsername(kickerUsername); await Room.removeUserFromRoom(room._id, affectedUser, { @@ -43,14 +45,14 @@ async function membershipLeaveAction(data: HomeserverEventSignatures['homeserver } } -async function membershipJoinAction(data: HomeserverEventSignatures['homeserver.matrix.membership'], services: HomeserverServices) { +async function membershipJoinAction(data: HomeserverEventSignatures['homeserver.matrix.membership']) { const room = await Rooms.findOne({ 'federation.mrid': data.room_id }); if (!room) { logger.warn(`No bridged room found for room_id: ${data.room_id}`); return; } - const [username, serverName, isLocal] = getUsernameServername(data.sender, services.config.serverName); + const [username, serverName, isLocal] = getUsernameServername(data.sender, federationSDK.getConfig('serverName')); // for local users we must to remove the @ and the server domain const localUser = isLocal && (await Users.findOneByUsername(username)); @@ -83,15 +85,15 @@ async function membershipJoinAction(data: HomeserverEventSignatures['homeserver. await Room.addUserToRoom(room._id, user); } -export function member(emitter: Emitter, services: HomeserverServices) { +export function member(emitter: Emitter) { emitter.on('homeserver.matrix.membership', async (data) => { try { if (data.content.membership === 'leave') { - return membershipLeaveAction(data, services); + return membershipLeaveAction(data); } if (data.content.membership === 'join') { - return membershipJoinAction(data, services); + return membershipJoinAction(data); } logger.debug(`Ignoring membership event with membership: ${data.content.membership}`); diff --git a/ee/packages/federation-matrix/src/events/message.ts b/ee/packages/federation-matrix/src/events/message.ts index cd38148010ba3..591ca6043de79 100644 --- a/ee/packages/federation-matrix/src/events/message.ts +++ b/ee/packages/federation-matrix/src/events/message.ts @@ -1,7 +1,14 @@ import { FederationMatrix, Message, MeteorService } from '@rocket.chat/core-services'; import type { IUser, IRoom, FileAttachmentProps } from '@rocket.chat/core-typings'; import type { Emitter } from '@rocket.chat/emitter'; -import type { FileMessageType, MessageType, FileMessageContent, HomeserverEventSignatures, EventID } from '@rocket.chat/federation-sdk'; +import { + type FileMessageType, + type MessageType, + type FileMessageContent, + type HomeserverEventSignatures, + type EventID, + federationSDK, +} from '@rocket.chat/federation-sdk'; import { Logger } from '@rocket.chat/logger'; import { Users, Rooms, Messages } from '@rocket.chat/models'; @@ -111,7 +118,7 @@ async function handleMediaMessage( }; } -export function message(emitter: Emitter, serverName: string) { +export function message(emitter: Emitter) { emitter.on('homeserver.matrix.message', async (data) => { try { const { content } = data; @@ -134,6 +141,8 @@ export function message(emitter: Emitter, serverName: throw new Error(`No mapped room found for room_id: ${data.room_id}`); } + const serverName = federationSDK.getConfig('serverName'); + const relation = content['m.relates_to']; // SPEC: For example, an m.thread relationship type denotes that the event is part of a “thread” of messages and should be rendered as such. diff --git a/ee/packages/federation-matrix/src/events/room.ts b/ee/packages/federation-matrix/src/events/room.ts index 369bd8193235e..b43874d42cfa7 100644 --- a/ee/packages/federation-matrix/src/events/room.ts +++ b/ee/packages/federation-matrix/src/events/room.ts @@ -1,11 +1,11 @@ import { Room } from '@rocket.chat/core-services'; import type { Emitter } from '@rocket.chat/emitter'; -import type { HomeserverEventSignatures, HomeserverServices } from '@rocket.chat/federation-sdk'; +import { federationSDK, type HomeserverEventSignatures } from '@rocket.chat/federation-sdk'; import { Rooms, Users } from '@rocket.chat/models'; import { getUsernameServername } from '../FederationMatrix'; -export function room(emitter: Emitter, services: HomeserverServices) { +export function room(emitter: Emitter) { emitter.on('homeserver.matrix.room.name', async (data) => { const { room_id: roomId, name, user_id: userId } = data; @@ -51,7 +51,9 @@ export function room(emitter: Emitter, services: Home throw new Error('mapped room not found'); } - const [allegedUsernameLocal, , allegedUserLocalIsLocal] = getUsernameServername(userId, services.config.serverName); + const serverName = federationSDK.getConfig('serverName'); + + const [allegedUsernameLocal, , allegedUserLocalIsLocal] = getUsernameServername(userId, serverName); const localUserId = allegedUserLocalIsLocal && (await Users.findOneByUsername(allegedUsernameLocal, { projection: { _id: 1 } })); if (!allegedUserLocalIsLocal) { @@ -62,7 +64,7 @@ export function room(emitter: Emitter, services: Home throw new Error('mapped user not found'); } - const [senderUsername, , senderIsLocal] = getUsernameServername(senderId, services.config.serverName); + const [senderUsername, , senderIsLocal] = getUsernameServername(senderId, serverName); if (senderIsLocal) { return; diff --git a/ee/packages/federation-matrix/src/index.ts b/ee/packages/federation-matrix/src/index.ts index 46fda94431ca4..e7777aabbad28 100644 --- a/ee/packages/federation-matrix/src/index.ts +++ b/ee/packages/federation-matrix/src/index.ts @@ -6,4 +6,4 @@ export { generateEd25519RandomSecretKey } from '@rocket.chat/federation-sdk'; export { getFederationRoutes } from './api/routes'; -export { setupFederationMatrix } from './setup'; +export { setupFederationMatrix, configureFederationMatrixSettings } from './setup'; diff --git a/ee/packages/federation-matrix/src/services/MatrixMediaService.ts b/ee/packages/federation-matrix/src/services/MatrixMediaService.ts index e19273f9e9a50..3b8bfa2c4e66d 100644 --- a/ee/packages/federation-matrix/src/services/MatrixMediaService.ts +++ b/ee/packages/federation-matrix/src/services/MatrixMediaService.ts @@ -1,6 +1,6 @@ import { Upload } from '@rocket.chat/core-services'; import type { IUpload } from '@rocket.chat/core-typings'; -import type { HomeserverServices } from '@rocket.chat/federation-sdk'; +import { federationSDK } from '@rocket.chat/federation-sdk'; import { Logger } from '@rocket.chat/logger'; import { Uploads } from '@rocket.chat/models'; @@ -16,12 +16,6 @@ export interface IRemoteFileReference { } export class MatrixMediaService { - private static homeserverServices: HomeserverServices; - - static setHomeserverServices(services: HomeserverServices): void { - this.homeserverServices = services; - } - static generateMXCUri(fileId: string, serverName: string): string { return `mxc://${serverName}/${fileId}`; } @@ -109,11 +103,7 @@ export class MatrixMediaService { return uploadAlreadyExists._id; } - if (!this.homeserverServices) { - throw new Error('Homeserver services not initialized. Call setHomeserverServices first.'); - } - - const buffer = await this.homeserverServices.media.downloadFromRemoteServer(parts.serverName, parts.mediaId); + const buffer = await federationSDK.downloadFromRemoteServer(parts.serverName, parts.mediaId); if (!buffer) { throw new Error('Download from remote server returned null content.'); } diff --git a/ee/packages/federation-matrix/src/setup.ts b/ee/packages/federation-matrix/src/setup.ts index 8f82e3062272c..eeb4003c7a3cd 100644 --- a/ee/packages/federation-matrix/src/setup.ts +++ b/ee/packages/federation-matrix/src/setup.ts @@ -1,9 +1,7 @@ -import { License } from '@rocket.chat/core-services'; import { Emitter } from '@rocket.chat/emitter'; import type { HomeserverEventSignatures } from '@rocket.chat/federation-sdk'; -import { ConfigService, createFederationContainer } from '@rocket.chat/federation-sdk'; +import { federationSDK, init } from '@rocket.chat/federation-sdk'; import { Logger } from '@rocket.chat/logger'; -import { Settings } from '@rocket.chat/models'; import { registerEvents } from './events'; @@ -36,23 +34,34 @@ function validateDomain(domain: string): boolean { return true; } -export async function setupFederationMatrix(instanceId: string): Promise { - const settingEnabled = (await Settings.getValueById('Federation_Service_Enabled')) || false; - const serverName = (await Settings.getValueById('Federation_Service_Domain')) || ''; - - const processEDUTyping = (await Settings.getValueById('Federation_Service_EDU_Process_Typing')) || false; - const processEDUPresence = (await Settings.getValueById('Federation_Service_EDU_Process_Presence')) || false; - const signingKey = (await Settings.getValueById('Federation_Service_Matrix_Signing_Key')) || ''; - const signingAlg = (await Settings.getValueById('Federation_Service_Matrix_Signing_Algorithm')) || ''; - const signingVersion = (await Settings.getValueById('Federation_Service_Matrix_Signing_Version')) || ''; - const allowedEncryptedRooms = (await Settings.getValueById('Federation_Service_Join_Encrypted_Rooms')) || false; - const allowedNonPrivateRooms = (await Settings.getValueById('Federation_Service_Join_Non_Private_Rooms')) || false; - - // TODO are these required? - const mongoUri = process.env.MONGO_URL || 'mongodb://localhost:3001/meteor'; - const dbName = process.env.DATABASE_NAME || new URL(mongoUri).pathname.slice(1); +export function configureFederationMatrixSettings(settings: { + instanceId: string; + domain: string; + signingKey: string; + signingAlgorithm: string; + signingVersion: string; + allowedEncryptedRooms: boolean; + allowedNonPrivateRooms: boolean; + processEDUTyping: boolean; + processEDUPresence: boolean; +}) { + const { + instanceId, + domain: serverName, + signingKey, + signingAlgorithm: signingAlg, + signingVersion, + allowedEncryptedRooms, + allowedNonPrivateRooms, + processEDUTyping, + processEDUPresence, + } = settings; + + if (!validateDomain(serverName)) { + throw new Error('Invalid Federation domain'); + } - const config = new ConfigService({ + federationSDK.setConfig({ instanceId, serverName, keyRefreshInterval: Number.parseInt(process.env.MATRIX_KEY_REFRESH_INTERVAL || '60', 10), @@ -61,11 +70,6 @@ export async function setupFederationMatrix(instanceId: string): Promise(); - await createFederationContainer( - { - emitter: eventHandler, + await init({ + emitter: eventHandler, + dbConfig: { + uri: mongoUri, + name: dbName, + poolSize: Number.parseInt(process.env.DATABASE_POOL_SIZE || '10', 10), }, - config, - ); - - const serviceEnabled = (await License.hasModule('federation')) && settingEnabled && validateDomain(serverName); - if (!serviceEnabled) { - return false; - } - - registerEvents(eventHandler, serverName, { - typing: processEDUTyping, - presence: processEDUPresence, }); - return true; + registerEvents(eventHandler); } diff --git a/packages/core-services/package.json b/packages/core-services/package.json index aea3e7477d9c0..72f9f55aa0c7b 100644 --- a/packages/core-services/package.json +++ b/packages/core-services/package.json @@ -35,7 +35,7 @@ }, "dependencies": { "@rocket.chat/core-typings": "workspace:^", - "@rocket.chat/federation-sdk": "0.2.0", + "@rocket.chat/federation-sdk": "0.3.0", "@rocket.chat/http-router": "workspace:^", "@rocket.chat/icons": "~0.44.0", "@rocket.chat/media-signaling": "workspace:^", diff --git a/packages/core-services/src/types/IFederationMatrixService.ts b/packages/core-services/src/types/IFederationMatrixService.ts index 817b8973f508b..1d036069a8d86 100644 --- a/packages/core-services/src/types/IFederationMatrixService.ts +++ b/packages/core-services/src/types/IFederationMatrixService.ts @@ -1,5 +1,5 @@ import type { IMessage, IRoomFederated, IRoomNativeFederated, IUser } from '@rocket.chat/core-typings'; -import type { EventID, PduForType, EventStore } from '@rocket.chat/federation-sdk'; +import type { EventStore } from '@rocket.chat/federation-sdk'; export interface IFederationMatrixService { createRoom(room: IRoomFederated, owner: IUser, members: string[]): Promise<{ room_id: string; event_id: string }>; @@ -28,5 +28,4 @@ export interface IFederationMatrixService { inviteUsersToRoom(room: IRoomFederated, usersUserName: string[], inviter: IUser): Promise; notifyUserTyping(rid: string, user: string, isTyping: boolean): Promise; verifyMatrixIds(matrixIds: string[]): Promise<{ [key: string]: string }>; - emitJoin(membershipEvent: PduForType<'m.room.member'>, eventId: EventID): Promise; } diff --git a/yarn.lock b/yarn.lock index c7203c6d304d6..0d6298410eae1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8199,7 +8199,7 @@ __metadata: "@rocket.chat/apps-engine": "workspace:^" "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/federation-sdk": "npm:0.2.0" + "@rocket.chat/federation-sdk": "npm:0.3.0" "@rocket.chat/http-router": "workspace:^" "@rocket.chat/icons": "npm:~0.44.0" "@rocket.chat/jest-presets": "workspace:~" @@ -8410,7 +8410,7 @@ __metadata: "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/emitter": "npm:^0.31.25" "@rocket.chat/eslint-config": "workspace:^" - "@rocket.chat/federation-sdk": "npm:0.2.0" + "@rocket.chat/federation-sdk": "npm:0.3.0" "@rocket.chat/http-router": "workspace:^" "@rocket.chat/license": "workspace:^" "@rocket.chat/models": "workspace:^" @@ -8425,7 +8425,7 @@ __metadata: jest: "npm:~30.2.0" marked: "npm:^16.1.2" mongodb: "npm:6.16.0" - pino: "npm:^9.11.0" + pino: "npm:^8.21.0" pino-pretty: "npm:^7.6.1" reflect-metadata: "npm:^0.2.2" sanitize-html: "npm:~2.17.0" @@ -8435,22 +8435,22 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/federation-sdk@npm:0.2.0": - version: 0.2.0 - resolution: "@rocket.chat/federation-sdk@npm:0.2.0" +"@rocket.chat/federation-sdk@npm:0.3.0": + version: 0.3.0 + resolution: "@rocket.chat/federation-sdk@npm:0.3.0" dependencies: "@datastructures-js/priority-queue": "npm:^6.3.3" "@noble/ed25519": "npm:^3.0.0" "@rocket.chat/emitter": "npm:^0.31.25" mongodb: "npm:^6.16.0" - pino: "npm:^9.11.0" + pino: "npm:^8.21.0" reflect-metadata: "npm:^0.2.2" tsyringe: "npm:^4.10.0" tweetnacl: "npm:^1.0.3" - zod: "npm:^3.22.4" + zod: "npm:^3.24.1" peerDependencies: typescript: ~5.9.2 - checksum: 10/94040e8abb2973658c8c62bf778cce7175735f1ae22b3d2e9393204dea808f9ef5364b01bffd9459880bbe560ed8bff2db159e33a6805f1dee11afad815d21c5 + checksum: 10/98cfc09d337855a5ecde98e80e480c10ae7cb76602054fef7202ef3e058fbd00cb9ff5751cb4199ea9e18d08984c59af4309987f95e1413d634952f08c5f886d languageName: node linkType: hard @@ -9121,7 +9121,7 @@ __metadata: "@rocket.chat/eslint-config": "workspace:^" "@rocket.chat/favicon": "workspace:^" "@rocket.chat/federation-matrix": "workspace:^" - "@rocket.chat/federation-sdk": "npm:0.2.0" + "@rocket.chat/federation-sdk": "npm:0.3.0" "@rocket.chat/freeswitch": "workspace:^" "@rocket.chat/fuselage": "npm:~0.66.4" "@rocket.chat/fuselage-forms": "npm:~0.1.0" @@ -10628,7 +10628,7 @@ __metadata: peerDependencies: "@rocket.chat/layout": "*" "@rocket.chat/tools": 0.2.3 - "@rocket.chat/ui-contexts": 24.0.0-rc.4 + "@rocket.chat/ui-contexts": 24.0.0 "@tanstack/react-query": "*" react: "*" react-hook-form: "*" @@ -30911,15 +30911,6 @@ __metadata: languageName: node linkType: hard -"pino-abstract-transport@npm:^2.0.0": - version: 2.0.0 - resolution: "pino-abstract-transport@npm:2.0.0" - dependencies: - split2: "npm:^4.0.0" - checksum: 10/e5699ecb06c7121055978e988e5cecea5b6892fc2589c64f1f86df5e7386bbbfd2ada268839e911b021c6b3123428aed7c6be3ac7940eee139556c75324c7e83 - languageName: node - linkType: hard - "pino-pretty@npm:^7.6.1": version: 7.6.1 resolution: "pino-pretty@npm:7.6.1" @@ -30950,13 +30941,6 @@ __metadata: languageName: node linkType: hard -"pino-std-serializers@npm:^7.0.0": - version: 7.0.0 - resolution: "pino-std-serializers@npm:7.0.0" - checksum: 10/884e08f65aa5463d820521ead3779d4472c78fc434d8582afb66f9dcb8d8c7119c69524b68106cb8caf92c0487be7794cf50e5b9c0383ae65b24bf2a03480951 - languageName: node - linkType: hard - "pino@npm:^8.21.0": version: 8.21.0 resolution: "pino@npm:8.21.0" @@ -30978,27 +30962,6 @@ __metadata: languageName: node linkType: hard -"pino@npm:^9.11.0": - version: 9.11.0 - resolution: "pino@npm:9.11.0" - dependencies: - atomic-sleep: "npm:^1.0.0" - fast-redact: "npm:^3.1.1" - on-exit-leak-free: "npm:^2.1.0" - pino-abstract-transport: "npm:^2.0.0" - pino-std-serializers: "npm:^7.0.0" - process-warning: "npm:^5.0.0" - quick-format-unescaped: "npm:^4.0.3" - real-require: "npm:^0.2.0" - safe-stable-stringify: "npm:^2.3.1" - sonic-boom: "npm:^4.0.1" - thread-stream: "npm:^3.0.0" - bin: - pino: bin.js - checksum: 10/359bc3624110a0261a5dc5fc3f990028920a8165d173bd5304b328da3ed9eb1281d233c2acfb1a263282fed0aa1a1e1d5f2f66e856fcb56926836458610e78bc - languageName: node - linkType: hard - "pirates@npm:^4.0.4, pirates@npm:^4.0.6, pirates@npm:^4.0.7": version: 4.0.7 resolution: "pirates@npm:4.0.7" @@ -32033,13 +31996,6 @@ __metadata: languageName: node linkType: hard -"process-warning@npm:^5.0.0": - version: 5.0.0 - resolution: "process-warning@npm:5.0.0" - checksum: 10/10f3e00ac9fc1943ec4566ff41fff2b964e660f853c283e622257719839d340b4616e707d62a02d6aa0038761bb1fa7c56bc7308d602d51bd96f05f9cd305dcd - languageName: node - linkType: hard - "process@npm:^0.10.0": version: 0.10.1 resolution: "process@npm:0.10.1" @@ -34913,15 +34869,6 @@ __metadata: languageName: node linkType: hard -"sonic-boom@npm:^4.0.1": - version: 4.2.0 - resolution: "sonic-boom@npm:4.2.0" - dependencies: - atomic-sleep: "npm:^1.0.0" - checksum: 10/385ef7fb5ea5976c1d2a1fef0b6df8df6b7caba8696d2d67f689d60c05e3ea2d536752ce7e1c69b9fad844635f1036d07c446f8e8149f5c6a80e0040a455b310 - languageName: node - linkType: hard - "sort-keys-length@npm:^1.0.0": version: 1.0.1 resolution: "sort-keys-length@npm:1.0.1" @@ -36419,15 +36366,6 @@ __metadata: languageName: node linkType: hard -"thread-stream@npm:^3.0.0": - version: 3.1.0 - resolution: "thread-stream@npm:3.1.0" - dependencies: - real-require: "npm:^0.2.0" - checksum: 10/ea2d816c4f6077a7062fac5414a88e82977f807c82ee330938fb9691fe11883bb03f078551c0518bb649c239e47ba113d44014fcbb5db42c5abd5996f35e4213 - languageName: node - linkType: hard - "thriftrw@npm:^3.5.0": version: 3.12.0 resolution: "thriftrw@npm:3.12.0" @@ -39140,13 +39078,6 @@ __metadata: languageName: node linkType: hard -"zod@npm:^3.22.4": - version: 3.25.76 - resolution: "zod@npm:3.25.76" - checksum: 10/f0c963ec40cd96858451d1690404d603d36507c1fc9682f2dae59ab38b578687d542708a7fdbf645f77926f78c9ed558f57c3d3aa226c285f798df0c4da16995 - languageName: node - linkType: hard - "zod@npm:^3.24.1": version: 3.24.1 resolution: "zod@npm:3.24.1"