Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bump-patch-1710772454766.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Bump @rocket.chat/meteor version.
5 changes: 5 additions & 0 deletions .changeset/cyan-countries-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Fixed matrix homeserver domain setting not being visible in admin panel
5 changes: 5 additions & 0 deletions .changeset/moody-ghosts-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Don't use the registration.yaml file to configure Matrix Federation anymore.
6 changes: 6 additions & 0 deletions .changeset/slimy-clocks-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/core-services": patch
---

`stopped` lifecycle method was unexpectedly synchronous when using microservices, causing our code to create race conditions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ yarn-error.log*
.envrc

*.sublime-workspace

**/.vim/
4 changes: 2 additions & 2 deletions apps/meteor/app/search/server/search.internalService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ class Search extends ServiceClassInternal {

const service = new Search();

settings.watch('Search.Provider', () => {
settings.watch('Search.Provider', async () => {
if (searchProviderService.activeProvider?.on) {
api.registerService(service);
} else {
api.destroyService(service);
await api.destroyService(service);
}
});
4 changes: 2 additions & 2 deletions apps/meteor/ee/server/NetworkBroker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ export class NetworkBroker implements IBroker {
return this.broker.call(method, data);
}

destroyService(instance: IServiceClass): void {
async destroyService(instance: IServiceClass): Promise<void> {
const name = instance.getName();
if (!name) {
return;
}
void this.broker.destroyService(name);
await this.broker.destroyService(name);
instance.removeAllListeners();
}

Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/ee/server/startup/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ if (!License.hasValidLicense()) {
void License.onLicense('federation', async () => {
const federationServiceEE = await FederationServiceEE.createFederationService();
if (federationService) {
api.destroyService(federationService);
await api.destroyService(federationService);
}
api.registerService(federationServiceEE);
});
39 changes: 39 additions & 0 deletions apps/meteor/ee/tests/unit/server/NetworkBroker.tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ServiceClass } from '@rocket.chat/core-services';
import { expect } from 'chai';
import sinon from 'sinon';

import { BrokerMocked } from '../../../../tests/mocks/server/BrokerMocked';
import { NetworkBroker } from '../../../server/NetworkBroker';

class DelayedStopBroker extends BrokerMocked {
async destroyService(name: string) {
const instance = this.services.get(name);

await new Promise((resolve) => setTimeout(resolve, 1000));

await instance.stopped();

await super.destroyService(name);
}
}

const broker = new NetworkBroker(new DelayedStopBroker() as any);

describe('NetworkBroker', () => {
it('should wait services to be fully destroyed', async () => {
const stoppedStub = sinon.stub();

const instance = new (class extends ServiceClass {
name = 'test';

async stopped() {
stoppedStub();
}
})();

broker.createService(instance);
await broker.destroyService(instance);

expect(stoppedStub.called).to.be.true;
});
});
2 changes: 2 additions & 0 deletions apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -2223,6 +2223,8 @@
"Federation_Description": "Federation allows an unlimited number of workspaces to communicate with each other.",
"Federation_Enable": "Enable Federation",
"Federation_Example_matrix_server": "Example: matrix.org",
"Federation_Matrix_enable_ephemeral_events": "Enable Matrix ephemeral events",
"Federation_Matrix_enable_ephemeral_events_Alert": "This requires a restart. </br> Enabling ephemeral events like user typing indicator can affect the performance of your Matrix Homeserver and Rocket.Chat server for federated communication",
"Federation_Federated_room_search": "Federated room search",
"Federation_Public_key": "Public Key",
"Federation_Search_federated_rooms": "Search federated rooms",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ export class MatrixBridge implements IFederationBridge {
// Dynamic import to prevent Rocket.Chat from loading the module until needed and then handle if that fails
const { Bridge, AppServiceRegistration, MatrixUser } = await import('@rocket.chat/forked-matrix-appservice-bridge');
MatrixUserInstance = MatrixUser;
const registrationFile = this.internalSettings.generateRegistrationFileObject();
const registrationFile = this.internalSettings.getAppServiceRegistrationObject();

this.bridgeInstance = new Bridge({
homeserverUrl: this.internalSettings.getHomeServerUrl(),
Expand All @@ -730,7 +730,7 @@ export class MatrixBridge implements IFederationBridge {
onLog: (line, isError) => {
console.log(line, isError);
},
...(this.internalSettings.generateRegistrationFileObject().enableEphemeralEvents
...(this.internalSettings.getAppServiceRegistrationObject().enableEphemeralEvents
? {
onEphemeralEvent: (request) => {
const event = request.getData() as unknown as AbstractMatrixEvent;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import crypto from 'crypto';
import fs from 'fs';
import { resolve } from 'path';

import { Settings } from '@rocket.chat/models';
import yaml from 'js-yaml';
Expand All @@ -16,7 +14,6 @@ export class RocketChatSettingsAdapter {
public async initialize() {
await this.addFederationSettings();
this.watchChangesAndUpdateRegistrationFile();
await this.updateSettingsWithProvidedConfigFileIfNecessary();
}

public getApplicationServiceId(): string {
Expand Down Expand Up @@ -65,12 +62,8 @@ export class RocketChatSettingsAdapter {
return settings.get('Federation_Matrix_enabled') === true;
}

public areEphemeralEventsEnabled(): boolean {
return this.isTypingStatusEnabled();
}

public isTypingStatusEnabled(): boolean {
return this.getRegistrationFileFromHomeserver()?.enableEphemeralEvents === true;
return settings.get('Federation_Matrix_enable_ephemeral_events') === true;
}

public onFederationEnabledStatusChanged(
Expand Down Expand Up @@ -103,19 +96,19 @@ export class RocketChatSettingsAdapter {
this.getHomeServerDomain(),
this.getBridgeUrl(),
this.getBridgePort(),
this.generateRegistrationFileObject(),
this.getAppServiceRegistrationObject(),
),
);
}

public generateRegistrationFileObject(): IFederationBridgeRegistrationFile {
public getAppServiceRegistrationObject(): IFederationBridgeRegistrationFile {
return {
id: this.getApplicationServiceId(),
homeserverToken: this.getApplicationHomeServerToken(),
applicationServiceToken: this.getApplicationApplicationServiceToken(),
bridgeUrl: this.getBridgeUrl(),
botName: this.getBridgeBotUsername(),
enableEphemeralEvents: this.areEphemeralEventsEnabled(),
enableEphemeralEvents: this.isTypingStatusEnabled(),
listenTo: {
users: [
{
Expand All @@ -140,7 +133,7 @@ export class RocketChatSettingsAdapter {
}

private async updateRegistrationFile(): Promise<void> {
const registrationFile = this.generateRegistrationFileObject();
const registrationFile = this.getAppServiceRegistrationObject();

await Settings.updateValueById(
'Federation_Matrix_registration_file',
Expand Down Expand Up @@ -172,9 +165,7 @@ export class RocketChatSettingsAdapter {
}

private async addFederationSettings(): Promise<void> {
const preExistingConfiguration = this.getRegistrationFileFromHomeserver();

await settingsRegistry.add('Federation_Matrix_enabled', Boolean(preExistingConfiguration), {
await settingsRegistry.add('Federation_Matrix_enabled', false, {
readonly: false,
type: 'boolean',
i18nLabel: 'Federation_Matrix_enabled',
Expand All @@ -185,11 +176,24 @@ export class RocketChatSettingsAdapter {
section: 'Matrix Bridge',
});

await settingsRegistry.add('Federation_Matrix_enable_ephemeral_events', false, {
readonly: false,
type: 'boolean',
i18nLabel: 'Federation_Matrix_enable_ephemeral_events',
i18nDescription: 'Federation_Matrix_enable_ephemeral_events_desc',
alert: 'Federation_Matrix_enable_ephemeral_events_Alert',
public: true,
group: 'Federation',
section: 'Matrix Bridge',
});

const uniqueId = settings.get('uniqueID') || uuidv4().slice(0, 15).replace(new RegExp('-', 'g'), '_');
const homeserverToken = crypto.createHash('sha256').update(`hs_${uniqueId}`).digest('hex');
const applicationServiceToken = crypto.createHash('sha256').update(`as_${uniqueId}`).digest('hex');

await settingsRegistry.add('Federation_Matrix_id', preExistingConfiguration?.id || `rocketchat_${uniqueId}`, {
const siteUrl = settings.get<string>('Site_Url');

await settingsRegistry.add('Federation_Matrix_id', `rocketchat_${uniqueId}`, {
readonly: true,
type: 'string',
i18nLabel: 'Federation_Matrix_id',
Expand All @@ -198,7 +202,7 @@ export class RocketChatSettingsAdapter {
section: 'Matrix Bridge',
});

await settingsRegistry.add('Federation_Matrix_hs_token', preExistingConfiguration?.homeserverToken || homeserverToken, {
await settingsRegistry.add('Federation_Matrix_hs_token', homeserverToken, {
readonly: true,
type: 'string',
i18nLabel: 'Federation_Matrix_hs_token',
Expand All @@ -207,7 +211,7 @@ export class RocketChatSettingsAdapter {
section: 'Matrix Bridge',
});

await settingsRegistry.add('Federation_Matrix_as_token', preExistingConfiguration?.applicationServiceToken || applicationServiceToken, {
await settingsRegistry.add('Federation_Matrix_as_token', applicationServiceToken, {
readonly: true,
type: 'string',
i18nLabel: 'Federation_Matrix_as_token',
Expand All @@ -216,39 +220,33 @@ export class RocketChatSettingsAdapter {
section: 'Matrix Bridge',
});

await settingsRegistry.add(
'Federation_Matrix_homeserver_url',
preExistingConfiguration?.rocketchat?.homeServerUrl || 'http://localhost:8008',
{
type: 'string',
i18nLabel: 'Federation_Matrix_homeserver_url',
i18nDescription: 'Federation_Matrix_homeserver_url_desc',
alert: 'Federation_Matrix_homeserver_url_alert',
group: 'Federation',
section: 'Matrix Bridge',
},
);
await settingsRegistry.add('Federation_Matrix_homeserver_url', 'http://localhost:8008', {
type: 'string',
i18nLabel: 'Federation_Matrix_homeserver_url',
i18nDescription: 'Federation_Matrix_homeserver_url_desc',
alert: 'Federation_Matrix_homeserver_url_alert',
group: 'Federation',
section: 'Matrix Bridge',
});

await settingsRegistry.add(
'Federation_Matrix_homeserver_domain',
preExistingConfiguration?.rocketchat?.domainName || 'local.rocket.chat',
{
type: 'string',
i18nLabel: 'Federation_Matrix_homeserver_domain',
i18nDescription: 'Federation_Matrix_homeserver_domain_desc',
alert: 'Federation_Matrix_homeserver_domain_alert',
},
);
await settingsRegistry.add('Federation_Matrix_homeserver_domain', siteUrl, {
type: 'string',
i18nLabel: 'Federation_Matrix_homeserver_domain',
i18nDescription: 'Federation_Matrix_homeserver_domain_desc',
alert: 'Federation_Matrix_homeserver_domain_alert',
group: 'Federation',
section: 'Matrix Bridge',
});

await settingsRegistry.add('Federation_Matrix_bridge_url', preExistingConfiguration?.bridgeUrl || 'http://host.docker.internal:3300', {
await settingsRegistry.add('Federation_Matrix_bridge_url', 'http://localhost:3300', {
type: 'string',
i18nLabel: 'Federation_Matrix_bridge_url',
i18nDescription: 'Federation_Matrix_bridge_url_desc',
group: 'Federation',
section: 'Matrix Bridge',
});

await settingsRegistry.add('Federation_Matrix_bridge_localpart', preExistingConfiguration?.botName || 'rocket.cat', {
await settingsRegistry.add('Federation_Matrix_bridge_localpart', 'rocket.cat', {
type: 'string',
i18nLabel: 'Federation_Matrix_bridge_localpart',
i18nDescription: 'Federation_Matrix_bridge_localpart_desc',
Expand All @@ -258,7 +256,6 @@ export class RocketChatSettingsAdapter {

await settingsRegistry.add('Federation_Matrix_registration_file', '', {
readonly: true,
hidden: Boolean(preExistingConfiguration),
type: 'code',
i18nLabel: 'Federation_Matrix_registration_file',
i18nDescription: 'Federation_Matrix_registration_file_desc',
Expand All @@ -280,49 +277,4 @@ export class RocketChatSettingsAdapter {
section: 'Matrix Bridge',
});
}

private getRegistrationFileFromHomeserver(): Record<string, any> | undefined {
try {
const registrationYaml = fs.readFileSync(this.getFilePathForHomeserverConfig(), 'utf8');

const parsedFile = yaml.load(registrationYaml as string) as Record<string, any>;
return {
applicationServiceToken: parsedFile.as_token,
bridgeUrl: parsedFile.url,
botName: parsedFile.sender_localpart,
homeserverToken: parsedFile.hs_token,
id: parsedFile.id,
listenTo: parsedFile.namespaces,
enableEphemeralEvents: parsedFile['de.sorunome.msc2409.push_ephemeral'],
rocketchat: { domainName: parsedFile.rocketchat?.homeserver_domain, homeServerUrl: parsedFile.rocketchat?.homeserver_url },
};
} catch (e) {
// no-op
}
}

private getFilePathForHomeserverConfig(): string {
return process.env.NODE_ENV === 'development'
? '../../../../../matrix-federation-config/registration.yaml'
: resolve(process.cwd(), '../../../matrix-federation-config/registration.yaml');
}

private async updateSettingsWithProvidedConfigFileIfNecessary() {
const existingConfiguration = this.getRegistrationFileFromHomeserver();
if (!existingConfiguration) {
return;
}

await Promise.all([
Settings.updateValueById('Federation_Matrix_enabled', true),
Settings.updateValueById('Federation_Matrix_id', existingConfiguration.id),
Settings.updateValueById('Federation_Matrix_hs_token', existingConfiguration.homeserverToken),
Settings.updateValueById('Federation_Matrix_as_token', existingConfiguration.applicationServiceToken),
Settings.updateValueById('Federation_Matrix_homeserver_url', existingConfiguration.rocketchat?.homeServerUrl),
Settings.updateValueById('Federation_Matrix_homeserver_domain', existingConfiguration.rocketchat?.domainName),
Settings.updateValueById('Federation_Matrix_bridge_url', existingConfiguration.bridgeUrl),
Settings.updateValueById('Federation_Matrix_bridge_localpart', existingConfiguration.botName),
Settings.update({ _id: 'Federation_Matrix_registration_file' }, { $set: { hidden: Boolean(existingConfiguration) } }),
]);
}
}
10 changes: 6 additions & 4 deletions apps/meteor/tests/mocks/server/BrokerMocked.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
export class BrokerMocked {
actions: Record<string, (...params: unknown[]) => Promise<unknown>> = {};

destroyService(): void {
// no op
services: Map<string, any> = new Map();

async destroyService(name: string): Promise<void> {
this.services.delete(name);
}

createService(): void {
// no op
createService(instance: any): void {
this.services.set(instance.name, instance);
}

async call(method: string, data: any): Promise<any> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const broker = new BrokerMocked();

describe('Check MAC', () => {
before(() => {
api.setBroker(broker);
api.setBroker(broker as any);
});

it('should do nothing if not omnichannel room', async () => {
Expand Down
Loading