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