Files
upstream-mirror 30810eecd7
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
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

181 lines
6.6 KiB
TypeScript

import { api } from '@rocket.chat/core-services';
import type { LicenseLimitKind } from '@rocket.chat/core-typings';
import { applyLicense, applyLicenseOrRemove, applyNewestLicense, License } from '@rocket.chat/license';
import { Subscriptions, Users, Settings, LivechatContacts } from '@rocket.chat/models';
import { wrapExceptions } from '@rocket.chat/tools';
import moment from 'moment';
import { getAppCount } from './lib/getAppCount';
import { callbacks } from '../../../../server/lib/callbacks';
import { syncWorkspace } from '../../../../server/lib/cloud/syncWorkspace';
import { SystemLogger } from '../../../../server/lib/logger/system';
import { notifyOnSettingChangedById } from '../../../../server/lib/notifyListener';
import { settings } from '../../../../server/settings';
const applyStartupLicense = async (): Promise<void> => {
// Licenses can't be validated before the workspace URL is known; set it explicitly
// so the startup license is never left pending behind the Site_Url watcher.
await License.setWorkspaceUrl(settings.get<string>('Site_Url'));
await applyNewestLicense(License, settings.get<string>('Enterprise_License') ?? '', process.env.ROCKETCHAT_LICENSE ?? '');
};
export const startLicense = async () => {
settings.watch<string>('Site_Url', (value) => {
if (value) {
void License.setWorkspaceUrl(value);
}
});
License.onValidateLicense(async () => {
(await Settings.updateValueById('Enterprise_License', License.encryptedLicense)).modifiedCount &&
void notifyOnSettingChangedById('Enterprise_License');
(await Settings.updateValueById('Enterprise_License_Status', 'Valid')).modifiedCount &&
void notifyOnSettingChangedById('Enterprise_License_Status');
});
License.onInvalidateLicense(async () => {
(await Settings.updateValueById('Enterprise_License_Status', 'Invalid')).modifiedCount &&
void notifyOnSettingChangedById('Enterprise_License_Status');
});
License.onRemoveLicense(async () => {
(await Settings.updateValueById('Enterprise_License', '')).modifiedCount &&
void notifyOnSettingChangedById('Enterprise_License_Status');
(await Settings.updateValueById('Enterprise_License_Status', 'Invalid')).modifiedCount &&
void notifyOnSettingChangedById('Enterprise_License_Status');
});
/**
* This is a debounced function that will sync the workspace data to the cloud.
* it caches the context, waits for a second and then syncs the data.
*/
const syncByTriggerDebounced = (() => {
let timeout: NodeJS.Timeout | undefined;
const contexts: Set<string> = new Set();
return async (context: string) => {
contexts.add(context);
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
timeout = undefined;
void syncByTrigger([...contexts]);
contexts.clear();
}, 1000);
};
})();
const syncByTrigger = async (contexts: string[]) => {
if (!License.encryptedLicense) {
return;
}
const existingData = wrapExceptions(() => JSON.parse(settings.get<string>('Enterprise_License_Data'))).catch(() => ({})) ?? {};
const date = new Date();
const day = date.getDate();
const month = date.getMonth() + 1;
const year = date.getFullYear();
const period = `${year}-${month}-${day}`;
const [, , signed] = License.encryptedLicense.split('.');
// Check if this sync has already been done. Based on License, behavior.
if ([...contexts.values()].every((context) => existingData.signed === signed && existingData[context] === period)) {
return;
}
const obj = Object.fromEntries(contexts.map((context) => [context, period]));
(
await Settings.updateValueById(
'Enterprise_License_Data',
JSON.stringify({
...(existingData.signed === signed && existingData),
...existingData,
...obj,
signed,
}),
)
).modifiedCount && void notifyOnSettingChangedById('Enterprise_License_Data');
try {
await syncWorkspace();
} catch (error) {
console.error(error);
}
};
License.setLicenseLimitCounter('activeUsers', () => Users.getActiveLocalUserCount());
License.setLicenseLimitCounter('guestUsers', () => Users.getActiveLocalGuestCount());
License.setLicenseLimitCounter('roomsPerGuest', async (context) =>
context?.userId ? Subscriptions.countByUserIdExceptType(context.userId, 'd') : 0,
);
License.setLicenseLimitCounter('privateApps', () => getAppCount('private'));
License.setLicenseLimitCounter('marketplaceApps', () => getAppCount('marketplace'));
License.setLicenseLimitCounter('monthlyActiveContacts', () => LivechatContacts.countContactsOnPeriod(moment.utc().format('YYYY-MM')));
return new Promise<void>((resolve) => {
// When settings are loaded, apply the current license if there is one.
settings.onReady(async () => {
import('./airGappedRestrictions');
await applyStartupLicense();
License.onInstall(() => {
if (License.hasOfflineLicense()) {
// startup level so it is visible at the default Log_Level, like 'License installed'
SystemLogger.startup(
'Offline license detected: outbound connections to Rocket.Chat Cloud services and the Rocket.Chat Push Gateway are disabled',
);
}
});
// After the current license is already loaded, watch the setting value to react to new licenses being applied.
settings.change<string>('Enterprise_License', (license) => applyLicenseOrRemove(license, true));
callbacks.add('workspaceLicenseRemoved', () => License.remove());
callbacks.add('workspaceLicenseChanged', (updatedLicense) => applyLicense(updatedLicense, true));
License.onInstall(async () => void api.broadcast('license.actions', {} as Record<Partial<LicenseLimitKind>, boolean>));
License.onInvalidate(async () => void api.broadcast('license.actions', {} as Record<Partial<LicenseLimitKind>, boolean>));
License.onBehaviorTriggered('prevent_action', (context) => syncByTriggerDebounced(`prevent_action_${context.limit}`));
License.onBehaviorTriggered('start_fair_policy', async (context) => syncByTriggerDebounced(`start_fair_policy_${context.limit}`));
License.onBehaviorTriggered('disable_modules', async (context) => syncByTriggerDebounced(`disable_modules_${context.limit}`));
License.onChange(() => api.broadcast('license.sync'));
License.onBehaviorToggled('prevent_action', (context) => {
if (!context.limit) {
return;
}
void api.broadcast('license.actions', {
[context.limit]: true,
} as Record<Partial<LicenseLimitKind>, boolean>);
});
License.onBehaviorToggled('allow_action', (context) => {
if (!context.limit) {
return;
}
void api.broadcast('license.actions', {
[context.limit]: false,
} as Record<Partial<LicenseLimitKind>, boolean>);
});
resolve();
});
});
};