Secure random integer generator inputs
[e-mobility-charging-stations-simulator.git] / src / utils / Utils.ts
CommitLineData
a4624c96 1import Configuration from './Configuration';
32a1eb7a 2import { WebSocketCloseEventStatusString } from '../types/WebSocket';
a4624c96 3import { WorkerProcessType } from '../types/Worker';
c37528f1 4import crypto from 'crypto';
6af9012e 5import { v4 as uuid } from 'uuid';
7dde0b73 6
3f40bc9c 7export default class Utils {
0f16a662 8 public static logPrefix(prefixString = ''): string {
147d0e0f
JB
9 return new Date().toLocaleString() + prefixString;
10 }
11
0f16a662 12 public static generateUUID(): string {
2e3ac96d 13 return uuid();
7dde0b73
JB
14 }
15
0f16a662 16 public static async sleep(milliSeconds: number): Promise<NodeJS.Timeout> {
9ac86a7e 17 return new Promise((resolve) => setTimeout(resolve, milliSeconds));
7dde0b73
JB
18 }
19
0f16a662 20 public static secondsToHHMMSS(seconds: number): string {
a9dc13ad 21 return Utils.milliSecondsToHHMMSS(seconds * 1000);
9ac86a7e
JB
22 }
23
0f16a662 24 public static milliSecondsToHHMMSS(milliSeconds: number): string {
9ac86a7e 25 return new Date(milliSeconds).toISOString().substr(11, 8);
7dde0b73
JB
26 }
27
0f16a662 28 public static removeExtraEmptyLines(tab: string[]): void {
7dde0b73
JB
29 // Start from the end
30 for (let i = tab.length - 1; i > 0; i--) {
31 // Two consecutive empty lines?
32 if (tab[i].length === 0 && tab[i - 1].length === 0) {
33 // Remove the last one
34 tab.splice(i, 1);
35 }
36 // Check last line
37 if (i === 1 && tab[i - 1].length === 0) {
38 // Remove the first one
39 tab.splice(i - 1, 1);
40 }
41 }
42 }
43
0f16a662 44 public static convertToDate(value: unknown): Date {
560bcf5b 45 // Check
6af9012e 46 if (!value) {
73d09045 47 return value as Date;
560bcf5b
JB
48 }
49 // Check Type
6af9012e 50 if (!(value instanceof Date)) {
73d09045 51 return new Date(value as string);
7dde0b73 52 }
6af9012e 53 return value;
7dde0b73
JB
54 }
55
0f16a662 56 public static convertToInt(value: unknown): number {
73d09045 57 let changedValue: number = value as number;
72766a82 58 if (!value) {
7dde0b73
JB
59 return 0;
60 }
72766a82 61 if (Number.isSafeInteger(value)) {
95926c9b 62 return value as number;
72766a82 63 }
7dde0b73 64 // Check
087a502d 65 if (Utils.isString(value)) {
7dde0b73 66 // Create Object
73d09045 67 changedValue = parseInt(value as string);
7dde0b73 68 }
72766a82 69 return changedValue;
7dde0b73
JB
70 }
71
0f16a662 72 public static convertToFloat(value: unknown): number {
73d09045 73 let changedValue: number = value as number;
72766a82 74 if (!value) {
7dde0b73
JB
75 return 0;
76 }
77 // Check
087a502d 78 if (Utils.isString(value)) {
7dde0b73 79 // Create Object
73d09045 80 changedValue = parseFloat(value as string);
7dde0b73 81 }
72766a82 82 return changedValue;
7dde0b73
JB
83 }
84
0f16a662 85 public static convertToBoolean(value: unknown): boolean {
a6e68f34
JB
86 let result = false;
87 // Check boolean
88 if (value) {
89 // Check the type
90 if (typeof value === 'boolean') {
91 // Already a boolean
92 result = value;
93 } else {
94 // Convert
95 result = (value === 'true');
96 }
97 }
98 return result;
99 }
100
fd00fa2e
JB
101 public static getRandomFloat(max: number, min = 0, negative = false): number {
102 const randomPositiveFloat = crypto.randomBytes(4).readUInt32LE() / 0xffffffff;
103 const sign = (negative && randomPositiveFloat < 0.5) ? 1 : -1;
104 return sign * (randomPositiveFloat * (max - min) + min);
560bcf5b
JB
105 }
106
0f16a662 107 public static getRandomInt(max: number, min = 0): number {
fd00fa2e 108 max = Math.floor(max);
7dde0b73 109 if (min) {
fd00fa2e 110 min = Math.ceil(min);
c37528f1 111 return Math.floor(Utils.secureRandom() * (max - min + 1)) + min;
7dde0b73 112 }
c37528f1 113 return Math.floor(Utils.secureRandom() * (max + 1));
560bcf5b
JB
114 }
115
0f16a662 116 public static roundTo(numberValue: number, scale: number): number {
ad3de6c4 117 const roundPower = Math.pow(10, scale);
035742f7 118 return Math.round(numberValue * roundPower) / roundPower;
560bcf5b
JB
119 }
120
0f16a662 121 public static truncTo(numberValue: number, scale: number): number {
6d3a11a0 122 const truncPower = Math.pow(10, scale);
035742f7 123 return Math.trunc(numberValue * truncPower) / truncPower;
6d3a11a0
JB
124 }
125
0f16a662 126 public static getRandomFloatRounded(max: number, min = 0, scale = 2): number {
560bcf5b
JB
127 if (min) {
128 return Utils.roundTo(Utils.getRandomFloat(max, min), scale);
129 }
130 return Utils.roundTo(Utils.getRandomFloat(max), scale);
7dde0b73
JB
131 }
132
0f16a662 133 public static getRandomFloatFluctuatedRounded(staticValue: number, fluctuationPercent: number, scale = 2): number {
9ccca265
JB
134 if (fluctuationPercent === 0) {
135 return Utils.roundTo(staticValue, scale);
136 }
97ef739c
JB
137 const fluctuationRatio = fluctuationPercent / 100;
138 return Utils.getRandomFloatRounded(staticValue * (1 + fluctuationRatio), staticValue * (1 - fluctuationRatio), scale);
9ccca265
JB
139 }
140
0f16a662 141 public static cloneObject<T>(object: T): T {
e56aa9a4 142 return JSON.parse(JSON.stringify(object)) as T;
2e6f5966 143 }
facd8ebd 144
0f16a662 145 public static isIterable<T>(obj: T): boolean {
67e9cccf
JB
146 if (obj) {
147 return typeof obj[Symbol.iterator] === 'function';
148 }
149 return false;
150 }
151
0f16a662 152 public static isEmptyJSon(document: unknown): boolean {
67e9cccf
JB
153 // Empty?
154 if (!document) {
155 return true;
156 }
157 // Check type
158 if (typeof document !== 'object') {
159 return true;
160 }
161 // Check
162 return Object.keys(document).length === 0;
163 }
164
0f16a662 165 public static isString(value: unknown): boolean {
560bcf5b
JB
166 return typeof value === 'string';
167 }
168
0f16a662 169 public static isUndefined(value: unknown): boolean {
ead548f2
JB
170 return typeof value === 'undefined';
171 }
172
0f16a662 173 public static isNullOrUndefined(value: unknown): boolean {
32a1eb7a 174 // eslint-disable-next-line no-eq-null, eqeqeq
ead548f2 175 if (value == null) {
facd8ebd
JB
176 return true;
177 }
178 return false;
179 }
4a56deef 180
0f16a662 181 public static isEmptyArray(object: unknown): boolean {
fd1ee77c
JB
182 if (!object) {
183 return true;
184 }
4a56deef
JB
185 if (Array.isArray(object) && object.length > 0) {
186 return false;
187 }
188 return true;
189 }
7abfea5f 190
0f16a662 191 public static isEmptyObject(obj: Record<string, unknown>): boolean {
7abfea5f
JB
192 return !Object.keys(obj).length;
193 }
7ec46a9a 194
0f16a662 195 public static insertAt = (str: string, subStr: string, pos: number): string => `${str.slice(0, pos)}${subStr}${str.slice(pos)}`;
032d6efc
JB
196
197 /**
81797102
JB
198 * @param [retryNumber=0]
199 * @returns delay in milliseconds
032d6efc 200 */
0f16a662 201 public static exponentialDelay(retryNumber = 0): number {
032d6efc 202 const delay = Math.pow(2, retryNumber) * 100;
c37528f1 203 const randomSum = delay * 0.2 * Utils.secureRandom(); // 0-20% of the delay
032d6efc
JB
204 return delay + randomSum;
205 }
32a1eb7a 206
e71cccf3
JB
207 /**
208 * Convert websocket error code to human readable string message
209 *
81797102
JB
210 * @param code websocket error code
211 * @returns human readable string message
e71cccf3 212 */
0f16a662 213 public static getWebSocketCloseEventStatusString(code: number): string {
32a1eb7a
JB
214 if (code >= 0 && code <= 999) {
215 return '(Unused)';
216 } else if (code >= 1016) {
217 if (code <= 1999) {
218 return '(For WebSocket standard)';
219 } else if (code <= 2999) {
220 return '(For WebSocket extensions)';
221 } else if (code <= 3999) {
222 return '(For libraries and frameworks)';
223 } else if (code <= 4999) {
224 return '(For applications)';
225 }
226 }
227 if (!Utils.isUndefined(WebSocketCloseEventStatusString[code])) {
17991e8c 228 return WebSocketCloseEventStatusString[code] as string;
32a1eb7a
JB
229 }
230 return '(Unknown)';
231 }
a4624c96 232
0f16a662 233 public static workerPoolInUse(): boolean {
9ab7950c 234 return [WorkerProcessType.DYNAMIC_POOL, WorkerProcessType.STATIC_POOL].includes(Configuration.getWorkerProcess());
a4624c96 235 }
059f35a5 236
0f16a662 237 public static workerDynamicPoolInUse(): boolean {
059f35a5
JB
238 return Configuration.getWorkerProcess() === WorkerProcessType.DYNAMIC_POOL;
239 }
c37528f1
JB
240
241 /**
0f16a662 242 * Generate a cryptographically secure random number in the [0,1[ range
c37528f1
JB
243 *
244 * @returns
245 */
0f16a662 246 public static secureRandom(): number {
c37528f1
JB
247 return crypto.randomBytes(4).readUInt32LE() / 0x100000000;
248 }
7dde0b73 249}