1 import crypto from
'crypto';
3 import clone from
'just-clone';
5 import { WebSocketCloseEventStatusString
} from
'../types/WebSocket';
7 export default class Utils
{
8 private constructor() {
12 public static logPrefix(prefixString
= ''): string {
13 return new Date().toLocaleString() + prefixString
;
16 public static generateUUID(): string {
17 return crypto
.randomUUID();
20 public static validateUUID(uuid
: string): boolean {
21 return /^[0-9a
-fA
-F
]{8}-[0-9a
-fA
-F
]{4}-4[0-9a
-fA
-F
]{3}-[0-9a
-fA
-F
]{4}-[0-9a
-fA
-F
]{12}$
/.test(
26 public static async sleep(milliSeconds
: number): Promise
<NodeJS
.Timeout
> {
27 return new Promise((resolve
) => setTimeout(resolve
as () => void, milliSeconds
));
30 public static formatDurationMilliSeconds(duration
: number): string {
31 duration
= Utils
.convertToInt(duration
);
32 const hours
= Math.floor(duration
/ (3600 * 1000));
33 const minutes
= Math.floor((duration
/ 1000 - hours
* 3600) / 60);
34 const seconds
= duration
/ 1000 - hours
* 3600 - minutes
* 60;
35 let hoursStr
= hours
.toString();
36 let minutesStr
= minutes
.toString();
37 let secondsStr
= seconds
.toString();
40 hoursStr
= '0' + hours
.toString();
43 minutesStr
= '0' + minutes
.toString();
46 secondsStr
= '0' + seconds
.toString();
48 return hoursStr
+ ':' + minutesStr
+ ':' + secondsStr
.substring(0, 6);
51 public static formatDurationSeconds(duration
: number): string {
52 return Utils
.formatDurationMilliSeconds(duration
* 1000);
55 public static convertToDate(
56 value
: Date | string | number | null | undefined
57 ): Date | null | undefined {
58 if (Utils
.isNullOrUndefined(value
)) {
59 return value
as null | undefined;
61 if (value
instanceof Date) {
64 if (Utils
.isString(value
) || typeof value
=== 'number') {
65 return new Date(value
);
70 public static convertToInt(value
: unknown
): number {
74 let changedValue
: number = value
as number;
75 if (Number.isSafeInteger(value
)) {
76 return value
as number;
78 if (typeof value
=== 'number') {
79 return Math.trunc(value
);
81 if (Utils
.isString(value
)) {
82 changedValue
= parseInt(value
as string);
84 if (isNaN(changedValue
)) {
85 throw new Error(`Cannot convert to integer: ${value.toString()}`);
90 public static convertToFloat(value
: unknown
): number {
94 let changedValue
: number = value
as number;
95 if (Utils
.isString(value
)) {
96 changedValue
= parseFloat(value
as string);
98 if (isNaN(changedValue
)) {
99 throw new Error(`Cannot convert to float: ${value.toString()}`);
104 public static convertToBoolean(value
: unknown
): boolean {
108 if (typeof value
=== 'boolean') {
111 Utils
.isString(value
) &&
112 ((value
as string).toLowerCase() === 'true' || value
=== '1')
115 } else if (typeof value
=== 'number' && value
=== 1) {
122 public static getRandomFloat(max
= Number.MAX_VALUE
, min
= 0, negative
= false): number {
123 if (max
< min
|| max
< 0 || min
< 0) {
124 throw new RangeError('Invalid interval');
126 const randomPositiveFloat
= crypto
.randomBytes(4).readUInt32LE() / 0xffffffff;
127 const sign
= negative
&& randomPositiveFloat
< 0.5 ? -1 : 1;
128 return sign
* (randomPositiveFloat
* (max
- min
) + min
);
131 public static getRandomInteger(max
= Number.MAX_SAFE_INTEGER
, min
= 0): number {
132 if (max
< min
|| max
< 0 || min
< 0) {
133 throw new RangeError('Invalid interval');
135 max
= Math.floor(max
);
136 if (!Utils
.isNullOrUndefined(min
) && min
!== 0) {
137 min
= Math.ceil(min
);
138 return Math.floor(Utils
.secureRandom() * (max
- min
+ 1)) + min
;
140 return Math.floor(Utils
.secureRandom() * (max
+ 1));
143 public static roundTo(numberValue
: number, scale
: number): number {
144 const roundPower
= Math.pow(10, scale
);
145 return Math.round(numberValue
* roundPower
) / roundPower
;
148 public static truncTo(numberValue
: number, scale
: number): number {
149 const truncPower
= Math.pow(10, scale
);
150 return Math.trunc(numberValue
* truncPower
) / truncPower
;
153 public static getRandomFloatRounded(max
= Number.MAX_VALUE
, min
= 0, scale
= 2): number {
155 return Utils
.roundTo(Utils
.getRandomFloat(max
, min
), scale
);
157 return Utils
.roundTo(Utils
.getRandomFloat(max
), scale
);
160 public static getRandomFloatFluctuatedRounded(
162 fluctuationPercent
: number,
165 if (fluctuationPercent
=== 0) {
166 return Utils
.roundTo(staticValue
, scale
);
168 const fluctuationRatio
= fluctuationPercent
/ 100;
169 return Utils
.getRandomFloatRounded(
170 staticValue
* (1 + fluctuationRatio
),
171 staticValue
* (1 - fluctuationRatio
),
176 public static isObject(item
: unknown
): boolean {
178 Utils
.isNullOrUndefined(item
) === false &&
179 typeof item
=== 'object' &&
180 Array.isArray(item
) === false
184 public static cloneObject
<T
extends object
>(object
: T
): T
{
185 return clone
<T
>(object
);
188 public static isIterable
<T
>(obj
: T
): boolean {
189 return obj
? typeof obj
[Symbol
.iterator
] === 'function' : false;
192 public static isString(value
: unknown
): boolean {
193 return typeof value
=== 'string';
196 public static isEmptyString(value
: unknown
): boolean {
197 return Utils
.isString(value
) && (value
as string).trim().length
=== 0;
200 public static isUndefined(value
: unknown
): boolean {
201 return typeof value
=== 'undefined';
204 public static isNullOrUndefined(value
: unknown
): boolean {
205 // eslint-disable-next-line eqeqeq, no-eq-null
206 return value
== null ? true : false;
209 public static isEmptyArray(object
: unknown
| unknown
[]): boolean {
210 if (!Array.isArray(object
)) {
213 if (object
.length
> 0) {
219 public static isEmptyObject(obj
: object
): boolean {
220 if (obj
?.constructor
!== Object) {
223 // Iterates over the keys of an object, if
224 // any exist, return false.
225 for (const _
in obj
) {
231 public static insertAt
= (str
: string, subStr
: string, pos
: number): string =>
232 `${str.slice(0, pos)}${subStr}${str.slice(pos)}`;
235 * @param retryNumber - the number of retries that have already been attempted
236 * @returns delay in milliseconds
238 public static exponentialDelay(retryNumber
= 0): number {
239 const delay
= Math.pow(2, retryNumber
) * 100;
240 const randomSum
= delay
* 0.2 * Utils
.secureRandom(); // 0-20% of the delay
241 return delay
+ randomSum
;
244 public static async promiseWithTimeout
<T
>(
248 timeoutCallback
: () => void = () => {
249 /* This is intentional */
252 // Create a timeout promise that rejects in timeout milliseconds
253 const timeoutPromise
= new Promise
<never>((_
, reject
) => {
256 reject(timeoutError
);
260 // Returns a race between timeout promise and the passed promise
261 return Promise
.race
<T
>([promise
, timeoutPromise
]);
265 * Generate a cryptographically secure random number in the [0,1[ range
269 public static secureRandom(): number {
270 return crypto
.randomBytes(4).readUInt32LE() / 0x100000000;
273 public static JSONStringifyWithMapSupport(
274 obj
: Record
<string, unknown
> | Record
<string, unknown
>[] | Map
<unknown
, unknown
>,
277 return JSON
.stringify(
279 (key
, value
: Record
<string, unknown
>) => {
280 if (value
instanceof Map
) {
293 * Convert websocket error code to human readable string message
295 * @param code - websocket error code
296 * @returns human readable string message
298 public static getWebSocketCloseEventStatusString(code
: number): string {
299 if (code
>= 0 && code
<= 999) {
301 } else if (code
>= 1016) {
303 return '(For WebSocket standard)';
304 } else if (code
<= 2999) {
305 return '(For WebSocket extensions)';
306 } else if (code
<= 3999) {
307 return '(For libraries and frameworks)';
308 } else if (code
<= 4999) {
309 return '(For applications)';
312 if (!Utils
.isUndefined(WebSocketCloseEventStatusString
[code
])) {
313 return WebSocketCloseEventStatusString
[code
] as string;