@bantis/local-cipher 1.0.1 → 2.0.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.
package/dist/index.js CHANGED
@@ -5,16 +5,64 @@ var core = require('@angular/core');
5
5
  var rxjs = require('rxjs');
6
6
  var operators = require('rxjs/operators');
7
7
 
8
+ /**
9
+ * Configuration types for @bantis/local-cipher
10
+ */
11
+ /**
12
+ * Default configuration values
13
+ */
14
+ const DEFAULT_CONFIG = {
15
+ encryption: {
16
+ iterations: 100000,
17
+ saltLength: 16,
18
+ ivLength: 12,
19
+ appIdentifier: 'bantis-local-cipher-v2',
20
+ keyLength: 256,
21
+ },
22
+ storage: {
23
+ compression: true,
24
+ compressionThreshold: 1024,
25
+ autoCleanup: true,
26
+ cleanupInterval: 60000,
27
+ },
28
+ debug: {
29
+ enabled: false,
30
+ logLevel: 'info',
31
+ prefix: 'SecureStorage',
32
+ },
33
+ };
34
+ /**
35
+ * Current library version
36
+ */
37
+ const LIBRARY_VERSION = '2.0.0';
38
+ /**
39
+ * Storage data version
40
+ */
41
+ const STORAGE_VERSION = 2;
42
+
8
43
  /**
9
44
  * EncryptionHelper - Clase responsable de todas las operaciones criptográficas
10
45
  * Implementa AES-256-GCM con derivación de claves PBKDF2 y fingerprinting del navegador
11
46
  */
12
47
  class EncryptionHelper {
13
- constructor() {
48
+ constructor(config) {
14
49
  // Propiedades privadas
15
50
  this.key = null;
16
51
  this.baseKey = '';
17
52
  this.baseKeyPromise = null;
53
+ this.keyVersion = 1;
54
+ this.config = { ...DEFAULT_CONFIG.encryption, ...config };
55
+ // Load key version from storage
56
+ const storedVersion = localStorage.getItem(EncryptionHelper.KEY_VERSION_KEY);
57
+ if (storedVersion) {
58
+ this.keyVersion = parseInt(storedVersion, 10);
59
+ }
60
+ }
61
+ /**
62
+ * Get current key version
63
+ */
64
+ getKeyVersion() {
65
+ return this.keyVersion;
18
66
  }
19
67
  /**
20
68
  * Genera un fingerprint único del navegador
@@ -32,7 +80,7 @@ class EncryptionHelper {
32
80
  screen.height.toString(),
33
81
  screen.colorDepth.toString(),
34
82
  new Intl.DateTimeFormat().resolvedOptions().timeZone,
35
- EncryptionHelper.APP_IDENTIFIER,
83
+ this.config.appIdentifier,
36
84
  ];
37
85
  const fingerprint = components.join('|');
38
86
  this.baseKey = await this.hashString(fingerprint);
@@ -66,11 +114,11 @@ class EncryptionHelper {
66
114
  return crypto.subtle.deriveKey({
67
115
  name: 'PBKDF2',
68
116
  salt,
69
- iterations: EncryptionHelper.ITERATIONS,
117
+ iterations: this.config.iterations,
70
118
  hash: EncryptionHelper.HASH_ALGORITHM,
71
119
  }, keyMaterial, {
72
120
  name: EncryptionHelper.ALGORITHM,
73
- length: EncryptionHelper.KEY_LENGTH,
121
+ length: this.config.keyLength,
74
122
  }, false, ['encrypt', 'decrypt']);
75
123
  }
76
124
  /**
@@ -78,11 +126,14 @@ class EncryptionHelper {
78
126
  */
79
127
  async initialize() {
80
128
  // Generar salt aleatorio
81
- const salt = crypto.getRandomValues(new Uint8Array(EncryptionHelper.SALT_LENGTH));
129
+ const salt = crypto.getRandomValues(new Uint8Array(this.config.saltLength));
82
130
  // Obtener y hashear el baseKey
83
131
  const baseKey = await this.generateBaseKey();
84
132
  // Derivar la clave
85
133
  this.key = await this.deriveKey(baseKey, salt);
134
+ // Increment key version
135
+ this.keyVersion++;
136
+ localStorage.setItem(EncryptionHelper.KEY_VERSION_KEY, this.keyVersion.toString());
86
137
  // Guardar el salt en localStorage
87
138
  localStorage.setItem(EncryptionHelper.SALT_STORAGE_KEY, this.arrayBufferToBase64(salt.buffer));
88
139
  }
@@ -119,7 +170,7 @@ class EncryptionHelper {
119
170
  const encoder = new TextEncoder();
120
171
  const data = encoder.encode(plaintext);
121
172
  // Generar IV aleatorio
122
- const iv = crypto.getRandomValues(new Uint8Array(EncryptionHelper.IV_LENGTH));
173
+ const iv = crypto.getRandomValues(new Uint8Array(this.config.ivLength));
123
174
  // Encriptar
124
175
  const encryptedBuffer = await crypto.subtle.encrypt({
125
176
  name: EncryptionHelper.ALGORITHM,
@@ -148,8 +199,8 @@ class EncryptionHelper {
148
199
  // Decodificar de Base64
149
200
  const combined = new Uint8Array(this.base64ToArrayBuffer(ciphertext));
150
201
  // Extraer IV y datos encriptados
151
- const iv = combined.slice(0, EncryptionHelper.IV_LENGTH);
152
- const encryptedData = combined.slice(EncryptionHelper.IV_LENGTH);
202
+ const iv = combined.slice(0, this.config.ivLength);
203
+ const encryptedData = combined.slice(this.config.ivLength);
153
204
  // Desencriptar
154
205
  const decryptedBuffer = await crypto.subtle.decrypt({
155
206
  name: EncryptionHelper.ALGORITHM,
@@ -227,92 +278,644 @@ class EncryptionHelper {
227
278
  .join('');
228
279
  }
229
280
  }
230
- // Constantes criptográficas
281
+ // Constantes criptográficas (ahora configurables)
231
282
  EncryptionHelper.ALGORITHM = 'AES-GCM';
232
- EncryptionHelper.KEY_LENGTH = 256;
233
- EncryptionHelper.IV_LENGTH = 12; // 96 bits para GCM
234
- EncryptionHelper.SALT_LENGTH = 16; // 128 bits
235
- EncryptionHelper.ITERATIONS = 100000;
236
283
  EncryptionHelper.HASH_ALGORITHM = 'SHA-256';
237
284
  EncryptionHelper.SALT_STORAGE_KEY = '__app_salt';
238
- EncryptionHelper.APP_IDENTIFIER = 'mtt-local-cipher-v1'; // Identificador único de la app
285
+ EncryptionHelper.KEY_VERSION_KEY = '__key_version';
239
286
 
240
287
  /**
241
- * SecureStorage - API de alto nivel que imita localStorage con cifrado automático
242
- * Implementa el patrón Singleton
288
+ * Simple event emitter for storage events
243
289
  */
244
- class SecureStorage {
290
+ class EventEmitter {
245
291
  constructor() {
246
- this.encryptionHelper = new EncryptionHelper();
292
+ this.listeners = new Map();
293
+ this.onceListeners = new Map();
294
+ }
295
+ /**
296
+ * Register an event listener
297
+ */
298
+ on(event, listener) {
299
+ if (!this.listeners.has(event)) {
300
+ this.listeners.set(event, new Set());
301
+ }
302
+ this.listeners.get(event).add(listener);
303
+ }
304
+ /**
305
+ * Register a one-time event listener
306
+ */
307
+ once(event, listener) {
308
+ if (!this.onceListeners.has(event)) {
309
+ this.onceListeners.set(event, new Set());
310
+ }
311
+ this.onceListeners.get(event).add(listener);
312
+ }
313
+ /**
314
+ * Remove an event listener
315
+ */
316
+ off(event, listener) {
317
+ this.listeners.get(event)?.delete(listener);
318
+ this.onceListeners.get(event)?.delete(listener);
319
+ }
320
+ /**
321
+ * Remove all listeners for an event
322
+ */
323
+ removeAllListeners(event) {
324
+ if (event) {
325
+ this.listeners.delete(event);
326
+ this.onceListeners.delete(event);
327
+ }
328
+ else {
329
+ this.listeners.clear();
330
+ this.onceListeners.clear();
331
+ }
332
+ }
333
+ /**
334
+ * Emit an event
335
+ */
336
+ emit(event, data) {
337
+ const eventData = {
338
+ type: event,
339
+ timestamp: Date.now(),
340
+ ...data,
341
+ };
342
+ // Call regular listeners
343
+ this.listeners.get(event)?.forEach(listener => {
344
+ try {
345
+ listener(eventData);
346
+ }
347
+ catch (error) {
348
+ console.error(`Error in event listener for ${event}:`, error);
349
+ }
350
+ });
351
+ // Call and remove once listeners
352
+ const onceSet = this.onceListeners.get(event);
353
+ if (onceSet) {
354
+ onceSet.forEach(listener => {
355
+ try {
356
+ listener(eventData);
357
+ }
358
+ catch (error) {
359
+ console.error(`Error in once listener for ${event}:`, error);
360
+ }
361
+ });
362
+ this.onceListeners.delete(event);
363
+ }
364
+ }
365
+ /**
366
+ * Get listener count for an event
367
+ */
368
+ listenerCount(event) {
369
+ const regularCount = this.listeners.get(event)?.size ?? 0;
370
+ const onceCount = this.onceListeners.get(event)?.size ?? 0;
371
+ return regularCount + onceCount;
372
+ }
373
+ /**
374
+ * Get all event types with listeners
375
+ */
376
+ eventNames() {
377
+ const events = new Set();
378
+ this.listeners.forEach((_, event) => events.add(event));
379
+ this.onceListeners.forEach((_, event) => events.add(event));
380
+ return Array.from(events);
381
+ }
382
+ }
383
+
384
+ /**
385
+ * Logger utility for debug mode
386
+ */
387
+ class Logger {
388
+ constructor(config = {}) {
389
+ this.enabled = config.enabled ?? false;
390
+ this.logLevel = config.logLevel ?? 'info';
391
+ this.prefix = config.prefix ?? 'SecureStorage';
392
+ }
393
+ shouldLog(level) {
394
+ if (!this.enabled)
395
+ return false;
396
+ return Logger.LOG_LEVELS[level] <= Logger.LOG_LEVELS[this.logLevel];
397
+ }
398
+ formatMessage(level, message, ...args) {
399
+ const timestamp = new Date().toISOString();
400
+ return `[${this.prefix}] [${level.toUpperCase()}] ${timestamp} - ${message}`;
401
+ }
402
+ error(message, ...args) {
403
+ if (this.shouldLog('error')) {
404
+ console.error(this.formatMessage('error', message), ...args);
405
+ }
406
+ }
407
+ warn(message, ...args) {
408
+ if (this.shouldLog('warn')) {
409
+ console.warn(this.formatMessage('warn', message), ...args);
410
+ }
411
+ }
412
+ info(message, ...args) {
413
+ if (this.shouldLog('info')) {
414
+ console.info(this.formatMessage('info', message), ...args);
415
+ }
416
+ }
417
+ debug(message, ...args) {
418
+ if (this.shouldLog('debug')) {
419
+ console.debug(this.formatMessage('debug', message), ...args);
420
+ }
421
+ }
422
+ verbose(message, ...args) {
423
+ if (this.shouldLog('verbose')) {
424
+ console.log(this.formatMessage('verbose', message), ...args);
425
+ }
426
+ }
427
+ time(label) {
428
+ if (this.enabled && this.shouldLog('debug')) {
429
+ console.time(`[${this.prefix}] ${label}`);
430
+ }
431
+ }
432
+ timeEnd(label) {
433
+ if (this.enabled && this.shouldLog('debug')) {
434
+ console.timeEnd(`[${this.prefix}] ${label}`);
435
+ }
436
+ }
437
+ group(label) {
438
+ if (this.enabled && this.shouldLog('debug')) {
439
+ console.group(`[${this.prefix}] ${label}`);
440
+ }
441
+ }
442
+ groupEnd() {
443
+ if (this.enabled && this.shouldLog('debug')) {
444
+ console.groupEnd();
445
+ }
446
+ }
447
+ }
448
+ Logger.LOG_LEVELS = {
449
+ silent: 0,
450
+ error: 1,
451
+ warn: 2,
452
+ info: 3,
453
+ debug: 4,
454
+ verbose: 5,
455
+ };
456
+
457
+ /**
458
+ * Key rotation utilities
459
+ */
460
+ class KeyRotation {
461
+ constructor(encryptionHelper, logger) {
462
+ this.encryptionHelper = encryptionHelper;
463
+ this.logger = logger ?? new Logger();
464
+ }
465
+ /**
466
+ * Rotate all encryption keys
467
+ * Re-encrypts all data with a new salt
468
+ */
469
+ async rotateKeys() {
470
+ this.logger.info('Starting key rotation...');
471
+ // 1. Export all current data
472
+ const backup = await this.exportEncryptedData();
473
+ // 2. Clear old encryption
474
+ this.encryptionHelper.clearEncryptedData();
475
+ // 3. Initialize new encryption
476
+ await this.encryptionHelper.initialize();
477
+ // 4. Re-encrypt all data
478
+ for (const [key, value] of Object.entries(backup.data)) {
479
+ const encryptedKey = await this.encryptionHelper.encryptKey(key);
480
+ const encryptedValue = await this.encryptionHelper.encrypt(value);
481
+ localStorage.setItem(encryptedKey, encryptedValue);
482
+ }
483
+ this.logger.info(`Key rotation completed. Re-encrypted ${backup.metadata.itemCount} items`);
484
+ }
485
+ /**
486
+ * Export all encrypted data as backup
487
+ */
488
+ async exportEncryptedData() {
489
+ this.logger.info('Exporting encrypted data...');
490
+ const data = {};
491
+ let itemCount = 0;
492
+ for (let i = 0; i < localStorage.length; i++) {
493
+ const encryptedKey = localStorage.key(i);
494
+ if (encryptedKey && encryptedKey.startsWith('__enc_')) {
495
+ const encryptedValue = localStorage.getItem(encryptedKey);
496
+ if (encryptedValue) {
497
+ try {
498
+ // Decrypt to get original key and value
499
+ const decryptedValue = await this.encryptionHelper.decrypt(encryptedValue);
500
+ // Store with encrypted key as identifier
501
+ data[encryptedKey] = decryptedValue;
502
+ itemCount++;
503
+ }
504
+ catch (error) {
505
+ this.logger.warn(`Failed to decrypt key ${encryptedKey}:`, error);
506
+ }
507
+ }
508
+ }
509
+ }
510
+ const backup = {
511
+ version: '2.0.0',
512
+ timestamp: Date.now(),
513
+ data,
514
+ metadata: {
515
+ keyVersion: this.encryptionHelper.getKeyVersion(),
516
+ algorithm: 'AES-256-GCM',
517
+ itemCount,
518
+ },
519
+ };
520
+ this.logger.info(`Exported ${itemCount} items`);
521
+ return backup;
522
+ }
523
+ /**
524
+ * Import encrypted data from backup
525
+ */
526
+ async importEncryptedData(backup) {
527
+ this.logger.info(`Importing backup from ${new Date(backup.timestamp).toISOString()}...`);
528
+ let imported = 0;
529
+ for (const [encryptedKey, value] of Object.entries(backup.data)) {
530
+ try {
531
+ const encryptedValue = await this.encryptionHelper.encrypt(value);
532
+ localStorage.setItem(encryptedKey, encryptedValue);
533
+ imported++;
534
+ }
535
+ catch (error) {
536
+ this.logger.error(`Failed to import key ${encryptedKey}:`, error);
537
+ }
538
+ }
539
+ this.logger.info(`Imported ${imported}/${backup.metadata.itemCount} items`);
540
+ }
541
+ }
542
+
543
+ /**
544
+ * Namespaced storage for organizing data
545
+ */
546
+ class NamespacedStorage {
547
+ constructor(storage, namespace) {
548
+ this.storage = storage;
549
+ this.prefix = `__ns_${namespace}__`;
550
+ }
551
+ /**
552
+ * Set item in this namespace
553
+ */
554
+ async setItem(key, value) {
555
+ return this.storage.setItem(`${this.prefix}${key}`, value);
556
+ }
557
+ /**
558
+ * Set item with expiry in this namespace
559
+ */
560
+ async setItemWithExpiry(key, value, options) {
561
+ return this.storage.setItemWithExpiry(`${this.prefix}${key}`, value, options);
562
+ }
563
+ /**
564
+ * Get item from this namespace
565
+ */
566
+ async getItem(key) {
567
+ return this.storage.getItem(`${this.prefix}${key}`);
568
+ }
569
+ /**
570
+ * Remove item from this namespace
571
+ */
572
+ async removeItem(key) {
573
+ return this.storage.removeItem(`${this.prefix}${key}`);
574
+ }
575
+ /**
576
+ * Check if item exists in this namespace
577
+ */
578
+ async hasItem(key) {
579
+ return this.storage.hasItem(`${this.prefix}${key}`);
580
+ }
581
+ /**
582
+ * Clear all items in this namespace
583
+ */
584
+ async clearNamespace() {
585
+ const keysToRemove = [];
586
+ for (let i = 0; i < localStorage.length; i++) {
587
+ const key = localStorage.key(i);
588
+ if (key && key.includes(this.prefix)) {
589
+ keysToRemove.push(key.replace(this.prefix, ''));
590
+ }
591
+ }
592
+ for (const key of keysToRemove) {
593
+ await this.removeItem(key);
594
+ }
595
+ }
596
+ /**
597
+ * Get all keys in this namespace
598
+ */
599
+ async keys() {
600
+ const keys = [];
601
+ for (let i = 0; i < localStorage.length; i++) {
602
+ const key = localStorage.key(i);
603
+ if (key && key.includes(this.prefix)) {
604
+ keys.push(key.replace(this.prefix, ''));
605
+ }
606
+ }
607
+ return keys;
608
+ }
609
+ }
610
+
611
+ /**
612
+ * Compression utilities using CompressionStream API
613
+ */
614
+ /**
615
+ * Check if compression is supported
616
+ */
617
+ function isCompressionSupported() {
618
+ return typeof CompressionStream !== 'undefined' && typeof DecompressionStream !== 'undefined';
619
+ }
620
+ /**
621
+ * Compress a string using gzip
622
+ */
623
+ async function compress(data) {
624
+ if (!isCompressionSupported()) {
625
+ // Fallback: return uncompressed data with marker
626
+ const encoder = new TextEncoder();
627
+ return encoder.encode(data);
628
+ }
629
+ try {
630
+ const encoder = new TextEncoder();
631
+ const stream = new Blob([data]).stream();
632
+ const compressedStream = stream.pipeThrough(new CompressionStream('gzip'));
633
+ const compressedBlob = await new Response(compressedStream).blob();
634
+ const buffer = await compressedBlob.arrayBuffer();
635
+ return new Uint8Array(buffer);
636
+ }
637
+ catch (error) {
638
+ console.warn('Compression failed, returning uncompressed data:', error);
639
+ const encoder = new TextEncoder();
640
+ return encoder.encode(data);
641
+ }
642
+ }
643
+ /**
644
+ * Decompress gzip data to string
645
+ */
646
+ async function decompress(data) {
647
+ if (!isCompressionSupported()) {
648
+ // Fallback: assume uncompressed
649
+ const decoder = new TextDecoder();
650
+ return decoder.decode(data);
651
+ }
652
+ try {
653
+ const stream = new Blob([data]).stream();
654
+ const decompressedStream = stream.pipeThrough(new DecompressionStream('gzip'));
655
+ const decompressedBlob = await new Response(decompressedStream).blob();
656
+ return await decompressedBlob.text();
657
+ }
658
+ catch (error) {
659
+ // If decompression fails, try to decode as plain text
660
+ console.warn('Decompression failed, trying plain text:', error);
661
+ const decoder = new TextDecoder();
662
+ return decoder.decode(data);
663
+ }
664
+ }
665
+ /**
666
+ * Check if data should be compressed based on size
667
+ */
668
+ function shouldCompress(data, threshold = 1024) {
669
+ return data.length >= threshold;
670
+ }
671
+
672
+ /**
673
+ * SecureStorage v2 - API de alto nivel que imita localStorage con cifrado automático
674
+ * Incluye: configuración personalizable, eventos, compresión, expiración, namespaces, rotación de claves
675
+ */
676
+ class SecureStorage {
677
+ constructor(config) {
678
+ this.cleanupInterval = null;
679
+ // Merge config with defaults
680
+ this.config = {
681
+ encryption: { ...DEFAULT_CONFIG.encryption, ...config?.encryption },
682
+ storage: { ...DEFAULT_CONFIG.storage, ...config?.storage },
683
+ debug: { ...DEFAULT_CONFIG.debug, ...config?.debug },
684
+ };
685
+ // Initialize components
686
+ this.logger = new Logger(this.config.debug);
687
+ this.encryptionHelper = new EncryptionHelper(this.config.encryption);
688
+ this.eventEmitter = new EventEmitter();
689
+ this.keyRotation = new KeyRotation(this.encryptionHelper, this.logger);
690
+ this.logger.info('SecureStorage v2 initialized', this.config);
691
+ // Setup auto-cleanup if enabled
692
+ if (this.config.storage.autoCleanup) {
693
+ this.setupAutoCleanup();
694
+ }
247
695
  }
248
696
  /**
249
697
  * Obtiene la instancia singleton de SecureStorage
250
698
  */
251
- static getInstance() {
699
+ static getInstance(config) {
252
700
  if (!SecureStorage.instance) {
253
- SecureStorage.instance = new SecureStorage();
701
+ SecureStorage.instance = new SecureStorage(config);
254
702
  }
255
703
  return SecureStorage.instance;
256
704
  }
705
+ /**
706
+ * Setup automatic cleanup of expired items
707
+ */
708
+ setupAutoCleanup() {
709
+ if (this.cleanupInterval) {
710
+ clearInterval(this.cleanupInterval);
711
+ }
712
+ this.cleanupInterval = setInterval(() => {
713
+ this.cleanExpired().catch(err => {
714
+ this.logger.error('Auto-cleanup failed:', err);
715
+ });
716
+ }, this.config.storage.cleanupInterval);
717
+ this.logger.debug(`Auto-cleanup enabled with interval: ${this.config.storage.cleanupInterval}ms`);
718
+ }
257
719
  /**
258
720
  * Guarda un valor encriptado en localStorage
259
- * @param key - Clave para almacenar
260
- * @param value - Valor a encriptar y almacenar
261
721
  */
262
722
  async setItem(key, value) {
723
+ this.logger.time(`setItem:${key}`);
263
724
  if (!EncryptionHelper.isSupported()) {
264
- console.warn('Web Crypto API no soportada, usando localStorage sin encriptar');
725
+ this.logger.warn('Web Crypto API no soportada, usando localStorage sin encriptar');
265
726
  localStorage.setItem(key, value);
266
727
  return;
267
728
  }
268
729
  try {
269
- // Encriptar el nombre de la clave
730
+ // Check if compression should be applied
731
+ const shouldCompressData = this.config.storage.compression &&
732
+ shouldCompress(value, this.config.storage.compressionThreshold);
733
+ let processedValue = value;
734
+ let compressed = false;
735
+ if (shouldCompressData) {
736
+ this.logger.debug(`Compressing value for key: ${key}`);
737
+ const compressedData = await compress(value);
738
+ processedValue = this.encryptionHelper['arrayBufferToBase64'](compressedData.buffer);
739
+ compressed = true;
740
+ this.eventEmitter.emit('compressed', { key });
741
+ }
742
+ // Create StoredValue wrapper
743
+ const now = Date.now();
744
+ const storedValue = {
745
+ value: processedValue,
746
+ createdAt: now,
747
+ modifiedAt: now,
748
+ version: STORAGE_VERSION,
749
+ compressed,
750
+ };
751
+ // Calculate checksum for integrity
752
+ const checksum = await this.calculateChecksum(processedValue);
753
+ storedValue.checksum = checksum;
754
+ // Serialize StoredValue
755
+ const serialized = JSON.stringify(storedValue);
756
+ // Encrypt the key
270
757
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
271
- // Encriptar el valor
272
- const encryptedValue = await this.encryptionHelper.encrypt(value);
273
- // Guardar en localStorage
758
+ // Encrypt the value
759
+ const encryptedValue = await this.encryptionHelper.encrypt(serialized);
760
+ // Store in localStorage
274
761
  localStorage.setItem(encryptedKey, encryptedValue);
762
+ this.logger.verbose(`Stored key: ${key}, compressed: ${compressed}, size: ${encryptedValue.length}`);
763
+ this.eventEmitter.emit('encrypted', { key, metadata: { compressed, size: encryptedValue.length } });
764
+ this.logger.timeEnd(`setItem:${key}`);
275
765
  }
276
766
  catch (error) {
277
- console.error('Error al guardar dato encriptado, usando fallback:', error);
767
+ this.logger.error(`Error al guardar dato encriptado para ${key}:`, error);
768
+ this.eventEmitter.emit('error', { key, error: error });
278
769
  localStorage.setItem(key, value);
279
770
  }
280
771
  }
772
+ /**
773
+ * Guarda un valor con tiempo de expiración
774
+ */
775
+ async setItemWithExpiry(key, value, options) {
776
+ this.logger.time(`setItemWithExpiry:${key}`);
777
+ if (!EncryptionHelper.isSupported()) {
778
+ this.logger.warn('Web Crypto API no soportada, usando localStorage sin encriptar');
779
+ localStorage.setItem(key, value);
780
+ return;
781
+ }
782
+ try {
783
+ // Check if compression should be applied
784
+ const shouldCompressData = this.config.storage.compression &&
785
+ shouldCompress(value, this.config.storage.compressionThreshold);
786
+ let processedValue = value;
787
+ let compressed = false;
788
+ if (shouldCompressData) {
789
+ this.logger.debug(`Compressing value for key: ${key}`);
790
+ const compressedData = await compress(value);
791
+ processedValue = this.encryptionHelper['arrayBufferToBase64'](compressedData.buffer);
792
+ compressed = true;
793
+ this.eventEmitter.emit('compressed', { key });
794
+ }
795
+ // Calculate expiration timestamp
796
+ let expiresAt;
797
+ if (options.expiresIn) {
798
+ expiresAt = Date.now() + options.expiresIn;
799
+ }
800
+ else if (options.expiresAt) {
801
+ expiresAt = options.expiresAt.getTime();
802
+ }
803
+ // Create StoredValue wrapper
804
+ const now = Date.now();
805
+ const storedValue = {
806
+ value: processedValue,
807
+ createdAt: now,
808
+ modifiedAt: now,
809
+ version: STORAGE_VERSION,
810
+ compressed,
811
+ expiresAt,
812
+ };
813
+ // Calculate checksum for integrity
814
+ const checksum = await this.calculateChecksum(processedValue);
815
+ storedValue.checksum = checksum;
816
+ // Serialize StoredValue
817
+ const serialized = JSON.stringify(storedValue);
818
+ // Encrypt the key
819
+ const encryptedKey = await this.encryptionHelper.encryptKey(key);
820
+ // Encrypt the value
821
+ const encryptedValue = await this.encryptionHelper.encrypt(serialized);
822
+ // Store in localStorage
823
+ localStorage.setItem(encryptedKey, encryptedValue);
824
+ this.logger.verbose(`Stored key with expiry: ${key}, expiresAt: ${expiresAt}`);
825
+ this.eventEmitter.emit('encrypted', { key, metadata: { compressed, expiresAt } });
826
+ this.logger.timeEnd(`setItemWithExpiry:${key}`);
827
+ }
828
+ catch (error) {
829
+ this.logger.error(`Error al guardar dato con expiración para ${key}:`, error);
830
+ this.eventEmitter.emit('error', { key, error: error });
831
+ throw error;
832
+ }
833
+ }
281
834
  /**
282
835
  * Recupera y desencripta un valor de localStorage
283
- * @param key - Clave a buscar
284
- * @returns Valor desencriptado o null si no existe
285
836
  */
286
837
  async getItem(key) {
838
+ this.logger.time(`getItem:${key}`);
287
839
  if (!EncryptionHelper.isSupported()) {
288
840
  return localStorage.getItem(key);
289
841
  }
290
842
  try {
291
- // Encriptar el nombre de la clave
843
+ // Encrypt the key
292
844
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
293
- // Buscar el valor encriptado
845
+ // Get encrypted value
294
846
  let encryptedValue = localStorage.getItem(encryptedKey);
295
- // Retrocompatibilidad: si no existe con clave encriptada, buscar con clave normal
847
+ // Backward compatibility: try with plain key
296
848
  if (!encryptedValue) {
297
849
  encryptedValue = localStorage.getItem(key);
298
850
  if (!encryptedValue) {
851
+ this.logger.timeEnd(`getItem:${key}`);
299
852
  return null;
300
853
  }
301
- // Si encontramos un valor con clave normal, intentar desencriptarlo
302
- // (podría ser un valor ya encriptado pero con clave antigua)
303
854
  }
304
- // Desencriptar el valor
305
- return await this.encryptionHelper.decrypt(encryptedValue);
855
+ // Decrypt the value
856
+ const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
857
+ // Try to parse as StoredValue (v2 format)
858
+ let storedValue;
859
+ try {
860
+ storedValue = JSON.parse(decrypted);
861
+ // Validate it's a StoredValue object
862
+ if (!storedValue.value || !storedValue.version) {
863
+ // It's v1 format (plain string), auto-migrate
864
+ this.logger.info(`Auto-migrating v1 data for key: ${key}`);
865
+ await this.setItem(key, decrypted);
866
+ this.logger.timeEnd(`getItem:${key}`);
867
+ return decrypted;
868
+ }
869
+ }
870
+ catch {
871
+ // Not JSON, it's v1 format (plain string), auto-migrate
872
+ this.logger.info(`Auto-migrating v1 data for key: ${key}`);
873
+ await this.setItem(key, decrypted);
874
+ this.logger.timeEnd(`getItem:${key}`);
875
+ return decrypted;
876
+ }
877
+ // Check expiration
878
+ if (storedValue.expiresAt && storedValue.expiresAt < Date.now()) {
879
+ this.logger.info(`Key expired: ${key}`);
880
+ await this.removeItem(key);
881
+ this.eventEmitter.emit('expired', { key });
882
+ this.logger.timeEnd(`getItem:${key}`);
883
+ return null;
884
+ }
885
+ // Verify integrity if checksum exists
886
+ if (storedValue.checksum) {
887
+ const calculatedChecksum = await this.calculateChecksum(storedValue.value);
888
+ if (calculatedChecksum !== storedValue.checksum) {
889
+ this.logger.warn(`Integrity check failed for key: ${key}`);
890
+ this.eventEmitter.emit('error', {
891
+ key,
892
+ error: new Error('Integrity verification failed')
893
+ });
894
+ }
895
+ }
896
+ // Decompress if needed
897
+ let finalValue = storedValue.value;
898
+ if (storedValue.compressed) {
899
+ this.logger.debug(`Decompressing value for key: ${key}`);
900
+ const compressedData = this.encryptionHelper['base64ToArrayBuffer'](storedValue.value);
901
+ finalValue = await decompress(new Uint8Array(compressedData));
902
+ this.eventEmitter.emit('decompressed', { key });
903
+ }
904
+ this.eventEmitter.emit('decrypted', { key });
905
+ this.logger.timeEnd(`getItem:${key}`);
906
+ return finalValue;
306
907
  }
307
908
  catch (error) {
308
- console.error('Error al recuperar dato encriptado:', error);
309
- // Fallback: intentar leer directamente
310
- return localStorage.getItem(key);
909
+ this.logger.error(`Error al recuperar dato encriptado para ${key}:`, error);
910
+ this.eventEmitter.emit('error', { key, error: error });
911
+ // Fallback: try plain key
912
+ const fallback = localStorage.getItem(key);
913
+ this.logger.timeEnd(`getItem:${key}`);
914
+ return fallback;
311
915
  }
312
916
  }
313
917
  /**
314
918
  * Elimina un valor de localStorage
315
- * @param key - Clave a eliminar
316
919
  */
317
920
  async removeItem(key) {
318
921
  if (!EncryptionHelper.isSupported()) {
@@ -320,21 +923,19 @@ class SecureStorage {
320
923
  return;
321
924
  }
322
925
  try {
323
- // Encriptar el nombre de la clave
324
926
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
325
- // Eliminar ambas versiones (encriptada y normal) por seguridad
326
927
  localStorage.removeItem(encryptedKey);
327
- localStorage.removeItem(key);
928
+ localStorage.removeItem(key); // Remove both versions
929
+ this.eventEmitter.emit('deleted', { key });
930
+ this.logger.info(`Removed key: ${key}`);
328
931
  }
329
932
  catch (error) {
330
- console.error('Error al eliminar dato encriptado:', error);
933
+ this.logger.error(`Error al eliminar dato para ${key}:`, error);
331
934
  localStorage.removeItem(key);
332
935
  }
333
936
  }
334
937
  /**
335
938
  * Verifica si existe un valor para la clave dada
336
- * @param key - Clave a verificar
337
- * @returns true si existe, false si no
338
939
  */
339
940
  async hasItem(key) {
340
941
  const value = await this.getItem(key);
@@ -345,44 +946,175 @@ class SecureStorage {
345
946
  */
346
947
  clear() {
347
948
  this.encryptionHelper.clearEncryptedData();
949
+ this.eventEmitter.emit('cleared', {});
950
+ this.logger.info('All encrypted data cleared');
951
+ }
952
+ /**
953
+ * Limpia todos los items expirados
954
+ */
955
+ async cleanExpired() {
956
+ this.logger.info('Starting cleanup of expired items...');
957
+ let cleanedCount = 0;
958
+ const keysToCheck = [];
959
+ for (let i = 0; i < localStorage.length; i++) {
960
+ const key = localStorage.key(i);
961
+ if (key && key.startsWith('__enc_')) {
962
+ keysToCheck.push(key);
963
+ }
964
+ }
965
+ for (const encryptedKey of keysToCheck) {
966
+ try {
967
+ const encryptedValue = localStorage.getItem(encryptedKey);
968
+ if (!encryptedValue)
969
+ continue;
970
+ const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
971
+ const storedValue = JSON.parse(decrypted);
972
+ if (storedValue.expiresAt && storedValue.expiresAt < Date.now()) {
973
+ localStorage.removeItem(encryptedKey);
974
+ cleanedCount++;
975
+ this.eventEmitter.emit('expired', { key: encryptedKey });
976
+ }
977
+ }
978
+ catch (error) {
979
+ this.logger.warn(`Failed to check expiration for ${encryptedKey}:`, error);
980
+ }
981
+ }
982
+ this.logger.info(`Cleanup completed. Removed ${cleanedCount} expired items`);
983
+ return cleanedCount;
984
+ }
985
+ /**
986
+ * Verifica la integridad de un valor almacenado
987
+ */
988
+ async verifyIntegrity(key) {
989
+ try {
990
+ const encryptedKey = await this.encryptionHelper.encryptKey(key);
991
+ const encryptedValue = localStorage.getItem(encryptedKey);
992
+ if (!encryptedValue)
993
+ return false;
994
+ const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
995
+ const storedValue = JSON.parse(decrypted);
996
+ if (!storedValue.checksum) {
997
+ this.logger.warn(`No checksum found for key: ${key}`);
998
+ return true; // No checksum to verify
999
+ }
1000
+ const calculatedChecksum = await this.calculateChecksum(storedValue.value);
1001
+ return calculatedChecksum === storedValue.checksum;
1002
+ }
1003
+ catch (error) {
1004
+ this.logger.error(`Error verifying integrity for ${key}:`, error);
1005
+ return false;
1006
+ }
1007
+ }
1008
+ /**
1009
+ * Obtiene información de integridad de un valor
1010
+ */
1011
+ async getIntegrityInfo(key) {
1012
+ try {
1013
+ const encryptedKey = await this.encryptionHelper.encryptKey(key);
1014
+ const encryptedValue = localStorage.getItem(encryptedKey);
1015
+ if (!encryptedValue)
1016
+ return null;
1017
+ const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
1018
+ const storedValue = JSON.parse(decrypted);
1019
+ const valid = storedValue.checksum
1020
+ ? await this.calculateChecksum(storedValue.value) === storedValue.checksum
1021
+ : true;
1022
+ return {
1023
+ valid,
1024
+ lastModified: storedValue.modifiedAt,
1025
+ checksum: storedValue.checksum || '',
1026
+ version: storedValue.version,
1027
+ };
1028
+ }
1029
+ catch (error) {
1030
+ this.logger.error(`Error getting integrity info for ${key}:`, error);
1031
+ return null;
1032
+ }
1033
+ }
1034
+ /**
1035
+ * Crea un namespace para organizar datos
1036
+ */
1037
+ namespace(name) {
1038
+ this.logger.debug(`Creating namespace: ${name}`);
1039
+ return new NamespacedStorage(this, name);
1040
+ }
1041
+ /**
1042
+ * Rota todas las claves de encriptación
1043
+ */
1044
+ async rotateKeys() {
1045
+ this.logger.info('Starting key rotation...');
1046
+ await this.keyRotation.rotateKeys();
1047
+ this.eventEmitter.emit('keyRotated', {});
1048
+ this.logger.info('Key rotation completed');
1049
+ }
1050
+ /**
1051
+ * Exporta todos los datos encriptados como backup
1052
+ */
1053
+ async exportEncryptedData() {
1054
+ return this.keyRotation.exportEncryptedData();
1055
+ }
1056
+ /**
1057
+ * Importa datos desde un backup
1058
+ */
1059
+ async importEncryptedData(backup) {
1060
+ return this.keyRotation.importEncryptedData(backup);
1061
+ }
1062
+ /**
1063
+ * Registra un listener de eventos
1064
+ */
1065
+ on(event, listener) {
1066
+ this.eventEmitter.on(event, listener);
1067
+ }
1068
+ /**
1069
+ * Registra un listener de un solo uso
1070
+ */
1071
+ once(event, listener) {
1072
+ this.eventEmitter.once(event, listener);
1073
+ }
1074
+ /**
1075
+ * Elimina un listener de eventos
1076
+ */
1077
+ off(event, listener) {
1078
+ this.eventEmitter.off(event, listener);
1079
+ }
1080
+ /**
1081
+ * Elimina todos los listeners de un evento
1082
+ */
1083
+ removeAllListeners(event) {
1084
+ this.eventEmitter.removeAllListeners(event);
348
1085
  }
349
1086
  /**
350
1087
  * Migra datos existentes no encriptados a formato encriptado
351
- * @param keys - Array de claves a migrar
352
1088
  */
353
1089
  async migrateExistingData(keys) {
354
1090
  if (!EncryptionHelper.isSupported()) {
355
- console.warn('Web Crypto API no soportada, no se puede migrar');
1091
+ this.logger.warn('Web Crypto API no soportada, no se puede migrar');
356
1092
  return;
357
1093
  }
358
- console.log(`🔄 Iniciando migración de ${keys.length} claves...`);
1094
+ this.logger.info(`Iniciando migración de ${keys.length} claves...`);
359
1095
  for (const key of keys) {
360
1096
  try {
361
- // Leer el valor no encriptado
362
1097
  const value = localStorage.getItem(key);
363
- if (value === null) {
364
- continue; // La clave no existe, saltar
365
- }
366
- // Verificar si ya está encriptado intentando desencriptarlo
1098
+ if (value === null)
1099
+ continue;
1100
+ // Try to decrypt to check if already encrypted
367
1101
  try {
368
1102
  await this.encryptionHelper.decrypt(value);
369
- console.log(`✓ ${key} ya está encriptado, saltando`);
1103
+ this.logger.info(`✓ ${key} ya está encriptado, saltando`);
370
1104
  continue;
371
1105
  }
372
1106
  catch {
373
- // No está encriptado, proceder con la migración
1107
+ // Not encrypted, proceed with migration
374
1108
  }
375
- // Guardar usando setItem (que encriptará automáticamente)
376
1109
  await this.setItem(key, value);
377
- // Eliminar la versión no encriptada
378
1110
  localStorage.removeItem(key);
379
- console.log(`✓ ${key} migrado exitosamente`);
1111
+ this.logger.info(`✓ ${key} migrado exitosamente`);
380
1112
  }
381
1113
  catch (error) {
382
- console.error(`✗ Error al migrar ${key}:`, error);
1114
+ this.logger.error(`✗ Error al migrar ${key}:`, error);
383
1115
  }
384
1116
  }
385
- console.log('✅ Migración completada');
1117
+ this.logger.info('✅ Migración completada');
386
1118
  }
387
1119
  /**
388
1120
  * Obtiene información de debug sobre el estado del almacenamiento
@@ -395,30 +1127,113 @@ class SecureStorage {
395
1127
  allKeys.push(key);
396
1128
  }
397
1129
  const encryptedKeys = allKeys.filter(key => key.startsWith('__enc_'));
398
- const unencryptedKeys = allKeys.filter(key => !key.startsWith('__enc_') && key !== '__app_salt');
1130
+ const unencryptedKeys = allKeys.filter(key => !key.startsWith('__enc_') && key !== '__app_salt' && key !== '__key_version');
399
1131
  return {
400
1132
  cryptoSupported: EncryptionHelper.isSupported(),
401
1133
  encryptedKeys,
402
1134
  unencryptedKeys,
403
1135
  totalKeys: allKeys.length,
1136
+ config: this.config,
404
1137
  };
405
1138
  }
1139
+ /**
1140
+ * Calcula el checksum SHA-256 de un valor
1141
+ */
1142
+ async calculateChecksum(value) {
1143
+ const encoder = new TextEncoder();
1144
+ const data = encoder.encode(value);
1145
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
1146
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
1147
+ return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
1148
+ }
1149
+ /**
1150
+ * Cleanup on destroy
1151
+ */
1152
+ destroy() {
1153
+ if (this.cleanupInterval) {
1154
+ clearInterval(this.cleanupInterval);
1155
+ this.cleanupInterval = null;
1156
+ }
1157
+ this.removeAllListeners();
1158
+ this.logger.info('SecureStorage destroyed');
1159
+ }
406
1160
  }
407
1161
  SecureStorage.instance = null;
408
1162
 
409
1163
  const secureStorage$2 = SecureStorage.getInstance();
1164
+ /**
1165
+ * Función de debug para verificar el estado del sistema de encriptación
1166
+ * Muestra información detallada en la consola
1167
+ */
1168
+ async function debugEncryptionState() {
1169
+ console.group('🔐 Estado del Sistema de Encriptación');
1170
+ console.log('Soporte Crypto API:', EncryptionHelper.isSupported());
1171
+ // Obtener información de debug
1172
+ const debugInfo = secureStorage$2.getDebugInfo();
1173
+ console.log('Claves encriptadas:', debugInfo.encryptedKeys.length);
1174
+ console.log('Claves sin encriptar:', debugInfo.unencryptedKeys);
1175
+ console.log('Total de claves:', debugInfo.totalKeys);
1176
+ if (debugInfo.encryptedKeys.length > 0) {
1177
+ console.log('✅ Datos encriptados encontrados:');
1178
+ debugInfo.encryptedKeys.forEach(key => {
1179
+ const value = localStorage.getItem(key);
1180
+ console.log(` ${key}: ${value?.substring(0, 30)}...`);
1181
+ });
1182
+ }
1183
+ else {
1184
+ console.log('⚠️ No se encontraron datos encriptados');
1185
+ }
1186
+ if (debugInfo.unencryptedKeys.length > 0) {
1187
+ console.log('⚠️ Claves sin encriptar encontradas:');
1188
+ debugInfo.unencryptedKeys.forEach(key => {
1189
+ console.log(` ${key}`);
1190
+ });
1191
+ }
1192
+ console.groupEnd();
1193
+ }
1194
+ /**
1195
+ * Fuerza la migración de claves comunes a formato encriptado
1196
+ * Útil para desarrollo y testing
1197
+ */
1198
+ async function forceMigration(customKeys) {
1199
+ const defaultKeys = [
1200
+ 'accessToken',
1201
+ 'refreshToken',
1202
+ 'user',
1203
+ 'sessionId',
1204
+ 'authToken',
1205
+ 'userData',
1206
+ ];
1207
+ const keysToMigrate = customKeys || defaultKeys;
1208
+ console.log(`🔄 Iniciando migración forzada de ${keysToMigrate.length} claves...`);
1209
+ await secureStorage$2.migrateExistingData(keysToMigrate);
1210
+ console.log('✅ Migración forzada completada');
1211
+ // Mostrar estado después de la migración
1212
+ await debugEncryptionState();
1213
+ }
1214
+
1215
+ // Allow custom storage instance or use default
1216
+ let defaultStorage = null;
1217
+ function getDefaultStorage() {
1218
+ if (!defaultStorage) {
1219
+ defaultStorage = SecureStorage.getInstance();
1220
+ }
1221
+ return defaultStorage;
1222
+ }
410
1223
  /**
411
1224
  * Hook de React para usar SecureStorage de forma reactiva
412
1225
  * Similar a useState pero con persistencia encriptada
413
1226
  *
414
1227
  * @param key - Clave para almacenar en localStorage
415
1228
  * @param initialValue - Valor inicial si no existe en storage
1229
+ * @param storage - Instancia personalizada de SecureStorage (opcional)
416
1230
  * @returns [value, setValue, loading, error]
417
1231
  *
418
1232
  * @example
419
1233
  * const [token, setToken, loading] = useSecureStorage('accessToken', '');
420
1234
  */
421
- function useSecureStorage(key, initialValue) {
1235
+ function useSecureStorage(key, initialValue, storage) {
1236
+ const secureStorage = storage || getDefaultStorage();
422
1237
  const [storedValue, setStoredValue] = react.useState(initialValue);
423
1238
  const [loading, setLoading] = react.useState(true);
424
1239
  const [error, setError] = react.useState(null);
@@ -428,7 +1243,7 @@ function useSecureStorage(key, initialValue) {
428
1243
  try {
429
1244
  setLoading(true);
430
1245
  setError(null);
431
- const item = await secureStorage$2.getItem(key);
1246
+ const item = await secureStorage.getItem(key);
432
1247
  if (item !== null) {
433
1248
  // Si T es un objeto, parsear JSON
434
1249
  if (typeof initialValue === 'object') {
@@ -461,25 +1276,27 @@ function useSecureStorage(key, initialValue) {
461
1276
  const valueToStore = typeof value === 'object'
462
1277
  ? JSON.stringify(value)
463
1278
  : String(value);
464
- await secureStorage$2.setItem(key, valueToStore);
1279
+ await secureStorage.setItem(key, valueToStore);
465
1280
  }
466
1281
  catch (err) {
467
1282
  setError(err instanceof Error ? err : new Error('Error al guardar valor'));
468
1283
  throw err;
469
1284
  }
470
- }, [key]);
1285
+ }, [key, secureStorage]);
471
1286
  return [storedValue, setValue, loading, error];
472
1287
  }
473
1288
  /**
474
1289
  * Hook para verificar si una clave existe en SecureStorage
475
1290
  *
476
1291
  * @param key - Clave a verificar
1292
+ * @param storage - Instancia personalizada de SecureStorage (opcional)
477
1293
  * @returns [exists, loading, error]
478
1294
  *
479
1295
  * @example
480
1296
  * const [hasToken, loading] = useSecureStorageItem('accessToken');
481
1297
  */
482
- function useSecureStorageItem(key) {
1298
+ function useSecureStorageItem(key, storage) {
1299
+ const secureStorage = storage || getDefaultStorage();
483
1300
  const [exists, setExists] = react.useState(false);
484
1301
  const [loading, setLoading] = react.useState(true);
485
1302
  const [error, setError] = react.useState(null);
@@ -488,7 +1305,7 @@ function useSecureStorageItem(key) {
488
1305
  try {
489
1306
  setLoading(true);
490
1307
  setError(null);
491
- const hasItem = await secureStorage$2.hasItem(key);
1308
+ const hasItem = await secureStorage.hasItem(key);
492
1309
  setExists(hasItem);
493
1310
  }
494
1311
  catch (err) {
@@ -500,29 +1317,35 @@ function useSecureStorageItem(key) {
500
1317
  }
501
1318
  };
502
1319
  checkExists();
503
- }, [key]);
1320
+ }, [key, secureStorage]);
504
1321
  return [exists, loading, error];
505
1322
  }
506
1323
  /**
507
1324
  * Hook para obtener información de debug del almacenamiento
508
1325
  *
1326
+ * @param storage - Instancia personalizada de SecureStorage (opcional)
509
1327
  * @returns Información de debug
510
1328
  *
511
1329
  * @example
512
1330
  * const debugInfo = useSecureStorageDebug();
513
1331
  * console.log(`Claves encriptadas: ${debugInfo.encryptedKeys.length}`);
514
1332
  */
515
- function useSecureStorageDebug() {
516
- const [debugInfo, setDebugInfo] = react.useState(secureStorage$2.getDebugInfo());
1333
+ function useSecureStorageDebug(storage) {
1334
+ const secureStorage = storage || getDefaultStorage();
1335
+ const [debugInfo, setDebugInfo] = react.useState(secureStorage.getDebugInfo());
517
1336
  react.useEffect(() => {
518
1337
  // Actualizar cada segundo
519
1338
  const interval = setInterval(() => {
520
- setDebugInfo(secureStorage$2.getDebugInfo());
1339
+ setDebugInfo(secureStorage.getDebugInfo());
521
1340
  }, 1000);
522
1341
  return () => clearInterval(interval);
523
- }, []);
1342
+ }, [secureStorage]);
524
1343
  return debugInfo;
525
1344
  }
1345
+ /**
1346
+ * Exportar la instancia de SecureStorage para uso directo
1347
+ */
1348
+ const secureStorage$1 = getDefaultStorage();
526
1349
 
527
1350
  /******************************************************************************
528
1351
  Copyright (c) Microsoft Corporation.
@@ -585,7 +1408,7 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
585
1408
  };
586
1409
 
587
1410
  /**
588
- * Servicio de Angular para SecureStorage
1411
+ * Servicio de Angular para SecureStorage v2
589
1412
  * Proporciona una API reactiva usando RxJS Observables
590
1413
  *
591
1414
  * @example
@@ -596,6 +1419,9 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
596
1419
  *
597
1420
  * // Leer
598
1421
  * this.secureStorage.getItem('token').subscribe(token => console.log(token));
1422
+ *
1423
+ * // Escuchar eventos
1424
+ * this.secureStorage.events$.subscribe(event => console.log(event));
599
1425
  */
600
1426
  let SecureStorageService = (() => {
601
1427
  let _classDecorators = [core.Injectable({
@@ -605,9 +1431,24 @@ let SecureStorageService = (() => {
605
1431
  let _classExtraInitializers = [];
606
1432
  let _classThis;
607
1433
  _classThis = class {
608
- constructor() {
1434
+ constructor(config) {
609
1435
  this.debugInfo$ = new rxjs.BehaviorSubject(this.getDebugInfo());
610
- this.storage = SecureStorage.getInstance();
1436
+ this.eventsSubject$ = new rxjs.Subject();
1437
+ /**
1438
+ * Observable de eventos de storage
1439
+ */
1440
+ this.events$ = this.eventsSubject$.asObservable();
1441
+ this.storage = SecureStorage.getInstance(config);
1442
+ // Setup event forwarding to Observable
1443
+ this.storage.on('encrypted', (data) => this.eventsSubject$.next(data));
1444
+ this.storage.on('decrypted', (data) => this.eventsSubject$.next(data));
1445
+ this.storage.on('deleted', (data) => this.eventsSubject$.next(data));
1446
+ this.storage.on('cleared', (data) => this.eventsSubject$.next(data));
1447
+ this.storage.on('expired', (data) => this.eventsSubject$.next(data));
1448
+ this.storage.on('error', (data) => this.eventsSubject$.next(data));
1449
+ this.storage.on('keyRotated', (data) => this.eventsSubject$.next(data));
1450
+ this.storage.on('compressed', (data) => this.eventsSubject$.next(data));
1451
+ this.storage.on('decompressed', (data) => this.eventsSubject$.next(data));
611
1452
  // Actualizar debug info cada segundo
612
1453
  setInterval(() => {
613
1454
  this.debugInfo$.next(this.getDebugInfo());
@@ -622,6 +1463,16 @@ let SecureStorageService = (() => {
622
1463
  setItem(key, value) {
623
1464
  return rxjs.from(this.storage.setItem(key, value));
624
1465
  }
1466
+ /**
1467
+ * Guarda un valor con expiración
1468
+ * @param key - Clave
1469
+ * @param value - Valor a guardar
1470
+ * @param options - Opciones de expiración
1471
+ * @returns Observable que completa cuando se guarda
1472
+ */
1473
+ setItemWithExpiry(key, value, options) {
1474
+ return rxjs.from(this.storage.setItemWithExpiry(key, value, options));
1475
+ }
625
1476
  /**
626
1477
  * Recupera un valor desencriptado
627
1478
  * @param key - Clave
@@ -652,6 +1503,59 @@ let SecureStorageService = (() => {
652
1503
  clear() {
653
1504
  this.storage.clear();
654
1505
  }
1506
+ /**
1507
+ * Limpia todos los items expirados
1508
+ * @returns Observable con el número de items eliminados
1509
+ */
1510
+ cleanExpired() {
1511
+ return rxjs.from(this.storage.cleanExpired());
1512
+ }
1513
+ /**
1514
+ * Verifica la integridad de un valor
1515
+ * @param key - Clave a verificar
1516
+ * @returns Observable con true si es válido
1517
+ */
1518
+ verifyIntegrity(key) {
1519
+ return rxjs.from(this.storage.verifyIntegrity(key));
1520
+ }
1521
+ /**
1522
+ * Obtiene información de integridad de un valor
1523
+ * @param key - Clave
1524
+ * @returns Observable con información de integridad
1525
+ */
1526
+ getIntegrityInfo(key) {
1527
+ return rxjs.from(this.storage.getIntegrityInfo(key));
1528
+ }
1529
+ /**
1530
+ * Crea un namespace para organizar datos
1531
+ * @param name - Nombre del namespace
1532
+ * @returns Instancia de NamespacedStorage
1533
+ */
1534
+ namespace(name) {
1535
+ return this.storage.namespace(name);
1536
+ }
1537
+ /**
1538
+ * Rota todas las claves de encriptación
1539
+ * @returns Observable que completa cuando termina la rotación
1540
+ */
1541
+ rotateKeys() {
1542
+ return rxjs.from(this.storage.rotateKeys());
1543
+ }
1544
+ /**
1545
+ * Exporta todos los datos como backup
1546
+ * @returns Observable con el backup
1547
+ */
1548
+ exportEncryptedData() {
1549
+ return rxjs.from(this.storage.exportEncryptedData());
1550
+ }
1551
+ /**
1552
+ * Importa datos desde un backup
1553
+ * @param backup - Backup a importar
1554
+ * @returns Observable que completa cuando termina la importación
1555
+ */
1556
+ importEncryptedData(backup) {
1557
+ return rxjs.from(this.storage.importEncryptedData(backup));
1558
+ }
655
1559
  /**
656
1560
  * Migra datos existentes a formato encriptado
657
1561
  * @param keys - Array de claves a migrar
@@ -681,6 +1585,15 @@ let SecureStorageService = (() => {
681
1585
  setObject(key, value) {
682
1586
  return this.setItem(key, JSON.stringify(value));
683
1587
  }
1588
+ /**
1589
+ * Helper para guardar objetos JSON con expiración
1590
+ * @param key - Clave
1591
+ * @param value - Objeto a guardar
1592
+ * @param options - Opciones de expiración
1593
+ */
1594
+ setObjectWithExpiry(key, value, options) {
1595
+ return this.setItemWithExpiry(key, JSON.stringify(value), options);
1596
+ }
684
1597
  /**
685
1598
  * Helper para recuperar objetos JSON
686
1599
  * @param key - Clave
@@ -689,6 +1602,30 @@ let SecureStorageService = (() => {
689
1602
  getObject(key) {
690
1603
  return this.getItem(key).pipe(operators.map(value => value ? JSON.parse(value) : null), operators.catchError(() => rxjs.from([null])));
691
1604
  }
1605
+ /**
1606
+ * Registra un listener para un tipo de evento específico
1607
+ * @param event - Tipo de evento
1608
+ * @param handler - Función manejadora
1609
+ */
1610
+ on(event, handler) {
1611
+ this.storage.on(event, handler);
1612
+ }
1613
+ /**
1614
+ * Elimina un listener de evento
1615
+ * @param event - Tipo de evento
1616
+ * @param handler - Función manejadora
1617
+ */
1618
+ off(event, handler) {
1619
+ this.storage.off(event, handler);
1620
+ }
1621
+ /**
1622
+ * Observable filtrado por tipo de evento
1623
+ * @param eventType - Tipo de evento a filtrar
1624
+ * @returns Observable con eventos del tipo especificado
1625
+ */
1626
+ onEvent$(eventType) {
1627
+ return this.events$.pipe(operators.map(event => event.type === eventType ? event : null), operators.map(event => event));
1628
+ }
692
1629
  };
693
1630
  __setFunctionName(_classThis, "SecureStorageService");
694
1631
  (() => {
@@ -701,60 +1638,8 @@ let SecureStorageService = (() => {
701
1638
  return _classThis;
702
1639
  })();
703
1640
 
704
- const secureStorage$1 = SecureStorage.getInstance();
705
- /**
706
- * Función de debug para verificar el estado del sistema de encriptación
707
- * Muestra información detallada en la consola
708
- */
709
- async function debugEncryptionState() {
710
- console.group('🔐 Estado del Sistema de Encriptación');
711
- console.log('Soporte Crypto API:', EncryptionHelper.isSupported());
712
- // Obtener información de debug
713
- const debugInfo = secureStorage$1.getDebugInfo();
714
- console.log('Claves encriptadas:', debugInfo.encryptedKeys.length);
715
- console.log('Claves sin encriptar:', debugInfo.unencryptedKeys);
716
- console.log('Total de claves:', debugInfo.totalKeys);
717
- if (debugInfo.encryptedKeys.length > 0) {
718
- console.log('✅ Datos encriptados encontrados:');
719
- debugInfo.encryptedKeys.forEach(key => {
720
- const value = localStorage.getItem(key);
721
- console.log(` ${key}: ${value?.substring(0, 30)}...`);
722
- });
723
- }
724
- else {
725
- console.log('⚠️ No se encontraron datos encriptados');
726
- }
727
- if (debugInfo.unencryptedKeys.length > 0) {
728
- console.log('⚠️ Claves sin encriptar encontradas:');
729
- debugInfo.unencryptedKeys.forEach(key => {
730
- console.log(` ${key}`);
731
- });
732
- }
733
- console.groupEnd();
734
- }
735
- /**
736
- * Fuerza la migración de claves comunes a formato encriptado
737
- * Útil para desarrollo y testing
738
- */
739
- async function forceMigration(customKeys) {
740
- const defaultKeys = [
741
- 'accessToken',
742
- 'refreshToken',
743
- 'user',
744
- 'sessionId',
745
- 'authToken',
746
- 'userData',
747
- ];
748
- const keysToMigrate = customKeys || defaultKeys;
749
- console.log(`🔄 Iniciando migración forzada de ${keysToMigrate.length} claves...`);
750
- await secureStorage$1.migrateExistingData(keysToMigrate);
751
- console.log('✅ Migración forzada completada');
752
- // Mostrar estado después de la migración
753
- await debugEncryptionState();
754
- }
755
-
756
1641
  /**
757
- * @mtt/local-cipher
1642
+ * @bantis/local-cipher v2.0.0
758
1643
  * Librería de cifrado local AES-256-GCM para Angular, React y JavaScript
759
1644
  *
760
1645
  * @author MTT
@@ -763,16 +1648,27 @@ async function forceMigration(customKeys) {
763
1648
  // Core exports
764
1649
  const secureStorage = SecureStorage.getInstance();
765
1650
  // Version
766
- const VERSION = '1.0.0';
1651
+ const VERSION = '2.0.0';
767
1652
 
1653
+ exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
768
1654
  exports.EncryptionHelper = EncryptionHelper;
1655
+ exports.EventEmitter = EventEmitter;
1656
+ exports.KeyRotation = KeyRotation;
1657
+ exports.LIBRARY_VERSION = LIBRARY_VERSION;
1658
+ exports.Logger = Logger;
1659
+ exports.NamespacedStorage = NamespacedStorage;
1660
+ exports.STORAGE_VERSION = STORAGE_VERSION;
769
1661
  exports.SecureStorage = SecureStorage;
770
1662
  exports.SecureStorageService = SecureStorageService;
771
1663
  exports.VERSION = VERSION;
1664
+ exports.compress = compress;
772
1665
  exports.debugEncryptionState = debugEncryptionState;
1666
+ exports.decompress = decompress;
773
1667
  exports.forceMigration = forceMigration;
774
- exports.reactSecureStorage = secureStorage$2;
1668
+ exports.isCompressionSupported = isCompressionSupported;
1669
+ exports.reactSecureStorage = secureStorage$1;
775
1670
  exports.secureStorage = secureStorage;
1671
+ exports.shouldCompress = shouldCompress;
776
1672
  exports.useSecureStorage = useSecureStorage;
777
1673
  exports.useSecureStorageDebug = useSecureStorageDebug;
778
1674
  exports.useSecureStorageItem = useSecureStorageItem;