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

1
packages/random/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.build*

View File

@@ -0,0 +1,25 @@
# @rocket.chat/random
## 1.2.3
### Patch Changes
- ([#38989](https://github.com/RocketChat/Rocket.Chat/pull/38989)) chore(eslint): Upgrades ESLint and its configuration
## 1.2.3-rc.0
### Patch Changes
- ([#38989](https://github.com/RocketChat/Rocket.Chat/pull/38989)) chore(eslint): Upgrades ESLint and its configuration
## 1.2.2
### Patch Changes
- ([#31138](https://github.com/RocketChat/Rocket.Chat/pull/31138)) feat(uikit): Move `@rocket.chat/ui-kit` package to the main monorepo
## 1.2.2-rc.0
### Patch Changes
- b223cbde14: feat(uikit): Move `@rocket.chat/ui-kit` package to the main monorepo

24
packages/random/README.md Normal file
View File

@@ -0,0 +1,24 @@
# random
The `random` package provides several functions for generating random
numbers. It uses a cryptographically strong pseudorandom number generator when
possible, but falls back to a weaker random number generator when
cryptographically strong randomness is not available (on older browsers or on
servers that don't have enough entropy to seed the cryptographically strong
generator).
- `Random.id([n])` - Returns a unique identifier, such as `"Jjwjg6gouWLXhMGKW"`, that is
likely to be unique in the whole world. The optional argument `n`
specifies the length of the identifier in characters and defaults to 17.
- `Random.secret([n])` - Returns a random string of printable characters with 6 bits of
entropy per character. The optional argument `n` specifies the length of
the secret string and defaults to 43 characters, or 256 bits of
entropy. Use `Random.secret` for security-critical secrets that are
intended for machine, rather than human, consumption.
- `Random.fraction()` - Returns a number between 0 and 1, like `Math.random`.
- `Random.choice(arrayOrString)` - Returns a random element of the given array or string.
- `Random.hexString(n)` - Returns a random string of `n` hexadecimal digits.

View File

@@ -0,0 +1,18 @@
import client from '@rocket.chat/jest-presets/client';
import server from '@rocket.chat/jest-presets/server';
import type { Config } from 'jest';
export default {
projects: [
{
displayName: 'client',
preset: client.preset,
testMatch: ['<rootDir>/src/**/*.client.spec.[jt]s?(x)'],
},
{
displayName: 'server',
preset: server.preset,
testMatch: ['<rootDir>/src/**/*.server.spec.[jt]s?(x)'],
},
],
} satisfies Config;

View File

@@ -0,0 +1,28 @@
{
"name": "@rocket.chat/random",
"version": "1.2.3",
"private": true,
"description": "Random number generator and utilities; Fork of Meteor's Random package",
"main": "./dist/main.server.js",
"browser": "./dist/main.client.js",
"types": "./dist/main.server.d.ts",
"scripts": {
"build": "rm -rf dist && tsc -p tsconfig.build.json",
"dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"test": "jest",
"testunit": "jest",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@rocket.chat/jest-presets": "workspace:~",
"@rocket.chat/tsconfig": "workspace:*",
"eslint": "~9.39.5",
"jest": "~30.2.0",
"typescript": "~5.9.3"
},
"volta": {
"extends": "../../package.json"
}
}

View File

@@ -0,0 +1,105 @@
import { RandomGenerator } from './RandomGenerator';
// Alea PRNG, which is not cryptographically strong
// see http://baagoe.org/en/wiki/Better_random_numbers_for_javascript
// for a full discussion and Alea implementation.
function createAlea(seeds: readonly unknown[]) {
function createMash() {
let n = 0xefc8249d;
const mash = (data: unknown) => {
data = String(data);
if (typeof data !== 'string') {
throw new Error('Expected a string');
}
for (let i = 0; i < data.length; i++) {
n += data.charCodeAt(i);
let h = 0.02519603282416938 * n;
n = h >>> 0;
h -= n;
h *= n;
n = h >>> 0;
h -= n;
n += h * 0x100000000; // 2^32
}
return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
};
mash.version = 'Mash 0.9';
return mash;
}
let s0 = 0;
let s1 = 0;
let s2 = 0;
let c = 1;
if (seeds.length === 0) {
seeds = [+new Date()];
}
const mash = createMash();
s0 = mash(' ');
s1 = mash(' ');
s2 = mash(' ');
for (let i = 0; i < seeds.length; i++) {
s0 -= mash(seeds[i]);
if (s0 < 0) {
s0 += 1;
}
s1 -= mash(seeds[i]);
if (s1 < 0) {
s1 += 1;
}
s2 -= mash(seeds[i]);
if (s2 < 0) {
s2 += 1;
}
}
const random = () => {
const t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32
s0 = s1;
s1 = s2;
s2 = t - (c = t | 0);
return s2;
};
random.uint32 = () => random() * 0x100000000; // 2^32
random.fract53 = () => random() + ((random() * 0x200000) | 0) * 1.1102230246251565e-16; // 2^-53
random.version = 'Alea 0.9';
random.args = seeds;
return random;
}
// options:
// - seeds: an array
// whose items will be `toString`ed and used as the seed to the Alea
// algorithm
export class AleaRandomGenerator extends RandomGenerator {
private readonly alea: () => number;
constructor({ seeds = [] }: { seeds?: readonly unknown[] } = {}) {
super();
if (!seeds) {
throw new Error('No seeds were provided for Alea PRNG');
}
this.alea = createAlea(seeds);
}
/**
* @name Random.fraction
* @summary Return a number between 0 and 1, like `Math.random`.
* @locus Anywhere
*/
fraction() {
return this.alea();
}
protected safelyCreateWithSeeds(...seeds: readonly unknown[]): RandomGenerator {
return new AleaRandomGenerator({ seeds });
}
insecure: RandomGenerator = this;
}

View File

@@ -0,0 +1,23 @@
import { AleaRandomGenerator } from './AleaRandomGenerator';
import { RandomGenerator } from './RandomGenerator';
import { createAleaGeneratorWithGeneratedSeed } from './createAleaGenerator';
// cryptographically strong PRNGs available in modern browsers
export class BrowserRandomGenerator extends RandomGenerator {
/**
* @name Random.fraction
* @summary Return a number between 0 and 1, like `Math.random`.
* @locus Anywhere
*/
fraction() {
const array = new Uint32Array(1);
window.crypto.getRandomValues(array);
return array[0] * 2.3283064365386963e-10; // 2^-32
}
protected safelyCreateWithSeeds(...seeds: readonly unknown[]) {
return new AleaRandomGenerator({ seeds });
}
insecure: RandomGenerator = createAleaGeneratorWithGeneratedSeed();
}

View File

@@ -0,0 +1,76 @@
import crypto from 'node:crypto';
import { AleaRandomGenerator } from './AleaRandomGenerator';
import { RandomGenerator } from './RandomGenerator';
import { createAleaGeneratorWithGeneratedSeed } from './createAleaGenerator';
export class NodeRandomGenerator extends RandomGenerator {
/**
* @name Random.fraction
* @summary Return a number between 0 and 1, like `Math.random`.
* @locus Anywhere
*/
fraction() {
const numerator = Number.parseInt(this.hexString(8), 16);
return numerator * 2.3283064365386963e-10; // 2^-3;
}
/**
* @name Random.hexString
* @summary Return a random string of `n` hexadecimal digits.
* @locus Anywhere
* @param digits Length of the string
*/
override hexString(digits: number) {
const numBytes = Math.ceil(digits / 2);
const bytes = this.randomBytes(numBytes);
const result = bytes.toString('hex');
// If the number of digits is odd, we'll have generated an extra 4 bits
// of randomness, so we need to trim the last digit.
return result.substring(0, digits);
}
override _randomString(charsCount: number, alphabet: string) {
// Generate all the randomness in a single call instead of one
// crypto.randomBytes() call per character. Each character consumes 4
// bytes read as a big-endian uint32, matching what fraction() would
// have produced, so the output distribution is unchanged.
// Normalized like the base-class loop: NaN/negative counts yield an
// empty string, fractional counts round up.
const count = Number.isNaN(charsCount) ? 0 : Math.max(0, Math.ceil(charsCount));
const bytes = this.randomBytes(count * 4);
let result = '';
for (let i = 0; i < count; i++) {
const fraction = bytes.readUInt32BE(i * 4) * 2.3283064365386963e-10; // 2^-32
result += alphabet.charAt(Math.floor(fraction * alphabet.length));
}
return result;
}
private randomBytes(numBytes: number): Buffer {
// Try to get cryptographically strong randomness. Fall back to
// non-cryptographically strong if not available.
try {
return crypto.randomBytes(numBytes);
} catch (e) {
// XXX should re-throw any error except insufficient entropy
return crypto.pseudoRandomBytes(numBytes);
}
}
/**
* @name Random.between Returns a random integer between min and max, inclusive.
* @param min Minimum value (inclusive)
* @param max Maximum value (inclusive)
* @returns A random integer between min and max, inclusive.
*/
between(min: number, max: number) {
return Math.floor(this.fraction() * (max - min + 1)) + min;
}
protected safelyCreateWithSeeds(...seeds: readonly unknown[]) {
return new AleaRandomGenerator({ seeds });
}
insecure: RandomGenerator = createAleaGeneratorWithGeneratedSeed();
}

View File

@@ -0,0 +1,107 @@
// We use cryptographically strong PRNGs (crypto.getRandomBytes() on the server,
// window.crypto.getRandomValues() in the browser) when available. If these
// PRNGs fail, we fall back to the Alea PRNG, which is not cryptographically
// strong, and we seed it with various sources such as the date, Math.random,
// and window size on the client. When using crypto.getRandomValues(), our
// primitive is hexString(), from which we construct fraction(). When using
// window.crypto.getRandomValues() or alea, the primitive is fraction and we use
// that to construct hex string.
const UNMISTAKABLE_CHARS = '23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz';
const BASE64_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_';
// `type` is one of `RandomGenerator.Type` as defined below.
//
// options:
// - seeds: (required, only for RandomGenerator.Type.ALEA) an array
// whose items will be `toString`ed and used as the seed to the Alea
// algorithm
export abstract class RandomGenerator {
/**
* @name Random.fraction
* @summary Return a number between 0 and 1, like `Math.random`.
* @locus Anywhere
*/
abstract fraction(): number;
/**
* Create a non-cryptographically secure PRNG with a given seed (using
* the Alea algorithm)
*/
createWithSeeds(...seeds: readonly unknown[]): RandomGenerator {
if (seeds.length === 0) {
throw new Error('No seeds were provided');
}
return this.safelyCreateWithSeeds(...seeds);
}
protected abstract safelyCreateWithSeeds(...seeds: readonly unknown[]): RandomGenerator;
/**
* Used like `Random`, but much faster and not cryptographically secure
*/
abstract insecure: RandomGenerator;
/**
* @name Random.hexString
* @summary Return a random string of `n` hexadecimal digits.
* @locus Anywhere
* @param digits Length of the string
*/
hexString(digits: number) {
return this._randomString(digits, '0123456789abcdef');
}
_randomString(charsCount: number, alphabet: string) {
let result = '';
for (let i = 0; i < charsCount; i++) {
result += this.choice(alphabet);
}
return result;
}
/**
* @name Random.id
* @summary Return a unique identifier, such as `"Jjwjg6gouWLXhMGKW"`, that is
* likely to be unique in the whole world.
* @locus Anywhere
* @param charsCount Optional length of the identifier in characters
* (defaults to 17)
*/
id(charsCount = 17) {
// 17 characters is around 96 bits of entropy, which is the amount of state in the Alea PRNG.
return this._randomString(charsCount, UNMISTAKABLE_CHARS);
}
/**
* @name Random.secret
* @summary Return a random string of printable characters with 6 bits of
* entropy per character. Use `Random.secret` for security-critical secrets
* that are intended for machine, rather than human, consumption.
* @locus Anywhere
* @param charsCount Optional length of the secret string (defaults to 43
* characters, or 256 bits of entropy)
*/
secret(charsCount = 43) {
// Default to 256 bits of entropy, or 43 characters at 6 bits per character.
return this._randomString(charsCount, BASE64_CHARS);
}
/**
* @name Random.choice
* @summary Return a random element of the given array or string.
* @locus Anywhere
* @param {Array|String} arrayOrString Array or string to choose from
*/
choice<TArrayLike extends unknown[] | string>(arrayOrString: TArrayLike) {
const index = Math.floor(this.fraction() * arrayOrString.length);
if (typeof arrayOrString === 'string') {
return arrayOrString.substr(index, 1);
}
return arrayOrString[index];
}
}

View File

@@ -0,0 +1,25 @@
import { AleaRandomGenerator } from './AleaRandomGenerator';
// instantiate RNG. Heuristically collect entropy from various sources when a
// cryptographic PRNG isn't available.
// client sources
const height =
(typeof window !== 'undefined' && window.innerHeight) ||
(typeof document !== 'undefined' && document.documentElement && document.documentElement.clientHeight) ||
(typeof document !== 'undefined' && document.body && document.body.clientHeight) ||
1;
const width =
(typeof window !== 'undefined' && window.innerWidth) ||
(typeof document !== 'undefined' && document.documentElement && document.documentElement.clientWidth) ||
(typeof document !== 'undefined' && document.body && document.body.clientWidth) ||
1;
const agent = (typeof navigator !== 'undefined' && navigator.userAgent) || '';
export function createAleaGeneratorWithGeneratedSeed() {
return new AleaRandomGenerator({
seeds: [new Date(), height, width, agent, Math.random()],
});
}

View File

@@ -0,0 +1,52 @@
import { Random } from './main.client';
it('which should generate the same sequence in all environments', () => {
const random = Random.createWithSeeds(0);
expect(random.id()).toBe('cp9hWvhg8GSvuZ9os');
expect(random.id()).toBe('3f3k6Xo7rrHCifQhR');
expect(random.id()).toBe('shxDnjWWmnKPEoLhM');
expect(random.id()).toBe('6QTjB8C5SEqhmz4ni');
});
describe('format', () => {
it('should output id in the right format', () => {
expect(Random.id(17)).toHaveLength(17);
expect(Random.id(29)).toHaveLength(29);
});
it('should output hexString in the right format', () => {
const hexString = Random.hexString(9);
expect(hexString).toHaveLength(9);
expect(Number.parseInt(hexString, 16)).not.toBeNaN();
});
it('should output fraction in the right range', () => {
const frac = Random.fraction();
expect(frac).toBeLessThan(1.0);
expect(frac).toBeGreaterThanOrEqual(0.0);
});
it('should output secret in the right format', () => {
expect(Random.secret().length).toBe(43);
expect(Random.secret(13).length).toBe(13);
});
});
describe('Alea', () => {
it('should be undefined if window.crypto?.getRandomValues is available', () => {
const useGetRandomValues = !!(typeof window !== 'undefined' && window.crypto?.getRandomValues);
if (useGetRandomValues) {
expect(Random).not.toHaveProperty('alea');
} else {
expect(Random).toHaveProperty('alea');
}
});
});
describe('createWithSeeds', () => {
it('should throw if no seeds are provided', () => {
expect(() => Random.createWithSeeds()).toThrow();
});
});

View File

@@ -0,0 +1,19 @@
// We use cryptographically strong PRNGs (window.crypto.getRandomValues())
// when available. If these PRNGs fail, we fall back to the Alea PRNG, which is
// not cryptographically strong, and we seed it with various sources
// such as the date, Math.random, and window size on the client.
// When using window.crypto.getRandomValues() or alea, the primitive is fraction
// and we use that to construct hex string.
import { BrowserRandomGenerator } from './BrowserRandomGenerator';
import type { RandomGenerator } from './RandomGenerator';
import { createAleaGeneratorWithGeneratedSeed } from './createAleaGenerator';
let generator: RandomGenerator;
if (typeof window !== 'undefined' && !!window.crypto?.getRandomValues) {
generator = new BrowserRandomGenerator();
} else {
generator = createAleaGeneratorWithGeneratedSeed();
}
export const Random = generator;

View File

@@ -0,0 +1,46 @@
import { Random } from './main.server';
it('which should generate the same sequence in all environments', () => {
const random = Random.createWithSeeds(0);
expect(random.id()).toBe('cp9hWvhg8GSvuZ9os');
expect(random.id()).toBe('3f3k6Xo7rrHCifQhR');
expect(random.id()).toBe('shxDnjWWmnKPEoLhM');
expect(random.id()).toBe('6QTjB8C5SEqhmz4ni');
});
describe('format', () => {
it('should output id in the right format', () => {
expect(Random.id(17)).toHaveLength(17);
expect(Random.id(29)).toHaveLength(29);
});
it('should output hexString in the right format', () => {
const hexString = Random.hexString(9);
expect(hexString).toHaveLength(9);
expect(Number.parseInt(hexString, 16)).not.toBeNaN();
});
it('should output fraction in the right range', () => {
const frac = Random.fraction();
expect(frac).toBeLessThan(1.0);
expect(frac).toBeGreaterThanOrEqual(0.0);
});
it('should output secret in the right format', () => {
expect(Random.secret().length).toBe(43);
expect(Random.secret(13).length).toBe(13);
});
});
describe('Alea', () => {
it('should be undefined', () => {
expect(Random).not.toHaveProperty('alea');
});
});
describe('createWithSeeds', () => {
it('should throw if no seeds are provided', () => {
expect(() => Random.createWithSeeds()).toThrow();
});
});

View File

@@ -0,0 +1,7 @@
// We use cryptographically strong PRNGs (crypto.getRandomBytes())
// When using crypto.getRandomValues(), our primitive is hexString(),
// from which we construct fraction().
import { NodeRandomGenerator } from './NodeRandomGenerator';
export const Random = new NodeRandomGenerator();

View File

@@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"target": "ES2015",
"module": "CommonJS",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
},
"exclude": ["**/*.spec.ts"]
}

View File

@@ -0,0 +1,7 @@
{
"extends": "@rocket.chat/tsconfig/base.json",
"compilerOptions": {
"rootDir": "src",
},
"include": ["src"]
}