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