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

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,18 @@
import type { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api';
import { Utilities } from '../../../../ee/lib/misc/Utilities';
export const apiCurlGetter =
(absoluteUrl: (path: string) => string) =>
(method: string, api: IApiEndpointMetadata): string[] => {
const example = api.examples?.[method];
return Utilities.curl({
url: absoluteUrl(api.computedPath),
method,
params: example?.params,
query: example?.query,
content: example?.content,
headers: example?.headers,
auth: '',
}).split('\n');
};

View File

@@ -0,0 +1,8 @@
import { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus';
export const appErroredStatuses = [
AppStatus.COMPILER_ERROR_DISABLED,
AppStatus.ERROR_DISABLED,
AppStatus.INVALID_SETTINGS_DISABLED,
AppStatus.INVALID_LICENSE_DISABLED,
];

View File

@@ -0,0 +1,22 @@
import type { App } from '../types';
import { filterAppsByCategories } from './filterAppsByCategories';
describe('filterAppsByCategories', () => {
it('should return true if the app is in the categories', () => {
const app = {
categories: ['category1', 'category2'],
};
const categories = ['category1'];
const result = filterAppsByCategories(app as App, categories);
expect(result).toBe(true);
});
it('should return false if the app is not in the categories', () => {
const app = {
categories: ['category1', 'category2'],
};
const categories = ['category3'];
const result = filterAppsByCategories(app as App, categories);
expect(result).toBe(false);
});
});

View File

@@ -0,0 +1,4 @@
import type { App } from '../types';
export const filterAppsByCategories = (app: App, categories: string[]): boolean =>
!app.categories || categories.length === 0 || app.categories.some((c) => categories.includes(c));

View File

@@ -0,0 +1,10 @@
import { appStatusSpanProps } from '../helpers';
import type { App } from '../types';
export const filterAppsByDisabled = (app: App): boolean => {
const appStatus = appStatusSpanProps(app)?.label;
const uiDisabledStatus = ['Disabled', 'Disabled*', 'Config Needed', 'Failed'];
return uiDisabledStatus.includes(appStatus || '');
};

View File

@@ -0,0 +1,10 @@
import { appStatusSpanProps } from '../helpers';
import type { App } from '../types';
export const filterAppsByEnabled = (app: App): boolean => {
const appStatus = appStatusSpanProps(app)?.label;
const uiEnabledStatus = ['Enabled', 'Enabled*', 'Trial period'];
return uiEnabledStatus.includes(appStatus || '');
};

View File

@@ -0,0 +1,3 @@
import type { App } from '../types';
export const filterAppsByEnterprise = ({ isEnterpriseOnly }: Partial<App>): boolean => Boolean(isEnterpriseOnly);

View File

@@ -0,0 +1,45 @@
import type { PurchaseType } from '@rocket.chat/core-typings';
import { filterAppsByFree } from './filterAppsByFree';
describe('filterAppsByFree', () => {
it('should return true if app purchase type is buy and price does not exist or is 0', () => {
const purchaseType: PurchaseType = 'buy';
const app = {
purchaseType,
price: 0,
};
const result = filterAppsByFree(app);
expect(result).toBe(true);
});
it('should return false if app purchase type is not buy', () => {
const purchaseType: PurchaseType = 'subscription';
const app = {
purchaseType,
price: 0,
};
const result = filterAppsByFree(app);
expect(result).toBe(false);
});
it('should return false if app price exists and is different than 0', () => {
const purchaseType: PurchaseType = 'buy';
const app = {
purchaseType,
price: 5,
};
const result = filterAppsByFree(app);
expect(result).toBe(false);
});
it('should return false if both app purchase type is different than buy and price exists and is different than 0', () => {
const purchaseType: PurchaseType = 'subscription';
const app = {
purchaseType,
price: 5,
};
const result = filterAppsByFree(app);
expect(result).toBe(false);
});
});

View File

@@ -0,0 +1,4 @@
import type { App } from '../types';
export const filterAppsByFree = ({ purchaseType, price, isEnterpriseOnly }: Partial<App>): boolean =>
purchaseType === 'buy' && !price && !isEnterpriseOnly;

View File

@@ -0,0 +1,45 @@
import type { PurchaseType } from '@rocket.chat/core-typings';
import { filterAppsByPaid } from './filterAppsByPaid';
describe('filterAppsByPaid', () => {
it('should return true if both app purchase type is subscription and app price exists and is not 0', () => {
const purchaseType: PurchaseType = 'subscription';
const app = {
purchaseType,
price: 5,
};
const result = filterAppsByPaid(app);
expect(result).toBe(true);
});
it('should return true if app purchase type is subscription', () => {
const purchaseType: PurchaseType = 'subscription';
const app = {
purchaseType,
price: 0,
};
const result = filterAppsByPaid(app);
expect(result).toBe(true);
});
it('should return true if app price exists and is not 0', () => {
const purchaseType: PurchaseType = 'buy';
const app = {
purchaseType,
price: 5,
};
const result = filterAppsByPaid(app);
expect(result).toBe(true);
});
it('should return false if both app price does not exist or is 0 and app purchase type is not subscription', () => {
const purchaseType: PurchaseType = 'buy';
const app = {
purchaseType,
price: 0,
};
const result = filterAppsByPaid(app);
expect(result).toBe(false);
});
});

View File

@@ -0,0 +1,3 @@
import type { App } from '../types';
export const filterAppsByPaid = ({ purchaseType, price }: Partial<App>): boolean => purchaseType === 'subscription' || Boolean(price);

View File

@@ -0,0 +1,21 @@
import { filterAppsByText } from './filterAppsByText';
describe('filterAppsByText', () => {
it('should return true if the text is the name of an app', () => {
const app = {
name: 'name1',
};
const text = 'name1';
const result = filterAppsByText(app.name, text);
expect(result).toBe(true);
});
it('should return false if the text is not the name of an app', () => {
const app = {
name: 'name1',
};
const text = 'name2';
const result = filterAppsByText(app.name, text);
expect(result).toBe(false);
});
});

View File

@@ -0,0 +1 @@
export const filterAppsByText = (name: string, text: string): boolean => name.toLowerCase().indexOf(text.toLowerCase()) > -1;

View File

@@ -0,0 +1 @@
export const formatPrice = (price: number): string => `\$${price.toFixed(2)}`;

View File

@@ -0,0 +1,32 @@
import type { PurchaseType, AppPricingPlan } from '@rocket.chat/core-typings';
import { formatPrice } from './formatPrice';
import { formatPricingPlan } from './formatPricingPlan';
type PlanType = 'Subscription' | 'Paid' | 'Free';
type FormattedPriceAndPlan = {
type: PlanType;
price: string;
};
export const formatPriceAndPurchaseType = (
purchaseType: PurchaseType,
pricingPlans: AppPricingPlan[],
price: number,
): FormattedPriceAndPlan => {
if (purchaseType === 'subscription') {
const type = 'Subscription';
if (!pricingPlans || !Array.isArray(pricingPlans) || pricingPlans.length === 0) {
return { type, price: '-' };
}
return { type, price: formatPricingPlan(pricingPlans[0]) };
}
if (price > 0) {
return { type: 'Paid', price: formatPrice(price) };
}
return { type: 'Free', price: '-' };
};

View File

@@ -0,0 +1,23 @@
import type { AppPricingPlan } from '@rocket.chat/core-typings';
import { formatPrice } from './formatPrice';
import { t } from '../../../../app/utils/lib/i18n';
export const formatPricingPlan = ({ strategy, price, tiers = [], trialDays }: AppPricingPlan): string => {
const { perUnit = false } = (Array.isArray(tiers) && tiers.find((tier) => tier.price === price)) || {};
const pricingPlanTranslationString = [
'Apps_Marketplace_pricingPlan',
Array.isArray(tiers) && tiers.length > 0 && '+*',
strategy,
trialDays && 'trialDays',
perUnit && 'perUser',
]
.filter(Boolean)
.join('_');
return t(pricingPlanTranslationString, {
price: formatPrice(price),
trialDays,
});
};

View File

@@ -0,0 +1,18 @@
import { t } from '../../../../app/utils/lib/i18n';
import { dispatchToastMessage } from '../../../lib/toast';
const shouldHandleErrorAsWarning = (message: string): boolean => {
const warnings = ['Could not reach the Marketplace'];
return warnings.includes(message);
};
export const handleAPIError = (errorObject: unknown): void => {
const { error = '', message = error } = errorObject as { message?: string; error?: string };
if (shouldHandleErrorAsWarning(message)) {
return dispatchToastMessage({ type: 'error', message: t(message) });
}
dispatchToastMessage({ type: 'error', message: t(`Apps_Error_${error}`) });
};

View File

@@ -0,0 +1,49 @@
import { t } from '../../../../app/utils/lib/i18n';
import { dispatchToastMessage } from '../../../lib/toast';
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ApiError {
xhr: {
responseJSON: {
error: string;
status: string;
messages: string[];
payload?: any;
};
};
}
export function handleInstallError(apiError: ApiError | Error): void {
if (apiError instanceof Error) {
dispatchToastMessage({ type: 'error', message: apiError.message });
return;
}
if (!apiError.xhr?.responseJSON) {
return;
}
const { status, messages, error, payload = null } = apiError.xhr.responseJSON;
let message: string;
switch (status) {
case 'storage_error':
message = messages.join('');
break;
case 'app_user_error':
message = messages.join('');
if (payload?.username) {
message = t('Apps_User_Already_Exists', { username: payload.username });
}
break;
default:
if (error) {
message = error;
} else {
message = t('There_has_been_an_error_installing_the_app');
}
}
dispatchToastMessage({ type: 'error', message });
}

View File

@@ -0,0 +1,8 @@
import type { RefObject } from 'react';
export const isValidReference = (reference: RefObject<HTMLElement | null>, e: { target: Node | null }): boolean => {
const isValidTarget = Boolean(e.target);
const isValidReference = e.target !== reference.current && !reference.current?.contains(e.target);
return isValidTarget && isValidReference;
};

View File

@@ -0,0 +1,9 @@
import type { App, AppOverview } from '@rocket.chat/core-typings';
const normalizeFeaturedApps = (appOverviewList: AppOverview[], appsResultItems: App[]): App[] => {
const featuredAppsIdList = appOverviewList.map<string>((featuredApp) => featuredApp.latest.id);
return appsResultItems.filter((app) => featuredAppsIdList.includes(app.id));
};
export default normalizeFeaturedApps;

View File

@@ -0,0 +1,5 @@
export const onMouseEventPreventSideEffects = (e: MouseEvent): void => {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
};

View File

@@ -0,0 +1,30 @@
import { sortAppsByAlphabeticalOrInverseOrder } from './sortAppsByAlphabeticalOrInverseOrder';
describe('sortAppsByAlphabeticalOrder', () => {
it.skip('should return a positive number if first word is, alphabetically, after second word', () => {
const firstWord = 'Alfa';
const secondWord = 'Bravo';
const result = sortAppsByAlphabeticalOrInverseOrder(firstWord, secondWord);
expect(result).toBeGreaterThan(0);
});
it.skip('should return a negative number if first word is, alphabetically, before second word', () => {
const firstWord = 'Bravo';
const secondWord = 'Alfa';
const result = sortAppsByAlphabeticalOrInverseOrder(firstWord, secondWord);
expect(result).toBeLessThan(0);
});
it('should return 0 if the words are the equivalent', () => {
const firstWord = 'Alfa';
const secondWord = 'Alfa';
const result = sortAppsByAlphabeticalOrInverseOrder(firstWord, secondWord);
expect(result).toBe(0);
});
});

View File

@@ -0,0 +1,2 @@
export const sortAppsByAlphabeticalOrInverseOrder = (firstWord: string, secondWord: string): number =>
firstWord.toLowerCase().localeCompare(secondWord.toLowerCase());

View File

@@ -0,0 +1,30 @@
import { sortAppsByClosestOrFarthestModificationDate } from './sortAppsByClosestOrFarthestModificationDate';
describe('sortAppsByClosestOrFarthestModificationDate', () => {
it('should return a positive number if firstDate is before secondDate', () => {
const firstDate = '2000-04-01T07:00:00';
const secondDate = '2022-02-21T13:00:00';
const result = sortAppsByClosestOrFarthestModificationDate(firstDate, secondDate);
expect(result).toBeGreaterThan(0);
});
it('should return a negative number if firstDate is after secondDate', () => {
const firstDate = '2022-02-21T13:00:00';
const secondDate = '2000-04-01T07:00:00';
const result = sortAppsByClosestOrFarthestModificationDate(firstDate, secondDate);
expect(result).toBeLessThan(0);
});
it.skip('should return zero if firstDate and secondDate are equivalent', () => {
const firstDate = '2000-04-01T07:00:00';
const secondDate = '2000-04-01T07:00:00';
const result = sortAppsByClosestOrFarthestModificationDate(firstDate, secondDate);
expect(result).toBeLessThan(0);
});
});

View File

@@ -0,0 +1,6 @@
export const sortAppsByClosestOrFarthestModificationDate = (firstDateString: string, secondDateString: string): number => {
const firstDate = new Date(firstDateString);
const secondDate = new Date(secondDateString);
return +secondDate - +firstDate;
};

View File

@@ -0,0 +1,14 @@
import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus';
import { appErroredStatuses } from './appErroredStatuses';
import { t } from '../../../../app/utils/lib/i18n';
import { dispatchToastMessage } from '../../../lib/toast';
export const warnAppInstall = (appName: string, status: AppStatus): void => {
if (appErroredStatuses.includes(status)) {
dispatchToastMessage({ type: 'error', message: (t(`App_status_${status}`), appName) });
return;
}
dispatchToastMessage({ type: 'success', message: `${appName} installed` });
};

View File

@@ -0,0 +1,19 @@
import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus';
import { appErroredStatuses } from './appErroredStatuses';
import { t } from '../../../../app/utils/lib/i18n';
import { dispatchToastMessage } from '../../../lib/toast';
export const warnEnableDisableApp = (appName: string, status: AppStatus, type: string): void => {
if (appErroredStatuses.includes(status)) {
dispatchToastMessage({ type: 'error', message: (t(`App_status_${status}`), appName) });
return;
}
if (type === 'enable') {
dispatchToastMessage({ type: 'success', message: `${appName} enabled` });
return;
}
dispatchToastMessage({ type: 'success', message: `${appName} disabled` });
};

View File

@@ -0,0 +1,14 @@
import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus';
import { appErroredStatuses } from './appErroredStatuses';
import { t } from '../../../../app/utils/lib/i18n';
import { dispatchToastMessage } from '../../../lib/toast';
export const warnStatusChange = (appName: string, status: AppStatus): void => {
if (appErroredStatuses.includes(status)) {
dispatchToastMessage({ type: 'error', message: (t(`App_status_${status}`), appName) });
return;
}
dispatchToastMessage({ type: 'info', message: (t(`App_status_${status}`), appName) });
};