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
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
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 1s
This commit is contained in:
43
apps/meteor/ee/server/lib/license/airGappedRestrictions.ts
Normal file
43
apps/meteor/ee/server/lib/license/airGappedRestrictions.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { AirGappedRestriction, License } from '@rocket.chat/license';
|
||||
import { Settings, Statistics } from '@rocket.chat/models';
|
||||
|
||||
import { i18n } from '../../../../server/lib/i18n';
|
||||
import { notifyOnSettingChangedById } from '../../../../server/lib/notifyListener';
|
||||
import { sendMessagesToAdmins } from '../../../../server/lib/sendMessagesToAdmins';
|
||||
import { updateAuditedBySystem } from '../../../../server/settings/lib/auditedSettingUpdates';
|
||||
|
||||
const updateRestrictionSetting = async (remainingDays: number) => {
|
||||
await updateAuditedBySystem({
|
||||
reason: 'updateRestrictionSetting',
|
||||
})(Settings.updateValueById, 'Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', remainingDays);
|
||||
void notifyOnSettingChangedById('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days');
|
||||
};
|
||||
|
||||
const sendRocketCatWarningToAdmins = async (remainingDays: number) => {
|
||||
const lastDayOrNoRestrictionsAtAll = remainingDays <= 0;
|
||||
if (lastDayOrNoRestrictionsAtAll) {
|
||||
return;
|
||||
}
|
||||
if (AirGappedRestriction.isWarningPeriod(remainingDays)) {
|
||||
await sendMessagesToAdmins({
|
||||
msgs: async ({ adminUser }) => ({
|
||||
msg: i18n.t('AirGapped_Restriction_Warning', { lng: adminUser.language || 'en', remainingDays }),
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
AirGappedRestriction.on('remainingDays', async ({ days }: { days: number }) => {
|
||||
await updateRestrictionSetting(days);
|
||||
await sendRocketCatWarningToAdmins(days);
|
||||
});
|
||||
|
||||
License.onValidateLicense(async () => {
|
||||
const token = await Statistics.findLastStatsToken();
|
||||
void AirGappedRestriction.computeRestriction(token);
|
||||
});
|
||||
|
||||
License.onRemoveLicense(async () => {
|
||||
const token = await Statistics.findLastStatsToken();
|
||||
void AirGappedRestriction.computeRestriction(token);
|
||||
});
|
||||
50
apps/meteor/ee/server/lib/license/canEnableApp.ts
Normal file
50
apps/meteor/ee/server/lib/license/canEnableApp.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { IAppStorageItem } from '@rocket.chat/apps/dist/server/storage/IAppStorageItem';
|
||||
import { Apps } from '@rocket.chat/core-services';
|
||||
import type { LicenseModule } from '@rocket.chat/core-typings';
|
||||
import { License, type LicenseImp } from '@rocket.chat/license';
|
||||
|
||||
import { getInstallationSourceFromAppStorageItem } from '../../../../lib/apps/getInstallationSourceFromAppStorageItem';
|
||||
|
||||
type _canEnableAppDependencies = {
|
||||
Apps: typeof Apps;
|
||||
License: LicenseImp;
|
||||
};
|
||||
|
||||
export const _canEnableApp = async ({ Apps, License }: _canEnableAppDependencies, app: IAppStorageItem): Promise<void> => {
|
||||
if (!(await Apps.isInitialized())) {
|
||||
throw new Error('apps-engine-not-initialized');
|
||||
}
|
||||
|
||||
// Migrated apps were installed before the validation was implemented
|
||||
// so they're always allowed to be enabled
|
||||
if (app.migrated) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (app.info.addon && !License.hasModule(app.info.addon as LicenseModule)) {
|
||||
throw new Error('app-addon-not-valid');
|
||||
}
|
||||
|
||||
const source = getInstallationSourceFromAppStorageItem(app);
|
||||
switch (source) {
|
||||
case 'private':
|
||||
if (await License.shouldPreventAction('privateApps')) {
|
||||
throw new Error('license-prevented');
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
if (await License.shouldPreventAction('marketplaceApps')) {
|
||||
throw new Error('license-prevented');
|
||||
}
|
||||
|
||||
const marketplaceInfo = app.marketplaceInfo?.[0];
|
||||
if (marketplaceInfo?.isEnterpriseOnly && !License.hasValidLicense()) {
|
||||
throw new Error('invalid-license');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
export const canEnableApp = async (app: IAppStorageItem): Promise<void> => _canEnableApp({ Apps, License }, app);
|
||||
35
apps/meteor/ee/server/lib/license/enforceFipsLicense.ts
Normal file
35
apps/meteor/ee/server/lib/license/enforceFipsLicense.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
import { License } from '@rocket.chat/license';
|
||||
|
||||
import { showErrorBox } from '../../../../server/lib/logger/showBox';
|
||||
|
||||
export function enforceFipsLicense(): void {
|
||||
if (!crypto.getFips()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (License.hasModule('fips')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reason = !License.encryptedLicense
|
||||
? 'No license was found in this workspace'
|
||||
: (!License.hasValidLicense() && 'The license applied to this workspace is invalid or expired') ||
|
||||
"The license applied to this workspace does not include the 'fips' module";
|
||||
|
||||
showErrorBox(
|
||||
'FIPS LICENSE ERROR',
|
||||
[
|
||||
`${reason}.`,
|
||||
'',
|
||||
'This server is running in FIPS mode, which requires a valid license',
|
||||
"including the 'fips' module.",
|
||||
'',
|
||||
'Provide your license through the ROCKETCHAT_LICENSE environment variable,',
|
||||
'or contact Rocket.Chat to obtain one: https://www.rocket.chat/sales-contact',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
34
apps/meteor/ee/server/lib/license/getSeatsRequestLink.ts
Normal file
34
apps/meteor/ee/server/lib/license/getSeatsRequestLink.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { ISetting } from '@rocket.chat/core-typings';
|
||||
import { Settings, Users } from '@rocket.chat/models';
|
||||
|
||||
type WizardSettings = Array<ISetting>;
|
||||
|
||||
export const getSeatsRequestLink = async (url: string, params?: Record<string, string>): Promise<string> => {
|
||||
const workspaceId = await Settings.findOneById('Cloud_Workspace_Id');
|
||||
const activeUsers = await Users.getActiveLocalUserCount();
|
||||
const wizardSettings: WizardSettings = await Settings.findSetupWizardSettings().toArray();
|
||||
|
||||
const newUrl = new URL(url);
|
||||
|
||||
if (workspaceId?.value) {
|
||||
newUrl.searchParams.append('workspaceId', String(workspaceId.value));
|
||||
}
|
||||
|
||||
if (activeUsers) {
|
||||
newUrl.searchParams.append('activeUsers', String(activeUsers));
|
||||
}
|
||||
|
||||
wizardSettings
|
||||
.filter(({ _id, value }) => ['Industry', 'Country', 'Size'].includes(_id) && value)
|
||||
.forEach((setting) => {
|
||||
newUrl.searchParams.append(setting._id.toLowerCase(), String(setting.value));
|
||||
});
|
||||
|
||||
if (params) {
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
newUrl.searchParams.append(key, String(value));
|
||||
});
|
||||
}
|
||||
|
||||
return newUrl.toString();
|
||||
};
|
||||
21
apps/meteor/ee/server/lib/license/lib/getAppCount.ts
Normal file
21
apps/meteor/ee/server/lib/license/lib/getAppCount.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Apps } from '@rocket.chat/core-services';
|
||||
import type { LicenseAppSources } from '@rocket.chat/core-typings';
|
||||
|
||||
import { getInstallationSourceFromAppStorageItem } from '../../../../../lib/apps/getInstallationSourceFromAppStorageItem';
|
||||
|
||||
export async function getAppCount(source: LicenseAppSources): Promise<number> {
|
||||
if (!(await Apps.isInitialized())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const apps = await Apps.getApps({ enabled: true });
|
||||
|
||||
if (!apps || !Array.isArray(apps)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const storageItems = await Promise.all(apps.map((app) => Apps.getAppStorageItemById(app.id)));
|
||||
const activeAppsFromSameSource = storageItems.filter((item) => item && getInstallationSourceFromAppStorageItem(item) === source);
|
||||
|
||||
return activeAppsFromSameSource.length;
|
||||
}
|
||||
57
apps/meteor/ee/server/lib/license/license.internalService.ts
Normal file
57
apps/meteor/ee/server/lib/license/license.internalService.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import type { ILicense } from '@rocket.chat/core-services';
|
||||
import { api, ServiceClassInternal } from '@rocket.chat/core-services';
|
||||
import type { LicenseModule } from '@rocket.chat/core-typings';
|
||||
import { License } from '@rocket.chat/license';
|
||||
|
||||
import { guestPermissions } from '../authorization/guestPermissions';
|
||||
import { resetEnterprisePermissions } from '../authorization/resetEnterprisePermissions';
|
||||
|
||||
export class LicenseService extends ServiceClassInternal implements ILicense {
|
||||
protected name = 'license';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
License.onValidateLicense((): void => {
|
||||
if (!License.hasValidLicense()) {
|
||||
return;
|
||||
}
|
||||
|
||||
void api.broadcast('authorization.guestPermissions', guestPermissions);
|
||||
void resetEnterprisePermissions();
|
||||
});
|
||||
|
||||
License.onModule((licenseModule) => {
|
||||
void api.broadcast('license.module', licenseModule);
|
||||
});
|
||||
|
||||
this.onEvent('license.actions', (preventedActions) => License.syncShouldPreventActionResults(preventedActions));
|
||||
|
||||
this.onEvent('license.sync', () => License.sync());
|
||||
}
|
||||
|
||||
override async started(): Promise<void> {
|
||||
if (!License.hasValidLicense()) {
|
||||
return;
|
||||
}
|
||||
|
||||
void api.broadcast('authorization.guestPermissions', guestPermissions);
|
||||
await resetEnterprisePermissions();
|
||||
}
|
||||
|
||||
hasModule(feature: LicenseModule): boolean {
|
||||
return License.hasModule(feature);
|
||||
}
|
||||
|
||||
hasValidLicense(): boolean {
|
||||
return License.hasValidLicense();
|
||||
}
|
||||
|
||||
getModules(): string[] {
|
||||
return License.getModules();
|
||||
}
|
||||
|
||||
getGuestPermissions(): string[] {
|
||||
return guestPermissions;
|
||||
}
|
||||
}
|
||||
28
apps/meteor/ee/server/lib/license/settings.ts
Normal file
28
apps/meteor/ee/server/lib/license/settings.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { settingsRegistry } from '../../../../server/settings';
|
||||
|
||||
// The proper name for this group is Premium, but we can't change it because it's already in use and we will break the settings
|
||||
// TODO: Keep this until next major updates
|
||||
await settingsRegistry.addGroup('Enterprise', async function () {
|
||||
await this.section('Enterprise', async function () {
|
||||
await this.add('Enterprise_License', '', {
|
||||
type: 'string',
|
||||
i18nLabel: 'Premium_License',
|
||||
alert: 'Premium_License_alert',
|
||||
});
|
||||
await this.add('Enterprise_License_Data', '', {
|
||||
type: 'string',
|
||||
hidden: true,
|
||||
public: false,
|
||||
});
|
||||
await this.add('Enterprise_License_Status', '', {
|
||||
readonly: true,
|
||||
type: 'string',
|
||||
i18nLabel: 'Status',
|
||||
});
|
||||
});
|
||||
await this.add('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', -1, {
|
||||
type: 'int',
|
||||
readonly: true,
|
||||
public: true,
|
||||
});
|
||||
});
|
||||
180
apps/meteor/ee/server/lib/license/startup.ts
Normal file
180
apps/meteor/ee/server/lib/license/startup.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user