tiny-essentials 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,572 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ import { objType } from '../../v1/basics/objFilter.mjs';
4
+ // Detecta se estamos no browser
5
+ const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
6
+ /**
7
+ * TinyCrypto is a utility class that provides methods for secure key generation,
8
+ * encryption, and decryption of data. It also allows for serialization
9
+ * and deserialization of complex data types, and offers methods to save and load encryption
10
+ * configurations and keys from files.
11
+ *
12
+ * @class
13
+ */
14
+ class TinyCrypto {
15
+ /**
16
+ * Creates a new instance of the CryptoManager class with configurable options.
17
+ *
18
+ * @param {Object} [options={}] - Configuration options for encryption and decryption.
19
+ * @param {string} [options.algorithm='aes-256-gcm'] - The encryption algorithm to use. Recommended: 'aes-256-gcm' for authenticated encryption.
20
+ * @param {string} [options.outputEncoding='hex'] - The encoding used when returning encrypted data (e.g., 'hex', 'base64').
21
+ * @param {string} [options.inputEncoding='utf8'] - The encoding used for plaintext inputs (e.g., 'utf8').
22
+ * @param {number} [options.authTagLength=16] - The length of the authentication tag used in GCM mode. Usually 16 for AES-256-GCM.
23
+ * @param {Buffer} [options.key] - Optional 32-byte cryptographic key. If not provided, a random key is generated.
24
+ *
25
+ * @throws {Error} Throws if the provided key is not 32 bytes long.
26
+ *
27
+ * @example
28
+ * const crypto = new CryptoManager({
29
+ * algorithm: 'aes-256-gcm',
30
+ * outputEncoding: 'base64',
31
+ * key: crypto.randomBytes(32),
32
+ * });
33
+ */
34
+ constructor(options = {}) {
35
+ this.algorithm = options.algorithm || 'aes-256-gcm';
36
+ this.outputEncoding = options.outputEncoding || 'hex';
37
+ this.inputEncoding = options.inputEncoding || 'utf8';
38
+ this.authTagLength = options.authTagLength || 16;
39
+ this.key = options.key || this.generateKey();
40
+ }
41
+ /**
42
+ * Generates a secure random cryptographic key.
43
+ *
44
+ * @param {number} [value=32] - The number of bytes to generate. Default is 32 bytes (256 bits), suitable for AES-256.
45
+ * @returns {Buffer} A securely generated random key as a Buffer.
46
+ *
47
+ * @example
48
+ * const key = cryptoManager.generateKey(); // Generates a 32-byte key
49
+ * const customKey = cryptoManager.generateKey(16); // Generates a 16-byte key (e.g. for AES-128)
50
+ */
51
+ generateKey(value = 32) {
52
+ return crypto.randomBytes(value); // 256-bit
53
+ }
54
+ /**
55
+ * Generates a secure random Initialization Vector (IV).
56
+ *
57
+ * @param {number} [value=12] - The number of bytes to generate. Default is 12 bytes (96 bits), the recommended size for AES-GCM.
58
+ * @returns {Buffer} A securely generated IV as a Buffer.
59
+ *
60
+ * @example
61
+ * const iv = cryptoManager.generateIV(); // Generates a 12-byte IV
62
+ * const customIV = cryptoManager.generateIV(16); // Generates a 16-byte IV if needed for other algorithms
63
+ */
64
+ generateIV(value = 12) {
65
+ return crypto.randomBytes(value); // 96-bit padrão para GCM
66
+ }
67
+ /**
68
+ * Encrypts a given value (string, number, object, etc.)
69
+ *
70
+ * The value is first serialized de forma segura (preservando o tipo) antes da criptografia.
71
+ *
72
+ * @param {*} data - The data to encrypt. Can be of any supported type (string, number, boolean, Date, JSON, etc.).
73
+ * @param {Buffer} [iv=this.generateIV()] - Optional Initialization Vector (IV). If not provided, a secure random IV is generated.
74
+ * @returns {Object} An object containing:
75
+ * - `iv` {string} - The IV used, encoded with the output encoding.
76
+ * - `encrypted` {string} - The encrypted payload.
77
+ * - `authTag` {string} - The authentication tag used to verify the integrity of the ciphertext.
78
+ *
79
+ * @example
80
+ * const result = cryptoManager.encrypt('Hello, world!');
81
+ * // {
82
+ * // iv: 'b32a...',
83
+ * // encrypted: 'c1d5...',
84
+ * // authTag: 'aa93...'
85
+ * // }
86
+ */
87
+ encrypt(data, iv = this.generateIV()) {
88
+ const plainText = this.#serialize(data);
89
+ const cipher = crypto.createCipheriv(this.algorithm, this.key, iv, {
90
+ authTagLength: this.authTagLength,
91
+ });
92
+ let encrypted = cipher.update(plainText, this.inputEncoding);
93
+ encrypted = Buffer.concat([encrypted, cipher.final()]);
94
+ const authTag = cipher.getAuthTag();
95
+ return {
96
+ iv: iv.toString(this.outputEncoding),
97
+ encrypted: encrypted.toString(this.outputEncoding),
98
+ authTag: authTag.toString(this.outputEncoding),
99
+ };
100
+ }
101
+ /**
102
+ * Decrypts a previously encrypted value.
103
+ *
104
+ * The method checks the integrity of the data using the authentication tag (`authTag`) and ensures the data is properly decrypted.
105
+ * After decryption, it automatically deserializes the data back to its original type.
106
+ *
107
+ * @param {Object} params - An object containing the encrypted data:
108
+ * - `iv` {string} - The Initialization Vector (IV) used in encryption, encoded with the output encoding.
109
+ * - `encrypted` {string} - The encrypted data to decrypt, encoded with the output encoding.
110
+ * - `authTag` {string} - The authentication tag used to verify the integrity of the encrypted data.
111
+ * @param {string|null} [expectedType=null] - Optionally specify the expected type of the decrypted data. If provided, the method will validate the type of the deserialized value.
112
+ * @returns {*} The decrypted value, which will be the original type of the data before encryption.
113
+ * @throws {Error} Throws if the authentication tag doesn't match or the data has been tampered with.
114
+ * @throws {Error} Throws if the deserialized value doesn't match the `expectedType`.
115
+ *
116
+ * @example
117
+ * const encryptedData = {
118
+ * iv: 'b32a...',
119
+ * encrypted: 'c1d5...',
120
+ * authTag: 'aa93...'
121
+ * };
122
+ * const decrypted = cryptoManager.decrypt(encryptedData, 'string');
123
+ * console.log(decrypted); // Outputs: 'Hello, world!'
124
+ */
125
+ decrypt({ iv, encrypted, authTag }, expectedType = null) {
126
+ const ivBuffer = Buffer.from(iv, this.outputEncoding);
127
+ const encryptedBuffer = Buffer.from(encrypted, this.outputEncoding);
128
+ const authTagBuffer = Buffer.from(authTag, this.outputEncoding);
129
+ const decipher = crypto.createDecipheriv(this.algorithm, this.key, ivBuffer, {
130
+ authTagLength: this.authTagLength,
131
+ });
132
+ decipher.setAuthTag(authTagBuffer);
133
+ let decrypted = decipher.update(encryptedBuffer, null, this.inputEncoding);
134
+ decrypted += decipher.final(this.inputEncoding);
135
+ const { value, type } = this.#deserialize(decrypted);
136
+ if (expectedType)
137
+ this.#validateDeserializedType(expectedType, type);
138
+ return value;
139
+ }
140
+ /**
141
+ * Retrieves the type of the original data from an encrypted object.
142
+ *
143
+ * This method decrypts the encrypted data and extracts its type information without fully deserializing the value.
144
+ * It is useful when you need to verify the type of the encrypted data before fully decrypting it.
145
+ *
146
+ * @param {Object} params - An object containing the encrypted data:
147
+ * - `iv` {string} - The Initialization Vector (IV) used in encryption, encoded with the output encoding.
148
+ * - `encrypted` {string} - The encrypted data to decrypt, encoded with the output encoding.
149
+ * - `authTag` {string} - The authentication tag used to verify the integrity of the encrypted data.
150
+ * @returns {string} The type of the original data (e.g., 'string', 'number', 'date', etc.).
151
+ *
152
+ * @example
153
+ * const encryptedData = {
154
+ * iv: 'b32a...',
155
+ * encrypted: 'c1d5...',
156
+ * authTag: 'aa93...'
157
+ * };
158
+ * const dataType = cryptoManager.getTypeFromEncrypted(encryptedData);
159
+ * console.log(dataType); // Outputs: 'string'
160
+ */
161
+ getTypeFromEncrypted({ iv, encrypted, authTag }) {
162
+ const ivBuffer = Buffer.from(iv, this.outputEncoding);
163
+ const encryptedBuffer = Buffer.from(encrypted, this.outputEncoding);
164
+ const authTagBuffer = Buffer.from(authTag, this.outputEncoding);
165
+ const decipher = crypto.createDecipheriv(this.algorithm, this.key, ivBuffer, {
166
+ authTagLength: this.authTagLength,
167
+ });
168
+ decipher.setAuthTag(authTagBuffer);
169
+ let decrypted = decipher.update(encryptedBuffer, null, this.inputEncoding);
170
+ decrypted += decipher.final(this.inputEncoding);
171
+ const { type } = this.#deserialize(decrypted);
172
+ return typeof type === 'string' ? type : 'unknown';
173
+ }
174
+ /**
175
+ * Saves the cryptographic key to a file.
176
+ *
177
+ * If running in a browser, the method generates a download link for the key as a text file.
178
+ * If running in Node.js, the method saves the key to the specified file path.
179
+ *
180
+ * @param {string} [filename='secret.key'] - The name of the file to save the key. Defaults to 'secret.key'.
181
+ * @throws {Error} Throws an error if the file cannot be written in Node.js.
182
+ *
183
+ * @example
184
+ * // In a browser, triggers a download of the key
185
+ * cryptoManager.saveKeyToFile('myKey.key');
186
+ *
187
+ * // In Node.js, saves the key to 'myKey.key'
188
+ * cryptoManager.saveKeyToFile('myKey.key');
189
+ */
190
+ saveKeyToFile(filename = 'secret.key') {
191
+ const data = this.key.toString('hex');
192
+ if (isBrowser) {
193
+ const blob = new Blob([data], { type: 'text/plain' });
194
+ const url = URL.createObjectURL(blob);
195
+ const a = document.createElement('a');
196
+ a.href = url;
197
+ a.download = filename;
198
+ a.click();
199
+ URL.revokeObjectURL(url);
200
+ }
201
+ else
202
+ fs.writeFileSync(filename, data);
203
+ }
204
+ /**
205
+ * Saves the current cryptographic configuration to a JSON file.
206
+ *
207
+ * If running in a browser, the method generates a download link for the configuration as a JSON file.
208
+ * If running in Node.js, the method saves the configuration to the specified file path.
209
+ *
210
+ * @param {string} [filename='crypto-config.json'] - The name of the file to save the configuration. Defaults to 'crypto-config.json'.
211
+ * @throws {Error} Throws an error if the file cannot be written in Node.js.
212
+ *
213
+ * @example
214
+ * // In a browser, triggers a download of the configuration
215
+ * cryptoManager.saveConfigToFile('myConfig.json');
216
+ *
217
+ * // In Node.js, saves the configuration to 'myConfig.json'
218
+ * cryptoManager.saveConfigToFile('myConfig.json');
219
+ */
220
+ saveConfigToFile(filename = 'crypto-config.json') {
221
+ const configData = JSON.stringify(this.exportConfig(), null, 2);
222
+ if (isBrowser) {
223
+ const blob = new Blob([configData], { type: 'application/json' });
224
+ const url = URL.createObjectURL(blob);
225
+ const a = document.createElement('a');
226
+ a.href = url;
227
+ a.download = filename;
228
+ a.click();
229
+ URL.revokeObjectURL(url);
230
+ }
231
+ else
232
+ fs.writeFileSync(filename, configData);
233
+ }
234
+ /**
235
+ * Loads and imports cryptographic configuration from a JSON file.
236
+ *
237
+ * If running in a browser, the method allows the user to select a file, reads the file as text,
238
+ * parses the JSON, and imports the configuration.
239
+ * If running in Node.js, the method reads the file synchronously and imports the configuration.
240
+ *
241
+ * @param {File|string} file - The file to load the configuration from. In the browser, this is a `File` object, and in Node.js, it's a file path.
242
+ * @returns {Promise<void>} A promise that resolves when the configuration is successfully loaded and imported.
243
+ * @throws {Error} Throws an error if the JSON file is invalid or the file cannot be read.
244
+ *
245
+ * @example
246
+ * // In a browser, prompt user to select a file and load the configuration
247
+ * cryptoManager.loadConfigFromFile(file)
248
+ * .then(() => console.log('Config loaded successfully'))
249
+ * .catch(err => console.error('Error loading config:', err));
250
+ *
251
+ * // In Node.js, load the configuration from a file path
252
+ * cryptoManager.loadConfigFromFile('myConfig.json')
253
+ * .then(() => console.log('Config loaded successfully'))
254
+ * .catch(err => console.error('Error loading config:', err));
255
+ */
256
+ async loadConfigFromFile(file) {
257
+ if (isBrowser) {
258
+ return new Promise((resolve, reject) => {
259
+ const reader = new FileReader();
260
+ reader.onload = () => {
261
+ try {
262
+ const config = JSON.parse(reader.result);
263
+ resolve(this.importConfig(config));
264
+ }
265
+ catch (err) {
266
+ reject(new Error('Invalid config JSON file'));
267
+ }
268
+ };
269
+ reader.onerror = () => reject(reader.error);
270
+ reader.readAsText(file);
271
+ });
272
+ }
273
+ else {
274
+ const raw = fs.readFileSync(file, 'utf8');
275
+ const config = JSON.parse(raw);
276
+ return this.importConfig(config);
277
+ }
278
+ }
279
+ /**
280
+ * Loads a cryptographic key from a file and sets it for encryption/decryption.
281
+ *
282
+ * If running in a browser, the method allows the user to select a file, reads the file as text,
283
+ * and loads the key (in hexadecimal format) into the current instance.
284
+ * If running in Node.js, the method reads the file synchronously, parses the hexadecimal key,
285
+ * and loads it into the current instance.
286
+ *
287
+ * @param {File|string} file - The file to load the key from. In the browser, this is a `File` object, and in Node.js, it's a file path.
288
+ * @returns {Promise<Buffer>} A promise that resolves with the key as a `Buffer` when the file is successfully loaded.
289
+ * @throws {Error} Throws an error if the file cannot be read or if the key is invalid.
290
+ *
291
+ * @example
292
+ * // In a browser, prompt user to select a file and load the key
293
+ * cryptoManager.loadKeyFromFile(file)
294
+ * .then(key => console.log('Key loaded successfully:', key))
295
+ * .catch(err => console.error('Error loading key:', err));
296
+ *
297
+ * // In Node.js, load the key from a file path
298
+ * cryptoManager.loadKeyFromFile('myKey.key')
299
+ * .then(key => console.log('Key loaded successfully:', key))
300
+ * .catch(err => console.error('Error loading key:', err));
301
+ */
302
+ async loadKeyFromFile(file) {
303
+ if (isBrowser) {
304
+ return new Promise((resolve, reject) => {
305
+ const reader = new FileReader();
306
+ reader.onload = () => {
307
+ const hexKey = reader.result.trim();
308
+ const keyBuffer = Buffer.from(hexKey, 'hex');
309
+ this.key = keyBuffer;
310
+ resolve(keyBuffer);
311
+ };
312
+ reader.onerror = () => reject(reader.error);
313
+ reader.readAsText(file);
314
+ });
315
+ }
316
+ else {
317
+ const hexKey = fs.readFileSync(file, 'utf8');
318
+ const keyBuffer = Buffer.from(hexKey, 'hex');
319
+ this.key = keyBuffer;
320
+ return keyBuffer;
321
+ }
322
+ }
323
+ /**
324
+ * Exports the current cryptographic configuration as a JSON object.
325
+ *
326
+ * The exported configuration includes the encryption algorithm, output encoding format,
327
+ * input encoding format, the cryptographic key (in hexadecimal format), and the authentication tag length.
328
+ * This method does not include any sensitive data like the raw key, only its hexadecimal representation.
329
+ *
330
+ * @returns {Object} The exported configuration as a plain JavaScript object.
331
+ * @example
332
+ * const config = cryptoManager.exportConfig();
333
+ * console.log(config);
334
+ * // Example output:
335
+ * // {
336
+ * // algorithm: 'aes-256-gcm',
337
+ * // outputEncoding: 'hex',
338
+ * // inputEncoding: 'utf8',
339
+ * // key: 'abcdef1234567890...',
340
+ * // authTagLength: 16
341
+ * // }
342
+ */
343
+ exportConfig() {
344
+ return {
345
+ algorithm: this.algorithm,
346
+ outputEncoding: this.outputEncoding,
347
+ inputEncoding: this.inputEncoding,
348
+ key: this.key.toString('hex'),
349
+ authTagLength: this.authTagLength,
350
+ };
351
+ }
352
+ /**
353
+ * Imports a cryptographic configuration from a JSON object.
354
+ *
355
+ * This method sets the configuration for the encryption process, including the algorithm, encoding formats,
356
+ * authentication tag length, and the cryptographic key (in hexadecimal string format).
357
+ * If any of the expected properties are missing or invalid, an error will be thrown.
358
+ *
359
+ * @param {Object} config - The configuration object to import.
360
+ * @param {string} config.algorithm - The encryption algorithm (e.g., 'aes-256-gcm').
361
+ * @param {string} config.outputEncoding - The output encoding format (e.g., 'hex').
362
+ * @param {string} config.inputEncoding - The input encoding format (e.g., 'utf8').
363
+ * @param {number} config.authTagLength - The authentication tag length (e.g., 16).
364
+ * @param {string} config.key - The cryptographic key in hexadecimal string format.
365
+ *
366
+ * @throws {Error} If any required property is missing or has an invalid type.
367
+ * @example
368
+ * const config = {
369
+ * algorithm: 'aes-256-gcm',
370
+ * outputEncoding: 'hex',
371
+ * inputEncoding: 'utf8',
372
+ * authTagLength: 16,
373
+ * key: 'abcdef1234567890abcdef1234567890',
374
+ * };
375
+ * cryptoManager.importConfig(config);
376
+ */
377
+ importConfig(config) {
378
+ if (typeof config.algorithm === 'string')
379
+ this.algorithm = config.algorithm;
380
+ else if (typeof config.algorithm !== 'undefined')
381
+ throw new Error('Invalid or missing "algorithm" property. Expected a string.');
382
+ if (typeof config.outputEncoding === 'string')
383
+ this.outputEncoding = config.outputEncoding;
384
+ else if (typeof config.outputEncoding !== 'undefined')
385
+ throw new Error('Invalid or missing "outputEncoding" property. Expected a string.');
386
+ if (typeof config.inputEncoding === 'string')
387
+ this.inputEncoding = config.inputEncoding;
388
+ else if (typeof config.inputEncoding !== 'undefined')
389
+ throw new Error('Invalid or missing "inputEncoding" property. Expected a string.');
390
+ if (typeof config.authTagLength === 'number')
391
+ this.authTagLength = config.authTagLength;
392
+ else if (typeof config.authTagLength !== 'undefined')
393
+ throw new Error('Invalid or missing "authTagLength" property. Expected a number.');
394
+ if (typeof config.key === 'string')
395
+ this.key = Buffer.from(config.key, 'hex');
396
+ else if (typeof config.key !== 'undefined')
397
+ throw new Error('Invalid or missing "key" property. Expected a hexadecimal string.');
398
+ }
399
+ /**
400
+ * A mapping of data types to their serialization functions.
401
+ *
402
+ * This object defines how various JavaScript types should be serialized to a JSON-compatible format.
403
+ * It includes handling for primitive types, complex objects, and non-serializable types such as functions
404
+ * and promises, which throw an error when attempted to be serialized.
405
+ *
406
+ * Each key corresponds to a specific data type (e.g., 'number', 'date', 'buffer', etc.),
407
+ * and the value is a function that serializes the data to a specific format.
408
+ *
409
+ * @type {Object}
410
+ * @property {Function} weakmap - Throws an error as WeakMap cannot be serialized.
411
+ * @property {Function} weakset - Throws an error as WeakSet cannot be serialized.
412
+ * @property {Function} promise - Throws an error as Promise cannot be serialized.
413
+ * @property {Function} function - Throws an error as Function cannot be serialized.
414
+ * @property {Function} regexp - Serializes RegExp objects to a JSON object with their string representation.
415
+ * @property {Function} htmlElement - Serializes HTML elements to their outerHTML string.
416
+ * @property {Function} date - Serializes Date objects to an ISO string format.
417
+ * @property {Function} bigint - Serializes BigInt objects to their string representation.
418
+ * @property {Function} number - Serializes numbers to a JSON-compatible format.
419
+ * @property {Function} boolean - Serializes booleans to a JSON-compatible format.
420
+ * @property {Function} string - Serializes strings to a JSON-compatible format.
421
+ * @property {Function} null - Serializes null to a special 'Null' type in JSON.
422
+ * @property {Function} undefined - Serializes undefined to a special 'Undefined' type in JSON.
423
+ * @property {Function} map - Serializes Map objects to an array of entries in JSON format.
424
+ * @property {Function} set - Serializes Set objects to an array of values in JSON format.
425
+ * @property {Function} symbol - Serializes Symbol objects to a JSON object with the symbol's description.
426
+ * @property {Function} array - Serializes arrays to a JSON-compatible format.
427
+ * @property {Function} object - Serializes general objects to a JSON-compatible format.
428
+ * @property {Function} buffer - Serializes Buffer objects to a base64-encoded string.
429
+ */
430
+ #valueConvertTypes = {
431
+ weakmap: () => {
432
+ throw new Error('WeakMap cannot be serialized');
433
+ },
434
+ weakset: () => {
435
+ throw new Error('WeakSet cannot be serialized');
436
+ },
437
+ promise: () => {
438
+ throw new Error('Promise cannot be serialized');
439
+ },
440
+ function: () => {
441
+ throw new Error('Function cannot be serialized');
442
+ },
443
+ regexp: (data) => JSON.stringify({ __type: 'regexp', value: data.toString() }),
444
+ htmlElement: (data) => JSON.stringify({ __type: 'htmlelement', value: data.outerHTML }),
445
+ date: (data) => JSON.stringify({ __type: 'date', value: data.toISOString() }),
446
+ bigint: (data) => JSON.stringify({ __type: 'bigint', value: data.toString() }),
447
+ number: (data) => JSON.stringify({ __type: 'number', value: data }),
448
+ boolean: (data) => JSON.stringify({ __type: 'boolean', value: data }),
449
+ string: (data) => JSON.stringify({ __type: 'string', value: data }),
450
+ null: (data) => JSON.stringify({ __type: 'null' }),
451
+ undefined: (data) => JSON.stringify({ __type: 'undefined' }),
452
+ map: (data) => JSON.stringify({
453
+ __type: 'map',
454
+ value: Array.from(data.entries()),
455
+ }),
456
+ set: (data) => JSON.stringify({
457
+ __type: 'set',
458
+ value: Array.from(data.values()),
459
+ }),
460
+ symbol: (data) => JSON.stringify({ __type: 'symbol', value: data.description }),
461
+ array: (data) => JSON.stringify({ __type: 'array', value: data }),
462
+ object: (data) => JSON.stringify({ __type: 'object', value: data }),
463
+ buffer: (data) => JSON.stringify({ __type: 'buffer', value: data.toString('base64') }),
464
+ };
465
+ /**
466
+ * A mapping of data types to their deserialization functions.
467
+ *
468
+ * This object defines how various serialized types should be deserialized back to their original JavaScript objects.
469
+ * It includes support for primitive types, complex objects, and browser-specific types like `HTMLElement`.
470
+ * Each key corresponds to a specific data type (e.g., 'Date', 'BigInt', 'Buffer', etc.),
471
+ * and the value is a function that deserializes the value to its original format.
472
+ *
473
+ * @type {Object}
474
+ * @property {Function} regexp - Deserializes a regular expression from its string representation (e.g., `/pattern/flags`).
475
+ * @property {Function} htmlelement - Deserializes an HTML element from its serialized outerHTML string (only works in browser environments).
476
+ * @property {Function} date - Deserializes a date from its ISO string representation.
477
+ * @property {Function} bigint - Deserializes a BigInt from its string representation.
478
+ * @property {Function} number - Deserializes a number from its string or numeric representation.
479
+ * @property {Function} boolean - Deserializes a boolean value from its string representation.
480
+ * @property {Function} null - Deserializes the `null` value.
481
+ * @property {Function} undefined - Deserializes the `undefined` value.
482
+ * @property {Function} map - Deserializes a Map from an array of key-value pairs.
483
+ * @property {Function} set - Deserializes a Set from an array of values.
484
+ * @property {Function} symbol - Deserializes a Symbol from its string description.
485
+ * @property {Function} array - Deserializes an array from its serialized representation.
486
+ * @property {Function} object - Deserializes a plain JSON object from its serialized representation.
487
+ * @property {Function} string - Deserializes a string from its serialized representation.
488
+ * @property {Function} buffer - Deserializes a Buffer from its base64-encoded string representation.
489
+ */
490
+ #valueTypes = {
491
+ regexp: (value) => {
492
+ const match = value.match(/^\/(.*)\/([gimsuy]*)$/);
493
+ return match ? new RegExp(match[1], match[2]) : new RegExp(value);
494
+ },
495
+ htmlelement: (value) => {
496
+ if (typeof document === 'undefined')
497
+ throw new Error('HTMLElement deserialization is only supported in browsers');
498
+ const div = document.createElement('div');
499
+ div.innerHTML = value;
500
+ return div.firstElementChild;
501
+ },
502
+ date: (value) => new Date(value),
503
+ bigint: (value) => BigInt(value),
504
+ number: (value) => Number(value),
505
+ boolean: (value) => Boolean(value),
506
+ null: (value) => null,
507
+ undefined: (value) => undefined,
508
+ map: (value) => new Map(value),
509
+ set: (value) => new Set(value),
510
+ symbol: (value) => Symbol(value),
511
+ array: (value) => value,
512
+ object: (value) => value,
513
+ string: (value) => String(value),
514
+ buffer: (value) => Buffer.from(value, 'base64'),
515
+ };
516
+ /**
517
+ * Serializes a given data value into a JSON-compatible format based on its type.
518
+ * This method converts various JavaScript data types into their serialized representation
519
+ * that can be encrypted or stored. If the data type is unsupported, an error is thrown.
520
+ *
521
+ * @param {any} data - The data to be serialized.
522
+ * @returns {string} The serialized data in JSON format.
523
+ * @throws {Error} If the data type is unsupported for serialization.
524
+ */
525
+ #serialize(data) {
526
+ const type = objType(data) || 'undefined';
527
+ if (this.#valueConvertTypes[type])
528
+ return this.#valueConvertTypes[type](data);
529
+ throw new Error(`Unsupported data type for encryption: ${type}`);
530
+ }
531
+ /**
532
+ * Deserializes a string back into its original JavaScript object based on the serialized type information.
533
+ * This method checks the serialized type and converts the string back to its original JavaScript object
534
+ * (such as a `Date`, `Buffer`, `RegExp`, etc.). If the type is unknown or unsupported, it returns the raw value.
535
+ *
536
+ * @param {string} text - The serialized data to be deserialized.
537
+ * @returns {{value: any, type: string}} An object containing the deserialized value and its type.
538
+ * @throws {Error} If deserialization fails due to an invalid or unknown type.
539
+ */
540
+ #deserialize(text) {
541
+ try {
542
+ const parsed = JSON.parse(text);
543
+ const type = parsed.__type;
544
+ if (typeof type !== 'string')
545
+ return { value: text, type: 'string' };
546
+ if (typeof this.#valueTypes[type] === 'function')
547
+ return {
548
+ value: this.#valueTypes[type](parsed.value),
549
+ type,
550
+ };
551
+ else
552
+ return { value: text, type: 'Unknown' };
553
+ }
554
+ catch {
555
+ return { value: text, type: 'Unknown' };
556
+ }
557
+ }
558
+ /**
559
+ * Validates that the actual type of a deserialized value matches the expected type.
560
+ * This method ensures that the type of the deserialized data matches what is expected,
561
+ * throwing an error if there's a mismatch.
562
+ *
563
+ * @param {string} expected - The expected type of the deserialized data.
564
+ * @param {string} actual - The actual type of the deserialized data.
565
+ * @throws {Error} If the types do not match.
566
+ */
567
+ #validateDeserializedType(expected, actual) {
568
+ if (expected !== actual)
569
+ throw new Error(`Type mismatch: expected ${expected}, but got ${actual}`);
570
+ }
571
+ }
572
+ export default TinyCrypto;
package/docs/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Documentation Menu
2
+
3
+ Welcome to the documentation! This is the central hub for exploring all available resources.
4
+
5
+ Here, you can navigate through different sections of the project. Below are the main directories:
6
+
7
+ ---
8
+
9
+ ## 📚 Main Directories
10
+
11
+ ### 1. **`libs/`**
12
+
13
+ This folder contains external libraries and utility modules that are designed to help with various tasks and features in the project.
14
+
15
+ - 🔐 **[TinyCrypto.md](./libs/TinyCrypto.md)** — A flexible and browser-compatible AES-256-GCM encryption utility with support for complex JavaScript types like `Date`, `RegExp`, `Buffer`, and even DOM elements (in the browser).
16
+
17
+ ---
18
+
19
+ ### 2. **`basics/`**
20
+
21
+ This folder contains the core scripts we have worked on so far. Each file is a module focused on specific functionality.
22
+
23
+ - 📦 **[array.md](./basics/array.md)** — A tiny utility for shuffling arrays using the Fisher–Yates algorithm.
24
+ - ⏰ **[clock.md](./basics/clock.md)** — A versatile time utility module for calculating and formatting time durations.
25
+ - 🧠 **[objFilter.md](./basics/objFilter.md)** — Type detection, extension, and analysis made easy with simple and extensible type validation.
26
+ - 🔢 **[simpleMath.md](./basics/simpleMath.md)** — A collection of simple math utilities for calculations like the Rule of Three and percentages.
27
+ - ✍️ **[text.md](./basics/text.md)** — A utility for transforming text into title case formats, with multiple options for capitalization.
28
+
29
+ ---
30
+
31
+ ## 🚀 Usage
32
+
33
+ To get started, navigate to the appropriate directory and explore the files listed. Each script includes detailed documentation on how to use the respective functionality.
34
+
35
+ ---
36
+
37
+ ## 📑 Contributing
38
+
39
+ Feel free to suggest changes, improvements, or additional features. You can fork the repository and submit a pull request!
@@ -0,0 +1,46 @@
1
+ # 📦 array.mjs
2
+
3
+ A tiny utility for shuffling arrays using the good old Fisher–Yates algorithm. Simple, efficient, and perfectly random (as far as JavaScript's `Math.random()` allows, anyway).
4
+
5
+ ## ✨ Features
6
+
7
+ - 📚 Uses the classic **Fisher–Yates** algorithm
8
+ - 🔄 Shuffles arrays **in-place**
9
+ - 🎯 Guarantees **uniform distribution** of permutations
10
+ - 🔍 Well-documented and easy to read
11
+ - 🧪 No dependencies — just plug and play
12
+
13
+ ## 🚀 Usage
14
+
15
+ ```js
16
+ import { shuffleArray } from './array.mjs';
17
+
18
+ const fruits = ['apple', 'banana', 'cherry', 'date'];
19
+ shuffleArray(fruits);
20
+
21
+ console.log(fruits); // ['banana', 'cherry', 'apple', 'date'] (order will vary)
22
+ ```
23
+
24
+ > Note: The original array is shuffled in place. If you want to preserve the original order, make a copy before shuffling.
25
+
26
+ ## 🧠 How It Works
27
+
28
+ This function implements the **Fisher–Yates shuffle** (also known as the Knuth shuffle), which runs in linear time and guarantees uniform randomness:
29
+
30
+ ```js
31
+ while (currentIndex !== 0) {
32
+ randomIndex = Math.floor(Math.random() * currentIndex);
33
+ currentIndex--;
34
+ [items[currentIndex], items[randomIndex]] = [items[randomIndex], items[currentIndex]];
35
+ }
36
+ ```
37
+
38
+ You can find the original discussion here:
39
+ 🔗 https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
40
+
41
+ ## 📁 API
42
+
43
+ ### `shuffleArray(items: string[]): string[]`
44
+
45
+ - **items** — An array of strings to shuffle.
46
+ - **Returns** — The same array instance, but now shuffled.