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