@bantis/local-cipher 2.2.0 → 2.3.0

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/react.esm.js CHANGED
@@ -19,6 +19,9 @@ const DEFAULT_CONFIG = {
19
19
  compressionThreshold: 1024,
20
20
  autoCleanup: true,
21
21
  cleanupInterval: 60000,
22
+ enableCache: true,
23
+ verifyIntegrityOnRead: false,
24
+ storageEngine: typeof window !== 'undefined' ? window.localStorage : {},
22
25
  },
23
26
  debug: {
24
27
  enabled: false,
@@ -46,6 +49,8 @@ class EncryptionHelper {
46
49
  this.baseKey = '';
47
50
  this.baseKeyPromise = null;
48
51
  this.keyVersion = 1;
52
+ // Cache para optimización de rendimiento
53
+ this.keyNameCache = new Map();
49
54
  this.config = { ...DEFAULT_CONFIG.encryption, ...config };
50
55
  // Load key version from storage
51
56
  const storedVersion = localStorage.getItem(EncryptionHelper.KEY_VERSION_KEY);
@@ -215,10 +220,16 @@ class EncryptionHelper {
215
220
  * @returns Nombre encriptado con prefijo __enc_
216
221
  */
217
222
  async encryptKey(keyName) {
223
+ // Optimización: devolver desde cache si existe
224
+ if (this.keyNameCache.has(keyName)) {
225
+ return this.keyNameCache.get(keyName);
226
+ }
218
227
  const baseKey = await this.generateBaseKey();
219
228
  const combined = keyName + baseKey;
220
229
  const hash = await this.hashString(combined);
221
- return `__enc_${hash.substring(0, 16)}`;
230
+ const encryptedKey = `__enc_${hash.substring(0, 16)}`;
231
+ this.keyNameCache.set(keyName, encryptedKey);
232
+ return encryptedKey;
222
233
  }
223
234
  /**
224
235
  * Limpia todos los datos encriptados del localStorage
@@ -236,10 +247,11 @@ class EncryptionHelper {
236
247
  keysToRemove.forEach(key => localStorage.removeItem(key));
237
248
  // Eliminar salt
238
249
  localStorage.removeItem(EncryptionHelper.SALT_STORAGE_KEY);
239
- // Resetear clave en memoria
250
+ // Resetear clave en memoria y cache
240
251
  this.key = null;
241
252
  this.baseKey = '';
242
253
  this.baseKeyPromise = null;
254
+ this.keyNameCache.clear();
243
255
  }
244
256
  /**
245
257
  * Verifica si el navegador soporta Web Crypto API
@@ -679,6 +691,7 @@ function shouldCompress(data, threshold = 1024) {
679
691
  class SecureStorage {
680
692
  constructor(config) {
681
693
  this.cleanupInterval = null;
694
+ this.memoryCache = new Map();
682
695
  // Merge config with defaults
683
696
  this.config = {
684
697
  encryption: { ...DEFAULT_CONFIG.encryption, ...config?.encryption },
@@ -760,8 +773,12 @@ class SecureStorage {
760
773
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
761
774
  // Encrypt the value
762
775
  const encryptedValue = await this.encryptionHelper.encrypt(serialized);
763
- // Store in localStorage
764
- localStorage.setItem(encryptedKey, encryptedValue);
776
+ // Store in storage
777
+ this.config.storage.storageEngine.setItem(encryptedKey, encryptedValue);
778
+ // Update cache
779
+ if (this.config.storage.enableCache) {
780
+ this.memoryCache.set(key, { value });
781
+ }
765
782
  this.logger.verbose(`Stored key: ${key}, compressed: ${compressed}, size: ${encryptedValue.length}`);
766
783
  this.eventEmitter.emit('encrypted', { key, metadata: { compressed, size: encryptedValue.length } });
767
784
  this.logger.timeEnd(`setItem:${key}`);
@@ -769,7 +786,7 @@ class SecureStorage {
769
786
  catch (error) {
770
787
  this.logger.error(`Error al guardar dato encriptado para ${key}:`, error);
771
788
  this.eventEmitter.emit('error', { key, error: error });
772
- localStorage.setItem(key, value);
789
+ this.config.storage.storageEngine.setItem(key, value);
773
790
  }
774
791
  }
775
792
  /**
@@ -822,8 +839,12 @@ class SecureStorage {
822
839
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
823
840
  // Encrypt the value
824
841
  const encryptedValue = await this.encryptionHelper.encrypt(serialized);
825
- // Store in localStorage
826
- localStorage.setItem(encryptedKey, encryptedValue);
842
+ // Store in storage
843
+ this.config.storage.storageEngine.setItem(encryptedKey, encryptedValue);
844
+ // Update cache
845
+ if (this.config.storage.enableCache) {
846
+ this.memoryCache.set(key, { value, expiresAt });
847
+ }
827
848
  this.logger.verbose(`Stored key with expiry: ${key}, expiresAt: ${expiresAt}`);
828
849
  this.eventEmitter.emit('encrypted', { key, metadata: { compressed, expiresAt } });
829
850
  this.logger.timeEnd(`setItemWithExpiry:${key}`);
@@ -840,16 +861,31 @@ class SecureStorage {
840
861
  async getItem(key) {
841
862
  this.logger.time(`getItem:${key}`);
842
863
  if (!EncryptionHelper.isSupported()) {
843
- return localStorage.getItem(key);
864
+ return this.config.storage.storageEngine.getItem(key);
844
865
  }
845
866
  try {
867
+ // Check memory cache first
868
+ if (this.config.storage.enableCache && this.memoryCache.has(key)) {
869
+ const cached = this.memoryCache.get(key);
870
+ if (cached.expiresAt && cached.expiresAt < Date.now()) {
871
+ this.logger.info(`Key expired in cache: ${key}`);
872
+ await this.removeItem(key);
873
+ this.eventEmitter.emit('expired', { key });
874
+ this.logger.timeEnd(`getItem:${key}`);
875
+ return null;
876
+ }
877
+ this.logger.debug(`Retrieved from cache: ${key}`);
878
+ this.eventEmitter.emit('decrypted', { key });
879
+ this.logger.timeEnd(`getItem:${key}`);
880
+ return cached.value;
881
+ }
846
882
  // Encrypt the key
847
883
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
848
884
  // Get encrypted value
849
- let encryptedValue = localStorage.getItem(encryptedKey);
885
+ let encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
850
886
  // Backward compatibility: try with plain key
851
887
  if (!encryptedValue) {
852
- encryptedValue = localStorage.getItem(key);
888
+ encryptedValue = this.config.storage.storageEngine.getItem(key);
853
889
  if (!encryptedValue) {
854
890
  this.logger.timeEnd(`getItem:${key}`);
855
891
  return null;
@@ -885,8 +921,8 @@ class SecureStorage {
885
921
  this.logger.timeEnd(`getItem:${key}`);
886
922
  return null;
887
923
  }
888
- // Verify integrity if checksum exists
889
- if (storedValue.checksum) {
924
+ // Verify integrity if checksum exists and configured
925
+ if (storedValue.checksum && this.config.storage.verifyIntegrityOnRead) {
890
926
  const calculatedChecksum = await this.calculateChecksum(storedValue.value);
891
927
  if (calculatedChecksum !== storedValue.checksum) {
892
928
  this.logger.warn(`Integrity check failed for key: ${key}`);
@@ -904,6 +940,9 @@ class SecureStorage {
904
940
  finalValue = await decompress(new Uint8Array(compressedData));
905
941
  this.eventEmitter.emit('decompressed', { key });
906
942
  }
943
+ if (this.config.storage.enableCache) {
944
+ this.memoryCache.set(key, { value: finalValue, expiresAt: storedValue.expiresAt });
945
+ }
907
946
  this.eventEmitter.emit('decrypted', { key });
908
947
  this.logger.timeEnd(`getItem:${key}`);
909
948
  return finalValue;
@@ -912,7 +951,7 @@ class SecureStorage {
912
951
  this.logger.error(`Error al recuperar dato encriptado para ${key}:`, error);
913
952
  this.eventEmitter.emit('error', { key, error: error });
914
953
  // Fallback: try plain key
915
- const fallback = localStorage.getItem(key);
954
+ const fallback = this.config.storage.storageEngine.getItem(key);
916
955
  this.logger.timeEnd(`getItem:${key}`);
917
956
  return fallback;
918
957
  }
@@ -922,19 +961,20 @@ class SecureStorage {
922
961
  */
923
962
  async removeItem(key) {
924
963
  if (!EncryptionHelper.isSupported()) {
925
- localStorage.removeItem(key);
964
+ this.config.storage.storageEngine.removeItem(key);
926
965
  return;
927
966
  }
928
967
  try {
929
968
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
930
- localStorage.removeItem(encryptedKey);
931
- localStorage.removeItem(key); // Remove both versions
969
+ this.config.storage.storageEngine.removeItem(encryptedKey);
970
+ this.config.storage.storageEngine.removeItem(key); // Remove both versions
971
+ this.memoryCache.delete(key);
932
972
  this.eventEmitter.emit('deleted', { key });
933
973
  this.logger.info(`Removed key: ${key}`);
934
974
  }
935
975
  catch (error) {
936
976
  this.logger.error(`Error al eliminar dato para ${key}:`, error);
937
- localStorage.removeItem(key);
977
+ this.config.storage.storageEngine.removeItem(key);
938
978
  }
939
979
  }
940
980
  /**
@@ -949,6 +989,7 @@ class SecureStorage {
949
989
  */
950
990
  clear() {
951
991
  this.encryptionHelper.clearEncryptedData();
992
+ this.memoryCache.clear();
952
993
  this.eventEmitter.emit('cleared', {});
953
994
  this.logger.info('All encrypted data cleared');
954
995
  }
@@ -959,21 +1000,28 @@ class SecureStorage {
959
1000
  this.logger.info('Starting cleanup of expired items...');
960
1001
  let cleanedCount = 0;
961
1002
  const keysToCheck = [];
962
- for (let i = 0; i < localStorage.length; i++) {
963
- const key = localStorage.key(i);
1003
+ for (let i = 0; i < this.config.storage.storageEngine.length; i++) {
1004
+ const key = this.config.storage.storageEngine.key(i);
964
1005
  if (key && key.startsWith('__enc_')) {
965
1006
  keysToCheck.push(key);
966
1007
  }
967
1008
  }
968
1009
  for (const encryptedKey of keysToCheck) {
969
1010
  try {
970
- const encryptedValue = localStorage.getItem(encryptedKey);
1011
+ const encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
971
1012
  if (!encryptedValue)
972
1013
  continue;
973
1014
  const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
974
1015
  const storedValue = JSON.parse(decrypted);
975
1016
  if (storedValue.expiresAt && storedValue.expiresAt < Date.now()) {
976
- localStorage.removeItem(encryptedKey);
1017
+ this.config.storage.storageEngine.removeItem(encryptedKey);
1018
+ // Encontrar la clave original en el cache y eliminarla
1019
+ for (const [cacheKey] of Array.from(this.memoryCache.entries())) {
1020
+ this.encryptionHelper.encryptKey(cacheKey).then(enc => {
1021
+ if (enc === encryptedKey)
1022
+ this.memoryCache.delete(cacheKey);
1023
+ });
1024
+ }
977
1025
  cleanedCount++;
978
1026
  this.eventEmitter.emit('expired', { key: encryptedKey });
979
1027
  }
@@ -991,7 +1039,7 @@ class SecureStorage {
991
1039
  async verifyIntegrity(key) {
992
1040
  try {
993
1041
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
994
- const encryptedValue = localStorage.getItem(encryptedKey);
1042
+ const encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
995
1043
  if (!encryptedValue)
996
1044
  return false;
997
1045
  const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
@@ -1014,7 +1062,7 @@ class SecureStorage {
1014
1062
  async getIntegrityInfo(key) {
1015
1063
  try {
1016
1064
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
1017
- const encryptedValue = localStorage.getItem(encryptedKey);
1065
+ const encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
1018
1066
  if (!encryptedValue)
1019
1067
  return null;
1020
1068
  const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
@@ -1097,7 +1145,7 @@ class SecureStorage {
1097
1145
  this.logger.info(`Iniciando migración de ${keys.length} claves...`);
1098
1146
  for (const key of keys) {
1099
1147
  try {
1100
- const value = localStorage.getItem(key);
1148
+ const value = this.config.storage.storageEngine.getItem(key);
1101
1149
  if (value === null)
1102
1150
  continue;
1103
1151
  // Try to decrypt to check if already encrypted
@@ -1110,7 +1158,7 @@ class SecureStorage {
1110
1158
  // Not encrypted, proceed with migration
1111
1159
  }
1112
1160
  await this.setItem(key, value);
1113
- localStorage.removeItem(key);
1161
+ this.config.storage.storageEngine.removeItem(key);
1114
1162
  this.logger.info(`✓ ${key} migrado exitosamente`);
1115
1163
  }
1116
1164
  catch (error) {
@@ -1124,8 +1172,8 @@ class SecureStorage {
1124
1172
  */
1125
1173
  getDebugInfo() {
1126
1174
  const allKeys = [];
1127
- for (let i = 0; i < localStorage.length; i++) {
1128
- const key = localStorage.key(i);
1175
+ for (let i = 0; i < this.config.storage.storageEngine.length; i++) {
1176
+ const key = this.config.storage.storageEngine.key(i);
1129
1177
  if (key)
1130
1178
  allKeys.push(key);
1131
1179
  }
@@ -1164,6 +1212,194 @@ class SecureStorage {
1164
1212
  }
1165
1213
  SecureStorage.instance = null;
1166
1214
 
1215
+ /**
1216
+ * SecureCookie - API de alto nivel para almacenamiento en cookies cifradas
1217
+ * Soporta opciones de cookies incluyendo domininios/subdominios y compresión.
1218
+ */
1219
+ class SecureCookie {
1220
+ constructor(config) {
1221
+ this.config = {
1222
+ encryption: { ...DEFAULT_CONFIG.encryption, ...config?.encryption },
1223
+ cookieOptions: { path: '/', ...config?.cookieOptions },
1224
+ compression: config?.compression ?? true,
1225
+ debug: { ...DEFAULT_CONFIG.debug, ...config?.debug }
1226
+ };
1227
+ this.logger = new Logger(this.config.debug);
1228
+ this.encryptionHelper = new EncryptionHelper(this.config.encryption);
1229
+ this.logger.info('SecureCookie initialized', this.config);
1230
+ }
1231
+ /**
1232
+ * Obtiene la instancia singleton de SecureCookie
1233
+ */
1234
+ static getInstance(config) {
1235
+ if (!SecureCookie.instance) {
1236
+ SecureCookie.instance = new SecureCookie(config);
1237
+ }
1238
+ return SecureCookie.instance;
1239
+ }
1240
+ /**
1241
+ * Serializa las opciones de cookie en un string
1242
+ */
1243
+ serializeOptions(options) {
1244
+ let cookieString = '';
1245
+ if (options.expires) {
1246
+ cookieString += `; expires=${options.expires.toUTCString()}`;
1247
+ }
1248
+ if (options.maxAge) {
1249
+ cookieString += `; max-age=${options.maxAge}`;
1250
+ }
1251
+ if (options.domain) {
1252
+ cookieString += `; domain=${options.domain}`;
1253
+ }
1254
+ if (options.path) {
1255
+ cookieString += `; path=${options.path}`;
1256
+ }
1257
+ if (options.secure) {
1258
+ cookieString += `; secure`;
1259
+ }
1260
+ if (options.sameSite) {
1261
+ cookieString += `; samesite=${options.sameSite}`;
1262
+ }
1263
+ return cookieString;
1264
+ }
1265
+ /**
1266
+ * Guarda un valor encriptado en una cookie
1267
+ */
1268
+ async set(name, value, options) {
1269
+ this.logger.time(`cookie:set:${name}`);
1270
+ if (!EncryptionHelper.isSupported()) {
1271
+ this.logger.warn('Web Crypto API no soportada, guardando cookie sin cifrar');
1272
+ const mergedOptions = { ...this.config.cookieOptions, ...options };
1273
+ document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${this.serializeOptions(mergedOptions)}`;
1274
+ return;
1275
+ }
1276
+ try {
1277
+ // Check compression (aggressively compress cookies to save space since max is ~4KB)
1278
+ const shouldCompressData = this.config.compression && shouldCompress(value, 200);
1279
+ let processedValue = value;
1280
+ let metadataPrefix = 'P_'; // P_ = Plain
1281
+ if (shouldCompressData) {
1282
+ this.logger.debug(`Compressing cookie value for: ${name}`);
1283
+ const compressedData = await compress(value);
1284
+ processedValue = this.encryptionHelper['arrayBufferToBase64'](compressedData.buffer);
1285
+ metadataPrefix = 'C_'; // C_ = Compressed
1286
+ }
1287
+ // Encrypt key name
1288
+ const encryptedKey = await this.encryptionHelper.encryptKey(name);
1289
+ // Serialize and Encrypt Value
1290
+ // Adding metadata prefix to avoid bloating value with massive StoredValue JSON headers
1291
+ const encryptedData = await this.encryptionHelper.encrypt(processedValue);
1292
+ const finalValue = metadataPrefix + encryptedData;
1293
+ // Prepare Cookie string
1294
+ const mergedOptions = { ...this.config.cookieOptions, ...options };
1295
+ const cookieOptionsStr = this.serializeOptions(mergedOptions);
1296
+ const cookieString = `${encodeURIComponent(encryptedKey)}=${encodeURIComponent(finalValue)}${cookieOptionsStr}`;
1297
+ // Verificación de tamaño de cookie
1298
+ if (cookieString.length > 4096) {
1299
+ this.logger.warn(`Cookie '${name}' es muy grande (${cookieString.length} bytes). Puede ser rechazada por el navegador.`);
1300
+ }
1301
+ document.cookie = cookieString;
1302
+ this.logger.verbose(`Stored cookie: ${name}`);
1303
+ this.logger.timeEnd(`cookie:set:${name}`);
1304
+ }
1305
+ catch (error) {
1306
+ this.logger.error(`Error al guardar cookie encriptada para ${name}:`, error);
1307
+ // Fallback sin cifrar como último recurso
1308
+ const mergedOptions = { ...this.config.cookieOptions, ...options };
1309
+ document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${this.serializeOptions(mergedOptions)}`;
1310
+ }
1311
+ }
1312
+ /**
1313
+ * Pide una cookie por nombre
1314
+ * Retorna el string o null si no existe
1315
+ */
1316
+ getRawCookie(name) {
1317
+ const nameEQ = encodeURIComponent(name) + '=';
1318
+ const ca = document.cookie.split(';');
1319
+ for (let i = 0; i < ca.length; i++) {
1320
+ let c = ca[i];
1321
+ while (c.charAt(0) === ' ')
1322
+ c = c.substring(1, c.length);
1323
+ if (c.indexOf(nameEQ) === 0)
1324
+ return decodeURIComponent(c.substring(nameEQ.length, c.length));
1325
+ }
1326
+ return null;
1327
+ }
1328
+ /**
1329
+ * Recupera y desencripta un valor de cookie
1330
+ */
1331
+ async get(name) {
1332
+ this.logger.time(`cookie:get:${name}`);
1333
+ if (!EncryptionHelper.isSupported()) {
1334
+ return this.getRawCookie(name);
1335
+ }
1336
+ try {
1337
+ // Find encrypted key
1338
+ const encryptedKey = await this.encryptionHelper.encryptKey(name);
1339
+ let rawValue = this.getRawCookie(encryptedKey);
1340
+ if (!rawValue) {
1341
+ // Backward compatibility just in case fallback was used
1342
+ rawValue = this.getRawCookie(name);
1343
+ if (!rawValue) {
1344
+ this.logger.timeEnd(`cookie:get:${name}`);
1345
+ return null;
1346
+ }
1347
+ }
1348
+ // Check if it has our metadata prefixes
1349
+ if (!rawValue.startsWith('P_') && !rawValue.startsWith('C_')) {
1350
+ // Podría ser un fallback plano
1351
+ this.logger.timeEnd(`cookie:get:${name}`);
1352
+ return rawValue; // Asumimos que es plano
1353
+ }
1354
+ const isCompressed = rawValue.startsWith('C_');
1355
+ const encryptedData = rawValue.substring(2);
1356
+ // Decrypt
1357
+ const decryptedString = await this.encryptionHelper.decrypt(encryptedData);
1358
+ // Decompress if needed
1359
+ let finalValue = decryptedString;
1360
+ if (isCompressed) {
1361
+ const compressedBuffer = this.encryptionHelper['base64ToArrayBuffer'](decryptedString);
1362
+ finalValue = await decompress(new Uint8Array(compressedBuffer));
1363
+ }
1364
+ this.logger.timeEnd(`cookie:get:${name}`);
1365
+ return finalValue;
1366
+ }
1367
+ catch (error) {
1368
+ this.logger.error(`Error al recuperar cookie encriptada para ${name}:`, error);
1369
+ // Fallback
1370
+ return this.getRawCookie(name);
1371
+ }
1372
+ }
1373
+ /**
1374
+ * Elimina una cookie
1375
+ */
1376
+ async remove(name, options) {
1377
+ const mergedOptions = { ...this.config.cookieOptions, ...options };
1378
+ // Expirar la cookie configurándole una fecha del pasado
1379
+ const expireOptions = {
1380
+ ...mergedOptions,
1381
+ expires: new Date(0),
1382
+ maxAge: 0
1383
+ };
1384
+ if (!EncryptionHelper.isSupported()) {
1385
+ document.cookie = `${encodeURIComponent(name)}=${this.serializeOptions(expireOptions)}`;
1386
+ return;
1387
+ }
1388
+ const encryptedKey = await this.encryptionHelper.encryptKey(name);
1389
+ // Remove encrypted cookie
1390
+ document.cookie = `${encodeURIComponent(encryptedKey)}=${this.serializeOptions(expireOptions)}`;
1391
+ // Also remove plain version just in case
1392
+ document.cookie = `${encodeURIComponent(name)}=${this.serializeOptions(expireOptions)}`;
1393
+ }
1394
+ /**
1395
+ * Limpia la instancia actual (útil para testing o refresco)
1396
+ */
1397
+ destroy() {
1398
+ SecureCookie.instance = null;
1399
+ }
1400
+ }
1401
+ SecureCookie.instance = null;
1402
+
1167
1403
  const secureStorage$2 = SecureStorage.getInstance();
1168
1404
  /**
1169
1405
  * Función de debug para verificar el estado del sistema de encriptación
@@ -1216,6 +1452,202 @@ async function forceMigration(customKeys) {
1216
1452
  await debugEncryptionState();
1217
1453
  }
1218
1454
 
1455
+ /**
1456
+ * PlainStorage - Una interfaz consistente con SecureStorage pero sin cifrado.
1457
+ * Envuelve localStorage o sessionStorage con serialización JSON.
1458
+ */
1459
+ class PlainStorage {
1460
+ constructor(storageEngine = typeof window !== 'undefined' ? window.localStorage : {}) {
1461
+ this.storage = storageEngine;
1462
+ this.logger = new Logger({ prefix: 'PlainStorage' });
1463
+ }
1464
+ async set(key, value) {
1465
+ try {
1466
+ const serialized = typeof value === 'string' ? value : JSON.stringify(value);
1467
+ this.storage.setItem(key, serialized);
1468
+ }
1469
+ catch (error) {
1470
+ this.logger.error(`Error saving unencrypted item ${key}:`, error);
1471
+ }
1472
+ }
1473
+ async get(key) {
1474
+ try {
1475
+ const value = this.storage.getItem(key);
1476
+ if (value === null)
1477
+ return null;
1478
+ try {
1479
+ return JSON.parse(value);
1480
+ }
1481
+ catch {
1482
+ return value; // Fallback for plain strings
1483
+ }
1484
+ }
1485
+ catch (error) {
1486
+ this.logger.error(`Error reading unencrypted item ${key}:`, error);
1487
+ return null;
1488
+ }
1489
+ }
1490
+ async remove(key) {
1491
+ this.storage.removeItem(key);
1492
+ }
1493
+ clear() {
1494
+ this.storage.clear();
1495
+ }
1496
+ }
1497
+
1498
+ /**
1499
+ * Crea una instancia de almacenamiento encriptado.
1500
+ * @param engine 'localStorage' o 'sessionStorage'
1501
+ * @param secretKey Llave secreta opcional para derivación de claves
1502
+ * @param config Configuración adicional opcional
1503
+ * @returns Instancia de SecureStorage
1504
+ */
1505
+ function createEncryptedStorage(engine = 'localStorage', secretKey, config) {
1506
+ const defaultEngine = typeof window !== 'undefined'
1507
+ ? (engine === 'sessionStorage' ? window.sessionStorage : window.localStorage)
1508
+ : {};
1509
+ const mergedConfig = {
1510
+ ...config,
1511
+ encryption: {
1512
+ ...config?.encryption,
1513
+ ...(secretKey ? { appIdentifier: secretKey } : {})
1514
+ },
1515
+ storage: {
1516
+ ...config?.storage,
1517
+ storageEngine: defaultEngine
1518
+ }
1519
+ };
1520
+ return SecureStorage.getInstance(mergedConfig);
1521
+ }
1522
+ /**
1523
+ * Crea una instancia de almacenamiento plano (sin cifrar).
1524
+ * @param engine 'localStorage' o 'sessionStorage'
1525
+ * @returns Instancia de PlainStorage
1526
+ */
1527
+ function createPlainStorage(engine = 'localStorage') {
1528
+ const defaultEngine = typeof window !== 'undefined'
1529
+ ? (engine === 'sessionStorage' ? window.sessionStorage : window.localStorage)
1530
+ : {};
1531
+ return new PlainStorage(defaultEngine);
1532
+ }
1533
+
1534
+ /**
1535
+ * PlainCookie - Una interfaz consistente con SecureCookie pero sin cifrado.
1536
+ */
1537
+ class PlainCookie {
1538
+ constructor(defaultOptions) {
1539
+ this.logger = new Logger({ prefix: 'PlainCookie' });
1540
+ this.defaultOptions = { path: '/', ...defaultOptions };
1541
+ }
1542
+ serializeOptions(options) {
1543
+ let cookieString = '';
1544
+ if (options.expires)
1545
+ cookieString += `; expires=${options.expires.toUTCString()}`;
1546
+ if (options.maxAge)
1547
+ cookieString += `; max-age=${options.maxAge}`;
1548
+ if (options.domain)
1549
+ cookieString += `; domain=${options.domain}`;
1550
+ if (options.path)
1551
+ cookieString += `; path=${options.path}`;
1552
+ if (options.secure)
1553
+ cookieString += `; secure`;
1554
+ if (options.sameSite)
1555
+ cookieString += `; samesite=${options.sameSite}`;
1556
+ return cookieString;
1557
+ }
1558
+ async set(name, value, options) {
1559
+ try {
1560
+ const serialized = typeof value === 'string' ? value : JSON.stringify(value);
1561
+ const mergedOptions = { ...this.defaultOptions, ...options };
1562
+ document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(serialized)}${this.serializeOptions(mergedOptions)}`;
1563
+ }
1564
+ catch (error) {
1565
+ this.logger.error(`Error saving unencrypted cookie ${name}:`, error);
1566
+ }
1567
+ }
1568
+ async get(name) {
1569
+ try {
1570
+ const nameEQ = encodeURIComponent(name) + '=';
1571
+ const ca = document.cookie.split(';');
1572
+ for (let i = 0; i < ca.length; i++) {
1573
+ let c = ca[i];
1574
+ while (c.charAt(0) === ' ')
1575
+ c = c.substring(1, c.length);
1576
+ if (c.indexOf(nameEQ) === 0) {
1577
+ const decoded = decodeURIComponent(c.substring(nameEQ.length, c.length));
1578
+ try {
1579
+ return JSON.parse(decoded);
1580
+ }
1581
+ catch {
1582
+ return decoded;
1583
+ }
1584
+ }
1585
+ }
1586
+ return null;
1587
+ }
1588
+ catch (error) {
1589
+ this.logger.error(`Error reading unencrypted cookie ${name}:`, error);
1590
+ return null;
1591
+ }
1592
+ }
1593
+ async remove(name, options) {
1594
+ const mergedOptions = { ...this.defaultOptions, ...options };
1595
+ document.cookie = `${encodeURIComponent(name)}=${this.serializeOptions({ ...mergedOptions, expires: new Date(0), maxAge: 0 })}`;
1596
+ }
1597
+ clearAll() {
1598
+ const cookies = document.cookie.split(';');
1599
+ for (let i = 0; i < cookies.length; i++) {
1600
+ const cookie = cookies[i];
1601
+ const eqPos = cookie.indexOf('=');
1602
+ const name = eqPos > -1 ? cookie.substring(0, eqPos).trim() : cookie.trim();
1603
+ this.remove(decodeURIComponent(name));
1604
+ }
1605
+ }
1606
+ }
1607
+
1608
+ /**
1609
+ * Crea una instancia de manejador de cookies encriptadas.
1610
+ * @param secretKey Llave secreta opcional para derivación de claves
1611
+ * @param domain Dominio opcional (incluir punto inicial para subdominios ej. '.dominio.com')
1612
+ * @param config Configuración adicional opcional
1613
+ * @returns Instancia de SecureCookie
1614
+ */
1615
+ function createEncryptedCookieManager(secretKey, domain, config) {
1616
+ const mergedConfig = {
1617
+ ...config,
1618
+ encryption: {
1619
+ ...config?.encryption,
1620
+ ...(secretKey ? { appIdentifier: secretKey } : {})
1621
+ },
1622
+ cookieOptions: {
1623
+ ...config?.cookieOptions,
1624
+ ...(domain ? { domain } : {})
1625
+ }
1626
+ };
1627
+ return SecureCookie.getInstance(mergedConfig);
1628
+ }
1629
+ /**
1630
+ * Crea una instancia de manejador de cookies plano (sin cifrar).
1631
+ * @param defaultOptions Opciones por defecto para las cookies (ej. domain)
1632
+ * @returns Instancia de PlainCookie
1633
+ */
1634
+ function createPlainCookieManager(defaultOptions) {
1635
+ return new PlainCookie(defaultOptions);
1636
+ }
1637
+
1638
+ /**
1639
+ * Instancia predeterminada de almacenamiento local (sin cifrar).
1640
+ */
1641
+ const localStore = createPlainStorage('localStorage');
1642
+ /**
1643
+ * Instancia predeterminada de almacenamiento de sesión (sin cifrar).
1644
+ */
1645
+ const sessionStore = createPlainStorage('sessionStorage');
1646
+ /**
1647
+ * Instancia predeterminada de gestor de cookies (sin cifrar).
1648
+ */
1649
+ const cookies = createPlainCookieManager();
1650
+
1219
1651
  /**
1220
1652
  * @bantis/local-cipher - Core Module
1221
1653
  * Framework-agnostic client-side encryption for browser storage
@@ -1225,8 +1657,9 @@ async function forceMigration(customKeys) {
1225
1657
  */
1226
1658
  // Core classes
1227
1659
  const secureStorage$1 = SecureStorage.getInstance();
1660
+ const secureCookie = SecureCookie.getInstance();
1228
1661
  // Version
1229
- const VERSION = '2.1.0';
1662
+ const VERSION = '2.2.0';
1230
1663
 
1231
1664
  /**
1232
1665
  * React Hooks for @bantis/local-cipher
@@ -1406,18 +1839,35 @@ function useSecureStorageDebug(storage) {
1406
1839
  }, [secureStorage]);
1407
1840
  return debugInfo;
1408
1841
  }
1409
- /**
1410
- * Hook para usar un namespace de SecureStorage
1411
- */
1412
- function useNamespace(namespace, storage) {
1413
- const secureStorage = storage || getDefaultStorage();
1414
- const [namespacedStorage] = useState(() => secureStorage.namespace(namespace));
1415
- return namespacedStorage;
1416
- }
1417
1842
  /**
1418
1843
  * Exportar la instancia de SecureStorage para uso directo
1419
1844
  */
1420
1845
  const secureStorage = getDefaultStorage();
1846
+ /**
1847
+ * Hook para sincronizar el estado de React con el Storage Manager (por defecto localStore en texto plano)
1848
+ */
1849
+ function useLocalStore(key, initialValue, storageManager = localStore) {
1850
+ const [value, setValue] = useState(initialValue);
1851
+ useEffect(() => {
1852
+ let mounted = true;
1853
+ storageManager.get(key).then((saved) => {
1854
+ if (!mounted)
1855
+ return;
1856
+ if (saved !== null) {
1857
+ setValue(saved);
1858
+ }
1859
+ else {
1860
+ storageManager.set(key, initialValue);
1861
+ }
1862
+ });
1863
+ return () => { mounted = false; };
1864
+ }, [key, storageManager]);
1865
+ const setNewValue = useCallback(async (newValue) => {
1866
+ setValue(newValue);
1867
+ await storageManager.set(key, newValue);
1868
+ }, [key, storageManager]);
1869
+ return [value, setNewValue];
1870
+ }
1421
1871
 
1422
- export { DEFAULT_CONFIG, EncryptionHelper, EventEmitter, KeyRotation, LIBRARY_VERSION, Logger, NamespacedStorage, STORAGE_VERSION, SecureStorage, VERSION, compress, debugEncryptionState, decompress, forceMigration, initializeSecureStorage, isCompressionSupported, secureStorage as reactSecureStorage, secureStorage$1 as secureStorage, shouldCompress, useNamespace, useSecureStorage, useSecureStorageDebug, useSecureStorageEvents, useSecureStorageItem, useSecureStorageWithExpiry };
1872
+ export { DEFAULT_CONFIG, EncryptionHelper, EventEmitter, KeyRotation, LIBRARY_VERSION, Logger, NamespacedStorage, PlainCookie, PlainStorage, STORAGE_VERSION, SecureCookie, SecureStorage, VERSION, compress, cookies, createEncryptedCookieManager, createEncryptedStorage, createPlainCookieManager, createPlainStorage, debugEncryptionState, decompress, forceMigration, initializeSecureStorage, isCompressionSupported, localStore, secureStorage as reactSecureStorage, secureCookie, secureStorage$1 as secureStorage, sessionStore, shouldCompress, useLocalStore, useSecureStorage, useSecureStorageDebug, useSecureStorageEvents, useSecureStorageItem, useSecureStorageWithExpiry };
1423
1873
  //# sourceMappingURL=react.esm.js.map