snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
import { Cloud } from '@rocket.chat/core-typings';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import * as z from 'zod';
import { handleAnnouncementsOnWorkspaceSync, handleNpsOnWorkspaceSync } from './handleCommsSync';
import { CloudWorkspaceConnectionError } from '../../../../lib/errors/CloudWorkspaceConnectionError';
import { CloudWorkspaceRegistrationError } from '../../../../lib/errors/CloudWorkspaceRegistrationError';
import { settings } from '../../../settings';
import { SystemLogger } from '../../logger/system';
import { buildWorkspaceRegistrationData } from '../buildRegistrationData';
import { CloudWorkspaceAccessTokenEmptyError, getWorkspaceAccessToken } from '../getWorkspaceAccessToken';
import { retrieveRegistrationStatus } from '../retrieveRegistrationStatus';
const fetchCloudAnnouncementsSync = async ({
token,
data,
}: {
token: string;
data: Cloud.WorkspaceSyncRequestPayload;
}): Promise<Cloud.WorkspaceCommsResponsePayload> => {
const cloudUrl = settings.get<string>('Cloud_Url');
const response = await fetch(`${cloudUrl}/api/v3/comms/workspace`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
body: data,
// SECURITY: the URL is a default hardcoded value or an envvar/setting set by an admin. It's safe to disable this check.
ignoreSsrfValidation: true,
});
if (!response.ok) {
try {
const { error } = await response.json();
throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${error}`);
} catch (error) {
throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${response.statusText}`);
}
}
const payload = await response.json();
const result = Cloud.WorkspaceCommsResponsePayloadSchema.safeParse(payload);
if (!result.success) {
throw new CloudWorkspaceConnectionError('failed type validation', {
cause: z.prettifyError(result.error),
});
}
return result.data;
};
export async function announcementSync() {
try {
const { workspaceRegistered } = await retrieveRegistrationStatus();
if (!workspaceRegistered) {
throw new CloudWorkspaceRegistrationError('Workspace is not registered');
}
const token = await getWorkspaceAccessToken(true);
if (!token) {
throw new CloudWorkspaceAccessTokenEmptyError();
}
const workspaceRegistrationData = await buildWorkspaceRegistrationData(undefined);
const { nps, announcements } = await fetchCloudAnnouncementsSync({
token,
data: workspaceRegistrationData,
});
if (nps) {
await handleNpsOnWorkspaceSync(nps);
}
if (announcements) {
await handleAnnouncementsOnWorkspaceSync(announcements);
}
return true;
} catch (err) {
switch (true) {
case err instanceof CloudWorkspaceConnectionError:
case err instanceof CloudWorkspaceRegistrationError:
case err instanceof CloudWorkspaceAccessTokenEmptyError: {
SystemLogger.info({
msg: 'Failed to sync with Rocket.Chat Cloud',
function: 'announcementSync',
err,
});
break;
}
default: {
SystemLogger.error({
msg: 'Error during workspace sync',
function: 'announcementSync',
err,
});
}
}
throw err;
}
}

View File

@@ -0,0 +1,42 @@
import { Cloud } from '@rocket.chat/core-typings';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import * as z from 'zod';
import { CloudWorkspaceConnectionError } from '../../../../lib/errors/CloudWorkspaceConnectionError';
import { settings } from '../../../settings';
export async function fetchWorkspaceSyncPayload({
token,
data,
}: {
token: string;
data: Cloud.WorkspaceSyncRequestPayload;
}): Promise<Cloud.WorkspaceSyncResponse> {
const workspaceRegistrationClientUri = settings.get<string>('Cloud_Workspace_Registration_Client_Uri');
const response = await fetch(`${workspaceRegistrationClientUri}/sync`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
body: data,
// SECURITY: the URL is a default hardcoded value or an envvar/setting set by an admin. It's safe to disable this check.
ignoreSsrfValidation: true,
});
if (!response.ok) {
const { error } = await response.json();
throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${error}`);
}
const payload = await response.json();
const result = Cloud.WorkspaceSyncResponseSchema.safeParse(payload);
if (!result.success) {
throw new CloudWorkspaceConnectionError('failed type validation', {
cause: z.prettifyError(result.error),
});
}
return result.data;
}

View File

@@ -0,0 +1,56 @@
import { NPS, Banner } from '@rocket.chat/core-services';
import type { Cloud, IBanner, Optional } from '@rocket.chat/core-typings';
import { getAndCreateNpsSurvey } from '../../../services/nps/getAndCreateNpsSurvey';
export const handleNpsOnWorkspaceSync = async (nps: Cloud.NpsSurveyAnnouncement) => {
const { id: npsId, startAt, expireAt } = nps;
await NPS.create({
npsId,
startAt,
expireAt,
createdBy: {
_id: 'rocket.cat',
username: 'rocket.cat',
},
});
const now = new Date();
if (startAt.getFullYear() === now.getFullYear() && startAt.getMonth() === now.getMonth() && startAt.getDate() === now.getDate()) {
await getAndCreateNpsSurvey(npsId);
}
};
export const handleBannerOnWorkspaceSync = async (banners: Optional<IBanner, '_updatedAt'>[]) => {
for (const banner of banners) {
await Banner.create(banner);
}
};
export const handleAnnouncementsOnWorkspaceSync = async (announcements: {
create: Cloud.Announcement[];
delete?: Cloud.Announcement['_id'][];
}) => {
const { create, delete: deleteIds } = announcements;
if (deleteIds) {
await Promise.all(deleteIds.map((announcementId) => Banner.disable(announcementId)));
}
await Promise.all(
create.map((announcement) => {
const { view, selector } = announcement;
return Banner.create({
...announcement,
...(selector?.roles ? { roles: selector.roles } : {}),
view: {
...view,
appId: 'cloud-announcements-core',
},
});
}),
);
};

View File

@@ -0,0 +1,64 @@
import { License } from '@rocket.chat/license';
import { CloudWorkspaceRegistrationError } from '../../../../lib/errors/CloudWorkspaceRegistrationError';
import { SystemLogger } from '../../logger/system';
import { CloudWorkspaceAccessTokenEmptyError, CloudWorkspaceAccessTokenError, isAbortError } from '../getWorkspaceAccessToken';
import { announcementSync } from './announcementSync';
import { legacySyncWorkspace } from './legacySyncWorkspace';
import { syncCloudData } from './syncCloudData';
import { getCachedSupportedVersionsToken } from '../supportedVersionsToken/supportedVersionsToken';
/**
* Syncs the workspace with the cloud
* @returns {Promise<void>}
* @throws {Error} - If there is an unexpected error during sync like a network error
*/
export async function syncWorkspace() {
if (License.hasOfflineLicense()) {
SystemLogger.debug({ msg: 'Skipping cloud sync: workspace has an offline license', function: 'syncWorkspace' });
await getCachedSupportedVersionsToken.reset();
return;
}
try {
await announcementSync();
await syncCloudData();
} catch (err) {
switch (true) {
case err instanceof CloudWorkspaceRegistrationError:
case err instanceof CloudWorkspaceAccessTokenError: {
// There is no access token, so we can't sync
SystemLogger.info('Workspace does not have a valid access token, sync aborted');
break;
}
default: {
if (!(err instanceof CloudWorkspaceAccessTokenEmptyError) && !isAbortError(err)) {
SystemLogger.error({ msg: 'Error during workspace sync', err });
}
SystemLogger.info({
msg: 'Falling back to legacy sync',
function: 'syncCloudData',
});
try {
await legacySyncWorkspace();
} catch (err) {
switch (true) {
case err instanceof CloudWorkspaceRegistrationError:
case err instanceof CloudWorkspaceAccessTokenError: {
// There is no access token, so we can't sync
break;
}
default: {
if (!(err instanceof CloudWorkspaceAccessTokenEmptyError) && !isAbortError(err)) {
SystemLogger.error({ msg: 'Error during fallback workspace sync', err });
}
throw err;
}
}
}
}
}
} finally {
await getCachedSupportedVersionsToken.reset();
}
}

View File

@@ -0,0 +1,108 @@
import { Cloud } from '@rocket.chat/core-typings';
import { Settings } from '@rocket.chat/models';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import * as z from 'zod';
import { CloudWorkspaceConnectionError } from '../../../../lib/errors/CloudWorkspaceConnectionError';
import { CloudWorkspaceRegistrationError } from '../../../../lib/errors/CloudWorkspaceRegistrationError';
import { settings } from '../../../settings';
import { notifyOnSettingChangedById } from '../../notifyListener';
import type { WorkspaceRegistrationData } from '../buildRegistrationData';
import { buildWorkspaceRegistrationData } from '../buildRegistrationData';
import { CloudWorkspaceAccessTokenEmptyError, getWorkspaceAccessToken } from '../getWorkspaceAccessToken';
import { getWorkspaceLicense } from '../getWorkspaceLicense';
import { retrieveRegistrationStatus } from '../retrieveRegistrationStatus';
import { handleBannerOnWorkspaceSync, handleNpsOnWorkspaceSync } from './handleCommsSync';
/** @deprecated */
const fetchWorkspaceClientPayload = async ({
token,
workspaceRegistrationData,
}: {
token: string;
workspaceRegistrationData: WorkspaceRegistrationData<undefined>;
}): Promise<Cloud.WorkspaceSyncPayload | undefined> => {
const workspaceRegistrationClientUri = settings.get<string>('Cloud_Workspace_Registration_Client_Uri');
const response = await fetch(`${workspaceRegistrationClientUri}/client`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
body: workspaceRegistrationData,
timeout: 5000,
// SECURITY: the URL is a default hardcoded value or an envvar/setting set by an admin. It's safe to disable this check.
ignoreSsrfValidation: true,
});
if (!response.ok) {
try {
const { error } = await response.json();
throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${error}`);
} catch (error) {
throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${response.statusText}`);
}
}
const payload = await response.json();
if (!payload) {
return undefined;
}
const result = Cloud.WorkspaceSyncPayloadSchema.safeParse(payload);
if (!result.success) {
throw new CloudWorkspaceConnectionError('Invalid response from Rocket.Chat Cloud', {
cause: z.prettifyError(result.error),
});
}
return result.data;
};
/** @deprecated */
const consumeWorkspaceSyncPayload = async (result: Cloud.WorkspaceSyncPayload) => {
if (result.publicKey) {
(await Settings.updateValueById('Cloud_Workspace_PublicKey', result.publicKey)).modifiedCount &&
void notifyOnSettingChangedById('Cloud_Workspace_PublicKey');
}
if (result.trial?.trialID) {
(await Settings.updateValueById('Cloud_Workspace_Had_Trial', true)).modifiedCount &&
void notifyOnSettingChangedById('Cloud_Workspace_Had_Trial');
}
// add banners
if (result.banners) {
await handleBannerOnWorkspaceSync(result.banners);
}
if (result.nps) {
await handleNpsOnWorkspaceSync(result.nps);
}
};
/** @deprecated */
export async function legacySyncWorkspace() {
const { workspaceRegistered } = await retrieveRegistrationStatus();
if (!workspaceRegistered) {
throw new CloudWorkspaceRegistrationError('Workspace is not registered');
}
const token = await getWorkspaceAccessToken(true);
if (!token) {
throw new CloudWorkspaceAccessTokenEmptyError();
}
const workspaceRegistrationData = await buildWorkspaceRegistrationData(undefined);
const payload = await fetchWorkspaceClientPayload({ token, workspaceRegistrationData });
if (payload) {
await consumeWorkspaceSyncPayload(payload);
}
await getWorkspaceLicense();
return true;
}

View File

@@ -0,0 +1,78 @@
import { DuplicatedLicenseError } from '@rocket.chat/license';
import { Settings } from '@rocket.chat/models';
import { fetchWorkspaceSyncPayload } from './fetchWorkspaceSyncPayload';
import { CloudWorkspaceAccessError } from '../../../../lib/errors/CloudWorkspaceAccessError';
import { CloudWorkspaceRegistrationError } from '../../../../lib/errors/CloudWorkspaceRegistrationError';
import { callbacks } from '../../callbacks';
import { SystemLogger } from '../../logger/system';
import { buildWorkspaceRegistrationData } from '../buildRegistrationData';
import { CloudWorkspaceAccessTokenEmptyError, getWorkspaceAccessToken } from '../getWorkspaceAccessToken';
import { retrieveRegistrationStatus } from '../retrieveRegistrationStatus';
export async function syncCloudData() {
try {
const { workspaceRegistered } = await retrieveRegistrationStatus();
if (!workspaceRegistered) {
throw new CloudWorkspaceRegistrationError('Workspace is not registered');
}
const token = await getWorkspaceAccessToken(true);
if (!token) {
throw new CloudWorkspaceAccessTokenEmptyError();
}
const workspaceRegistrationData = await buildWorkspaceRegistrationData(undefined);
const {
license,
removeLicense = false,
cloudSyncAnnouncement,
} = await fetchWorkspaceSyncPayload({
token,
data: workspaceRegistrationData,
});
await Settings.updateValueById('Cloud_Sync_Announcement_Payload', JSON.stringify(cloudSyncAnnouncement ?? null));
if (removeLicense) {
await callbacks.run('workspaceLicenseRemoved');
} else {
await callbacks.run('workspaceLicenseChanged', license);
}
SystemLogger.info({
msg: 'Synced with Rocket.Chat Cloud',
function: 'syncCloudData',
});
return true;
} catch (err) {
/**
* If some of CloudWorkspaceAccessError and CloudWorkspaceRegistrationError happens, makes no sense to run the legacySyncWorkspace
* because it will fail too.
* The DuplicatedLicenseError license error is also ignored because it is not a problem. the Cloud is allowed to send the same license twice.
*/
switch (true) {
case err instanceof DuplicatedLicenseError:
return;
case err instanceof CloudWorkspaceAccessError:
case err instanceof CloudWorkspaceRegistrationError:
case err instanceof CloudWorkspaceAccessTokenEmptyError:
SystemLogger.info({
msg: 'Failed to sync with Rocket.Chat Cloud',
function: 'syncCloudData',
err,
});
break;
default:
SystemLogger.error({
msg: 'Failed to sync with Rocket.Chat Cloud',
function: 'syncCloudData',
err,
});
}
throw err;
}
}