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,4 @@
export const addMinutesToADate = (date: Date, minutes: number): Date => {
const minutesInMs = minutes * 60 * 1000;
return new Date(date.getTime() + minutesInMs);
};

View File

@@ -0,0 +1,4 @@
export function ensureArray<T>(param: T | Array<T>): Array<T> {
const emptyArray: Array<T> = [];
return emptyArray.concat(param);
}

View File

@@ -0,0 +1,7 @@
export const closeUnclosedCodeBlock = (text: string): string => {
const backtickCount = (text.match(/```/g) || []).length;
if (backtickCount % 2 !== 0) {
return `${text}\n\`\`\``;
}
return text;
};

View File

@@ -0,0 +1,5 @@
export const compareByRanking =
<T>(rank: (x: T) => number) =>
(a: T, b: T) => {
return rank(a) - rank(b);
};

View File

@@ -0,0 +1,3 @@
export const exhaustiveCheck = (_: never): never => {
throw new Error('Exhaustive check failed');
};

View File

@@ -0,0 +1,13 @@
import { compile } from 'path-to-regexp';
type PathParams<TPath extends string> = TPath extends `${string}:${infer TParam}/${infer TRest}`
? (TParam extends `${infer U}?` ? U : TParam) | PathParams<TRest>
: TPath extends `${string}:${infer TParam}`
? TParam extends `${infer U}?`
? U
: TParam
: never;
export function generatePath<TPath extends string>(path: TPath, params?: Partial<Record<PathParams<TPath>, string>>): string {
return compile(path, { encode: encodeURIComponent })(params);
}

View File

@@ -0,0 +1,13 @@
export const getFileExtension = (fileName?: string): string => {
if (!fileName) {
return 'file';
}
const arr = fileName.split('.');
if (arr.length < 2 || (arr[0] === '' && arr.length === 2)) {
return 'file';
}
return arr.pop()?.toLocaleUpperCase() || 'file';
};

View File

@@ -0,0 +1,69 @@
export const withThrottling =
({ wait, leading, trailing }: { wait: number; leading?: boolean; trailing?: boolean }) =>
<TFunction extends (...args: any[]) => any>(fn: TFunction) => {
let timer: ReturnType<typeof setTimeout> | undefined = undefined;
let result: ReturnType<TFunction>;
let previous = 0;
function throttled(this: ThisParameterType<TFunction>, ...args: Parameters<TFunction>) {
const now = Date.now();
if (!previous && leading === false) previous = now;
const remaining = wait - (now - previous);
if (remaining <= 0 || remaining > wait) {
if (timer) clearTimeout(timer);
timer = undefined;
previous = now;
result = fn.apply(this, args);
} else if (!timer && trailing !== false) {
const later = () => {
previous = leading === false ? 0 : Date.now();
result = fn.apply(this, args);
timer = undefined;
};
timer = setTimeout(later, remaining);
}
return result;
}
const cancel = () => {
if (timer) clearTimeout(timer);
previous = 0;
timer = undefined;
};
return Object.assign(throttled, { cancel });
};
export const withDebouncing =
({ wait, immediate }: { wait: number; immediate?: boolean }) =>
<TFunction extends (...args: any[]) => any>(fn: TFunction) => {
let timer: ReturnType<typeof setTimeout> | undefined = undefined;
let result: ReturnType<TFunction>;
let previous: number;
function debounced(this: ThisParameterType<TFunction>, ...args: Parameters<TFunction>) {
previous = Date.now();
if (!timer) {
const later = () => {
const passed = Date.now() - previous;
if (wait > passed) {
timer = setTimeout(later, wait - passed);
} else {
timer = undefined;
if (!immediate) result = fn.apply(this, args);
}
};
timer = setTimeout(later, wait);
if (immediate) result = fn.apply(this, args);
}
return result;
}
const cancel = () => {
clearTimeout(timer);
timer = undefined;
};
return Object.assign(debounced, { cancel });
};

View File

@@ -0,0 +1,25 @@
import { isJSON } from './isJSON';
describe('isJSON', () => {
it('returns true for any successfully parsed JSON', () => {
expect(isJSON('{"a":1}')).toBe(true);
expect(isJSON('{}')).toBe(true);
expect(isJSON('[1,2]')).toBe(true);
expect(isJSON('"hello"')).toBe(true);
expect(isJSON('true')).toBe(true);
expect(isJSON('42')).toBe(true);
});
it('returns true for valid but falsy JSON values', () => {
expect(isJSON('false')).toBe(true);
expect(isJSON('null')).toBe(true);
expect(isJSON('0')).toBe(true);
expect(isJSON('""')).toBe(true);
});
it('returns false for malformed JSON', () => {
expect(isJSON('{ not json }')).toBe(false);
expect(isJSON('{"a":')).toBe(false);
expect(isJSON('')).toBe(false);
});
});

View File

@@ -0,0 +1,8 @@
export const isJSON = (value: string): boolean => {
try {
JSON.parse(value);
return true;
} catch {
return false;
}
};

View File

@@ -0,0 +1,7 @@
/**
* @deprecated use isObject from tools package instead
*/
export function isObject(obj: unknown): obj is object {
const type = typeof obj;
return type === 'function' || (type === 'object' && !!obj);
}

View File

@@ -0,0 +1,3 @@
export function isPlainObject(value: unknown): value is Record<string | symbol, unknown> {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}

View File

@@ -0,0 +1 @@
export const isRelativeURL = (str: string): boolean => /^[^\/]+\/[^\/].*$|^\/[^\/].*$/gim.test(str);

View File

@@ -0,0 +1,8 @@
/**
* Strips a leading '@' from a username if present.
*
* Federated usernames (e.g. @john.doe:matrix.org) are stored with a leading '@',
* while local usernames are not. This utility normalises both forms so callers
* that render `@{username}` don't accidentally produce `@@john.doe:matrix.org`.
*/
export const normalizeUsername = (username: string): string => (username.startsWith('@') ? username.slice(1) : username);

View File

@@ -0,0 +1,7 @@
export function omit<TObject, TKey extends keyof TObject>(obj: TObject, ...keys: TKey[]): Omit<TObject, TKey> {
const result = { ...obj };
keys.forEach((key) => {
delete result[key];
});
return result;
}

View File

@@ -0,0 +1,9 @@
export function parseCSV(csv: string, removeEmptyItems = true): string[] {
const items = csv.split(',').map((item) => item.trim());
if (removeEmptyItems) {
return items.filter(Boolean);
}
return items;
}

View File

@@ -0,0 +1,64 @@
function split(parameters: string, ignoreExtraUnquotedSpaces = true): string[] {
// Replace \n\r with a single \n
const line = parameters.replace(/\n\r/gm, '\n');
const items = line.split(/[ \n\r\t]/);
if (ignoreExtraUnquotedSpaces) {
return items.filter(Boolean);
}
return items;
}
export function parseParameters(parameters: string, ignoreExtraUnquotedSpaces = true): string[] {
if (!parameters.trim()) {
return [];
}
const match = parameters.match(/((["'])(?:(?=(\\?))\3.)*?\2)/gs);
let line = parameters;
if (!match) {
return split(line, ignoreExtraUnquotedSpaces);
}
match.forEach((item) => {
const newItem = item
// Replace start quote with SSA character
.replace(/(^['"])/g, '\u0086')
// Replace end quote with ESA character
.replace(/['"]$/g, '\u0087')
// Replace spaces inside the quotes with a Punctuation Space character
.replace(/ /g, '\u2008')
// Replace new lines inside the quotes with a PLD character
.replace(/\n/gm, '\u008B')
// Replace carriage returns inside the quotes with a PLU character
.replace(/\r/gm, '\u008C')
// Replace tabs inside the quotes with a VTS character
.replace(/\t/g, '\u008A');
line = line.replace(item, newItem);
});
// If two quoted parameters are not separated by a space, add one automatically
line = line.replace(/\u0087\u0086/g, '\u0087 \u0086');
const items = split(line, ignoreExtraUnquotedSpaces);
return items.map((item) =>
item
// Convert back the spaces from inside quotes
.replace(/\u2008/g, ' ')
// Convert back the new lines from inside quotes
.replace(/\u008B/g, '\n')
// Convert back the carriage returns from inside quotes
.replace(/\u008C/g, '\r')
// Convert back the tabs from inside quotes
.replace(/\u008A/g, '\t')
// Remove SSA and ESA characters
.replace(/[\u0086\u0087]/g, '')
// Unescape quotes
.replace(/\\\"/g, '"'),
);
}

View File

@@ -0,0 +1,5 @@
// default email engines - like gmail - will render texts with dots as an anchor tag.
// If we can, we should avoid that.
export const safeHtmlDots = (text: string): string => {
return text.replace(/\./g, '&#8228');
};

View File

@@ -0,0 +1,14 @@
/**
* @todo check alternatives for date/time formatting
*/
export const secondsToHHMMSS = (sec: number | string): string => {
if (typeof sec !== 'number') {
sec = parseFloat(sec);
}
const hours = Math.floor(sec / 3600);
const minutes = Math.floor((sec - hours * 3600) / 60);
const seconds = Math.round(sec - hours * 3600 - minutes * 60);
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
};

View File

@@ -0,0 +1,2 @@
// Promisified sleep function
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

View File

@@ -0,0 +1,113 @@
import { escapeRegExp } from '@rocket.chat/tools';
import DOMPurify from 'dompurify';
export function truncate(str: string, length: number): string {
return str.length > length ? `${str.slice(0, length - 3)}...` : str;
}
function makeString(object: unknown): string {
if (!object) return '';
return String(object);
}
function defaultToWhiteSpace(characters: unknown): string {
if (!characters) return '\\s';
if (typeof characters === 'object' && 'source' in characters) return (characters as { source: string }).source;
return `[${escapeRegExp(makeString(characters))}]`;
}
const nativeTrim = String.prototype.trim;
const nativeTrimLeft = String.prototype.trimLeft;
const nativeTrimRight = String.prototype.trimRight;
export function trim(_str: unknown, _characters?: unknown): string {
const str = makeString(_str);
if (!_characters && nativeTrim) return nativeTrim.call(str);
const characters = defaultToWhiteSpace(_characters);
return str.replace(new RegExp(`^${characters}+|${characters}+$`, 'g'), '');
}
export function ltrim(_str: unknown, _characters: unknown): string {
const str = makeString(_str);
if (!_characters && nativeTrimLeft) return nativeTrimLeft.call(str);
const characters = defaultToWhiteSpace(_characters);
return str.replace(new RegExp(`^${characters}+`), '');
}
export function rtrim(_str: unknown, _characters: unknown): string {
const str = makeString(_str);
if (!_characters && nativeTrimRight) return nativeTrimRight.call(str);
const characters = defaultToWhiteSpace(_characters);
return str.replace(new RegExp(`${characters}+$`), '');
}
export function capitalize(_str: unknown, lowercaseRest: boolean): string {
const str = makeString(_str);
const remainingChars = !lowercaseRest ? str.slice(1) : str.slice(1).toLowerCase();
return str.charAt(0).toUpperCase() + remainingChars;
}
export function stripTags(str: unknown): string {
return DOMPurify.sanitize(makeString(str)).replace(/<\/?[^>]+>/g, '');
}
export function strLeft(_str: unknown, _sep: unknown): string {
const str = makeString(_str);
const sep = makeString(_sep);
const pos = !sep ? -1 : str.indexOf(sep);
return ~pos ? str.slice(0, pos) : str;
}
export function strRight(_str: unknown, _sep: unknown): string {
const str = makeString(_str);
const sep = makeString(_sep);
const pos = !sep ? -1 : str.indexOf(sep);
return ~pos ? str.slice(pos + sep.length, str.length) : str;
}
export function strRightBack(_str: unknown, _sep: unknown): string {
const str = makeString(_str);
const sep = makeString(_sep);
const pos = !sep ? -1 : str.lastIndexOf(sep);
return ~pos ? str.slice(pos + sep.length, str.length) : str;
}
export function numberFormat(_number: number, dec: number, dsep?: string, tsep?: string): string {
if (isNaN(_number) || _number === null || _number === undefined) return '';
const number = _number.toFixed(~~dec);
tsep = typeof tsep === 'string' ? tsep : ',';
const parts = number.split('.');
const fnums = parts[0];
const decimals = parts[1] ? (dsep || '.') + parts[1] : '';
return fnums.replace(/(\d)(?=(?:\d{3})+$)/g, `$1${tsep}`) + decimals;
}
export function pad(_str: unknown, _length: number, padStr?: string, type: 'right' | 'left' | 'both' = 'right') {
const str = makeString(_str);
const length = ~~_length;
let padlen = 0;
if (!padStr) padStr = ' ';
else if (padStr.length > 1) padStr = padStr.charAt(0);
switch (type) {
case 'right':
padlen = length - str.length;
return str + padStr.repeat(padlen);
case 'both':
padlen = length - str.length;
return padStr.repeat(Math.ceil(padlen / 2)) + str + padStr.repeat(Math.floor(padlen / 2));
default: // 'left'
padlen = length - str.length;
return padStr.repeat(padlen) + str;
}
}
export function lrpad(str: unknown, length: number, padStr?: string): string {
return pad(str, length, padStr, 'both');
}

View File

@@ -0,0 +1,20 @@
import { throttle } from 'underscore';
export function throttledCounter(fn: (counter: number) => unknown, wait: number) {
let counter = 0;
const throttledFn = throttle(
() => {
fn(counter);
counter = 0;
},
wait,
{ leading: false },
);
return () => {
counter++;
throttledFn();
};
}