@bantis/local-cipher 2.1.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/angular.js CHANGED
@@ -23,6 +23,9 @@ const DEFAULT_CONFIG = {
23
23
  compressionThreshold: 1024,
24
24
  autoCleanup: true,
25
25
  cleanupInterval: 60000,
26
+ enableCache: true,
27
+ verifyIntegrityOnRead: false,
28
+ storageEngine: typeof window !== 'undefined' ? window.localStorage : {},
26
29
  },
27
30
  debug: {
28
31
  enabled: false,
@@ -50,6 +53,8 @@ class EncryptionHelper {
50
53
  this.baseKey = '';
51
54
  this.baseKeyPromise = null;
52
55
  this.keyVersion = 1;
56
+ // Cache para optimización de rendimiento
57
+ this.keyNameCache = new Map();
53
58
  this.config = { ...DEFAULT_CONFIG.encryption, ...config };
54
59
  // Load key version from storage
55
60
  const storedVersion = localStorage.getItem(EncryptionHelper.KEY_VERSION_KEY);
@@ -112,7 +117,7 @@ class EncryptionHelper {
112
117
  // Derivar la clave AES-GCM
113
118
  return crypto.subtle.deriveKey({
114
119
  name: 'PBKDF2',
115
- salt,
120
+ salt: salt,
116
121
  iterations: this.config.iterations,
117
122
  hash: EncryptionHelper.HASH_ALGORITHM,
118
123
  }, keyMaterial, {
@@ -219,10 +224,16 @@ class EncryptionHelper {
219
224
  * @returns Nombre encriptado con prefijo __enc_
220
225
  */
221
226
  async encryptKey(keyName) {
227
+ // Optimización: devolver desde cache si existe
228
+ if (this.keyNameCache.has(keyName)) {
229
+ return this.keyNameCache.get(keyName);
230
+ }
222
231
  const baseKey = await this.generateBaseKey();
223
232
  const combined = keyName + baseKey;
224
233
  const hash = await this.hashString(combined);
225
- return `__enc_${hash.substring(0, 16)}`;
234
+ const encryptedKey = `__enc_${hash.substring(0, 16)}`;
235
+ this.keyNameCache.set(keyName, encryptedKey);
236
+ return encryptedKey;
226
237
  }
227
238
  /**
228
239
  * Limpia todos los datos encriptados del localStorage
@@ -240,10 +251,11 @@ class EncryptionHelper {
240
251
  keysToRemove.forEach(key => localStorage.removeItem(key));
241
252
  // Eliminar salt
242
253
  localStorage.removeItem(EncryptionHelper.SALT_STORAGE_KEY);
243
- // Resetear clave en memoria
254
+ // Resetear clave en memoria y cache
244
255
  this.key = null;
245
256
  this.baseKey = '';
246
257
  this.baseKeyPromise = null;
258
+ this.keyNameCache.clear();
247
259
  }
248
260
  /**
249
261
  * Verifica si el navegador soporta Web Crypto API
@@ -394,7 +406,7 @@ class Logger {
394
406
  return false;
395
407
  return Logger.LOG_LEVELS[level] <= Logger.LOG_LEVELS[this.logLevel];
396
408
  }
397
- formatMessage(level, message, ...args) {
409
+ formatMessage(level, message) {
398
410
  const timestamp = new Date().toISOString();
399
411
  return `[${this.prefix}] [${level.toUpperCase()}] ${timestamp} - ${message}`;
400
412
  }
@@ -547,17 +559,37 @@ class NamespacedStorage {
547
559
  this.storage = storage;
548
560
  this.prefix = `__ns_${namespace}__`;
549
561
  }
562
+ async getIndex() {
563
+ const indexValue = await this.storage.getItem(`${this.prefix}__index__`);
564
+ return indexValue ? JSON.parse(indexValue) : [];
565
+ }
566
+ async saveToIndex(key) {
567
+ const index = await this.getIndex();
568
+ if (!index.includes(key)) {
569
+ index.push(key);
570
+ await this.storage.setItem(`${this.prefix}__index__`, JSON.stringify(index));
571
+ }
572
+ }
573
+ async removeFromIndex(key) {
574
+ const index = await this.getIndex();
575
+ const newIndex = index.filter(k => k !== key);
576
+ if (newIndex.length !== index.length) {
577
+ await this.storage.setItem(`${this.prefix}__index__`, JSON.stringify(newIndex));
578
+ }
579
+ }
550
580
  /**
551
581
  * Set item in this namespace
552
582
  */
553
583
  async setItem(key, value) {
554
- return this.storage.setItem(`${this.prefix}${key}`, value);
584
+ await this.storage.setItem(`${this.prefix}${key}`, value);
585
+ await this.saveToIndex(key);
555
586
  }
556
587
  /**
557
588
  * Set item with expiry in this namespace
558
589
  */
559
590
  async setItemWithExpiry(key, value, options) {
560
- return this.storage.setItemWithExpiry(`${this.prefix}${key}`, value, options);
591
+ await this.storage.setItemWithExpiry(`${this.prefix}${key}`, value, options);
592
+ await this.saveToIndex(key);
561
593
  }
562
594
  /**
563
595
  * Get item from this namespace
@@ -569,7 +601,8 @@ class NamespacedStorage {
569
601
  * Remove item from this namespace
570
602
  */
571
603
  async removeItem(key) {
572
- return this.storage.removeItem(`${this.prefix}${key}`);
604
+ await this.storage.removeItem(`${this.prefix}${key}`);
605
+ await this.removeFromIndex(key);
573
606
  }
574
607
  /**
575
608
  * Check if item exists in this namespace
@@ -581,29 +614,17 @@ class NamespacedStorage {
581
614
  * Clear all items in this namespace
582
615
  */
583
616
  async clearNamespace() {
584
- const keysToRemove = [];
585
- for (let i = 0; i < localStorage.length; i++) {
586
- const key = localStorage.key(i);
587
- if (key && key.includes(this.prefix)) {
588
- keysToRemove.push(key.replace(this.prefix, ''));
589
- }
590
- }
617
+ const keysToRemove = await this.getIndex();
591
618
  for (const key of keysToRemove) {
592
- await this.removeItem(key);
619
+ await this.storage.removeItem(`${this.prefix}${key}`);
593
620
  }
621
+ await this.storage.removeItem(`${this.prefix}__index__`);
594
622
  }
595
623
  /**
596
624
  * Get all keys in this namespace
597
625
  */
598
626
  async keys() {
599
- const keys = [];
600
- for (let i = 0; i < localStorage.length; i++) {
601
- const key = localStorage.key(i);
602
- if (key && key.includes(this.prefix)) {
603
- keys.push(key.replace(this.prefix, ''));
604
- }
605
- }
606
- return keys;
627
+ return this.getIndex();
607
628
  }
608
629
  }
609
630
 
@@ -626,7 +647,6 @@ async function compress(data) {
626
647
  return encoder.encode(data);
627
648
  }
628
649
  try {
629
- const encoder = new TextEncoder();
630
650
  const stream = new Blob([data]).stream();
631
651
  const compressedStream = stream.pipeThrough(new CompressionStream('gzip'));
632
652
  const compressedBlob = await new Response(compressedStream).blob();
@@ -675,6 +695,7 @@ function shouldCompress(data, threshold = 1024) {
675
695
  class SecureStorage {
676
696
  constructor(config) {
677
697
  this.cleanupInterval = null;
698
+ this.memoryCache = new Map();
678
699
  // Merge config with defaults
679
700
  this.config = {
680
701
  encryption: { ...DEFAULT_CONFIG.encryption, ...config?.encryption },
@@ -756,8 +777,12 @@ class SecureStorage {
756
777
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
757
778
  // Encrypt the value
758
779
  const encryptedValue = await this.encryptionHelper.encrypt(serialized);
759
- // Store in localStorage
760
- localStorage.setItem(encryptedKey, encryptedValue);
780
+ // Store in storage
781
+ this.config.storage.storageEngine.setItem(encryptedKey, encryptedValue);
782
+ // Update cache
783
+ if (this.config.storage.enableCache) {
784
+ this.memoryCache.set(key, { value });
785
+ }
761
786
  this.logger.verbose(`Stored key: ${key}, compressed: ${compressed}, size: ${encryptedValue.length}`);
762
787
  this.eventEmitter.emit('encrypted', { key, metadata: { compressed, size: encryptedValue.length } });
763
788
  this.logger.timeEnd(`setItem:${key}`);
@@ -765,7 +790,7 @@ class SecureStorage {
765
790
  catch (error) {
766
791
  this.logger.error(`Error al guardar dato encriptado para ${key}:`, error);
767
792
  this.eventEmitter.emit('error', { key, error: error });
768
- localStorage.setItem(key, value);
793
+ this.config.storage.storageEngine.setItem(key, value);
769
794
  }
770
795
  }
771
796
  /**
@@ -818,8 +843,12 @@ class SecureStorage {
818
843
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
819
844
  // Encrypt the value
820
845
  const encryptedValue = await this.encryptionHelper.encrypt(serialized);
821
- // Store in localStorage
822
- localStorage.setItem(encryptedKey, encryptedValue);
846
+ // Store in storage
847
+ this.config.storage.storageEngine.setItem(encryptedKey, encryptedValue);
848
+ // Update cache
849
+ if (this.config.storage.enableCache) {
850
+ this.memoryCache.set(key, { value, expiresAt });
851
+ }
823
852
  this.logger.verbose(`Stored key with expiry: ${key}, expiresAt: ${expiresAt}`);
824
853
  this.eventEmitter.emit('encrypted', { key, metadata: { compressed, expiresAt } });
825
854
  this.logger.timeEnd(`setItemWithExpiry:${key}`);
@@ -836,16 +865,31 @@ class SecureStorage {
836
865
  async getItem(key) {
837
866
  this.logger.time(`getItem:${key}`);
838
867
  if (!EncryptionHelper.isSupported()) {
839
- return localStorage.getItem(key);
868
+ return this.config.storage.storageEngine.getItem(key);
840
869
  }
841
870
  try {
871
+ // Check memory cache first
872
+ if (this.config.storage.enableCache && this.memoryCache.has(key)) {
873
+ const cached = this.memoryCache.get(key);
874
+ if (cached.expiresAt && cached.expiresAt < Date.now()) {
875
+ this.logger.info(`Key expired in cache: ${key}`);
876
+ await this.removeItem(key);
877
+ this.eventEmitter.emit('expired', { key });
878
+ this.logger.timeEnd(`getItem:${key}`);
879
+ return null;
880
+ }
881
+ this.logger.debug(`Retrieved from cache: ${key}`);
882
+ this.eventEmitter.emit('decrypted', { key });
883
+ this.logger.timeEnd(`getItem:${key}`);
884
+ return cached.value;
885
+ }
842
886
  // Encrypt the key
843
887
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
844
888
  // Get encrypted value
845
- let encryptedValue = localStorage.getItem(encryptedKey);
889
+ let encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
846
890
  // Backward compatibility: try with plain key
847
891
  if (!encryptedValue) {
848
- encryptedValue = localStorage.getItem(key);
892
+ encryptedValue = this.config.storage.storageEngine.getItem(key);
849
893
  if (!encryptedValue) {
850
894
  this.logger.timeEnd(`getItem:${key}`);
851
895
  return null;
@@ -881,8 +925,8 @@ class SecureStorage {
881
925
  this.logger.timeEnd(`getItem:${key}`);
882
926
  return null;
883
927
  }
884
- // Verify integrity if checksum exists
885
- if (storedValue.checksum) {
928
+ // Verify integrity if checksum exists and configured
929
+ if (storedValue.checksum && this.config.storage.verifyIntegrityOnRead) {
886
930
  const calculatedChecksum = await this.calculateChecksum(storedValue.value);
887
931
  if (calculatedChecksum !== storedValue.checksum) {
888
932
  this.logger.warn(`Integrity check failed for key: ${key}`);
@@ -900,6 +944,9 @@ class SecureStorage {
900
944
  finalValue = await decompress(new Uint8Array(compressedData));
901
945
  this.eventEmitter.emit('decompressed', { key });
902
946
  }
947
+ if (this.config.storage.enableCache) {
948
+ this.memoryCache.set(key, { value: finalValue, expiresAt: storedValue.expiresAt });
949
+ }
903
950
  this.eventEmitter.emit('decrypted', { key });
904
951
  this.logger.timeEnd(`getItem:${key}`);
905
952
  return finalValue;
@@ -908,7 +955,7 @@ class SecureStorage {
908
955
  this.logger.error(`Error al recuperar dato encriptado para ${key}:`, error);
909
956
  this.eventEmitter.emit('error', { key, error: error });
910
957
  // Fallback: try plain key
911
- const fallback = localStorage.getItem(key);
958
+ const fallback = this.config.storage.storageEngine.getItem(key);
912
959
  this.logger.timeEnd(`getItem:${key}`);
913
960
  return fallback;
914
961
  }
@@ -918,19 +965,20 @@ class SecureStorage {
918
965
  */
919
966
  async removeItem(key) {
920
967
  if (!EncryptionHelper.isSupported()) {
921
- localStorage.removeItem(key);
968
+ this.config.storage.storageEngine.removeItem(key);
922
969
  return;
923
970
  }
924
971
  try {
925
972
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
926
- localStorage.removeItem(encryptedKey);
927
- localStorage.removeItem(key); // Remove both versions
973
+ this.config.storage.storageEngine.removeItem(encryptedKey);
974
+ this.config.storage.storageEngine.removeItem(key); // Remove both versions
975
+ this.memoryCache.delete(key);
928
976
  this.eventEmitter.emit('deleted', { key });
929
977
  this.logger.info(`Removed key: ${key}`);
930
978
  }
931
979
  catch (error) {
932
980
  this.logger.error(`Error al eliminar dato para ${key}:`, error);
933
- localStorage.removeItem(key);
981
+ this.config.storage.storageEngine.removeItem(key);
934
982
  }
935
983
  }
936
984
  /**
@@ -945,6 +993,7 @@ class SecureStorage {
945
993
  */
946
994
  clear() {
947
995
  this.encryptionHelper.clearEncryptedData();
996
+ this.memoryCache.clear();
948
997
  this.eventEmitter.emit('cleared', {});
949
998
  this.logger.info('All encrypted data cleared');
950
999
  }
@@ -955,21 +1004,28 @@ class SecureStorage {
955
1004
  this.logger.info('Starting cleanup of expired items...');
956
1005
  let cleanedCount = 0;
957
1006
  const keysToCheck = [];
958
- for (let i = 0; i < localStorage.length; i++) {
959
- const key = localStorage.key(i);
1007
+ for (let i = 0; i < this.config.storage.storageEngine.length; i++) {
1008
+ const key = this.config.storage.storageEngine.key(i);
960
1009
  if (key && key.startsWith('__enc_')) {
961
1010
  keysToCheck.push(key);
962
1011
  }
963
1012
  }
964
1013
  for (const encryptedKey of keysToCheck) {
965
1014
  try {
966
- const encryptedValue = localStorage.getItem(encryptedKey);
1015
+ const encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
967
1016
  if (!encryptedValue)
968
1017
  continue;
969
1018
  const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
970
1019
  const storedValue = JSON.parse(decrypted);
971
1020
  if (storedValue.expiresAt && storedValue.expiresAt < Date.now()) {
972
- localStorage.removeItem(encryptedKey);
1021
+ this.config.storage.storageEngine.removeItem(encryptedKey);
1022
+ // Encontrar la clave original en el cache y eliminarla
1023
+ for (const [cacheKey] of Array.from(this.memoryCache.entries())) {
1024
+ this.encryptionHelper.encryptKey(cacheKey).then(enc => {
1025
+ if (enc === encryptedKey)
1026
+ this.memoryCache.delete(cacheKey);
1027
+ });
1028
+ }
973
1029
  cleanedCount++;
974
1030
  this.eventEmitter.emit('expired', { key: encryptedKey });
975
1031
  }
@@ -987,7 +1043,7 @@ class SecureStorage {
987
1043
  async verifyIntegrity(key) {
988
1044
  try {
989
1045
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
990
- const encryptedValue = localStorage.getItem(encryptedKey);
1046
+ const encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
991
1047
  if (!encryptedValue)
992
1048
  return false;
993
1049
  const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
@@ -1010,7 +1066,7 @@ class SecureStorage {
1010
1066
  async getIntegrityInfo(key) {
1011
1067
  try {
1012
1068
  const encryptedKey = await this.encryptionHelper.encryptKey(key);
1013
- const encryptedValue = localStorage.getItem(encryptedKey);
1069
+ const encryptedValue = this.config.storage.storageEngine.getItem(encryptedKey);
1014
1070
  if (!encryptedValue)
1015
1071
  return null;
1016
1072
  const decrypted = await this.encryptionHelper.decrypt(encryptedValue);
@@ -1093,7 +1149,7 @@ class SecureStorage {
1093
1149
  this.logger.info(`Iniciando migración de ${keys.length} claves...`);
1094
1150
  for (const key of keys) {
1095
1151
  try {
1096
- const value = localStorage.getItem(key);
1152
+ const value = this.config.storage.storageEngine.getItem(key);
1097
1153
  if (value === null)
1098
1154
  continue;
1099
1155
  // Try to decrypt to check if already encrypted
@@ -1106,7 +1162,7 @@ class SecureStorage {
1106
1162
  // Not encrypted, proceed with migration
1107
1163
  }
1108
1164
  await this.setItem(key, value);
1109
- localStorage.removeItem(key);
1165
+ this.config.storage.storageEngine.removeItem(key);
1110
1166
  this.logger.info(`✓ ${key} migrado exitosamente`);
1111
1167
  }
1112
1168
  catch (error) {
@@ -1120,8 +1176,8 @@ class SecureStorage {
1120
1176
  */
1121
1177
  getDebugInfo() {
1122
1178
  const allKeys = [];
1123
- for (let i = 0; i < localStorage.length; i++) {
1124
- const key = localStorage.key(i);
1179
+ for (let i = 0; i < this.config.storage.storageEngine.length; i++) {
1180
+ const key = this.config.storage.storageEngine.key(i);
1125
1181
  if (key)
1126
1182
  allKeys.push(key);
1127
1183
  }
@@ -1155,10 +1211,199 @@ class SecureStorage {
1155
1211
  }
1156
1212
  this.removeAllListeners();
1157
1213
  this.logger.info('SecureStorage destroyed');
1214
+ SecureStorage.instance = null;
1158
1215
  }
1159
1216
  }
1160
1217
  SecureStorage.instance = null;
1161
1218
 
1219
+ /**
1220
+ * SecureCookie - API de alto nivel para almacenamiento en cookies cifradas
1221
+ * Soporta opciones de cookies incluyendo domininios/subdominios y compresión.
1222
+ */
1223
+ class SecureCookie {
1224
+ constructor(config) {
1225
+ this.config = {
1226
+ encryption: { ...DEFAULT_CONFIG.encryption, ...config?.encryption },
1227
+ cookieOptions: { path: '/', ...config?.cookieOptions },
1228
+ compression: config?.compression ?? true,
1229
+ debug: { ...DEFAULT_CONFIG.debug, ...config?.debug }
1230
+ };
1231
+ this.logger = new Logger(this.config.debug);
1232
+ this.encryptionHelper = new EncryptionHelper(this.config.encryption);
1233
+ this.logger.info('SecureCookie initialized', this.config);
1234
+ }
1235
+ /**
1236
+ * Obtiene la instancia singleton de SecureCookie
1237
+ */
1238
+ static getInstance(config) {
1239
+ if (!SecureCookie.instance) {
1240
+ SecureCookie.instance = new SecureCookie(config);
1241
+ }
1242
+ return SecureCookie.instance;
1243
+ }
1244
+ /**
1245
+ * Serializa las opciones de cookie en un string
1246
+ */
1247
+ serializeOptions(options) {
1248
+ let cookieString = '';
1249
+ if (options.expires) {
1250
+ cookieString += `; expires=${options.expires.toUTCString()}`;
1251
+ }
1252
+ if (options.maxAge) {
1253
+ cookieString += `; max-age=${options.maxAge}`;
1254
+ }
1255
+ if (options.domain) {
1256
+ cookieString += `; domain=${options.domain}`;
1257
+ }
1258
+ if (options.path) {
1259
+ cookieString += `; path=${options.path}`;
1260
+ }
1261
+ if (options.secure) {
1262
+ cookieString += `; secure`;
1263
+ }
1264
+ if (options.sameSite) {
1265
+ cookieString += `; samesite=${options.sameSite}`;
1266
+ }
1267
+ return cookieString;
1268
+ }
1269
+ /**
1270
+ * Guarda un valor encriptado en una cookie
1271
+ */
1272
+ async set(name, value, options) {
1273
+ this.logger.time(`cookie:set:${name}`);
1274
+ if (!EncryptionHelper.isSupported()) {
1275
+ this.logger.warn('Web Crypto API no soportada, guardando cookie sin cifrar');
1276
+ const mergedOptions = { ...this.config.cookieOptions, ...options };
1277
+ document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${this.serializeOptions(mergedOptions)}`;
1278
+ return;
1279
+ }
1280
+ try {
1281
+ // Check compression (aggressively compress cookies to save space since max is ~4KB)
1282
+ const shouldCompressData = this.config.compression && shouldCompress(value, 200);
1283
+ let processedValue = value;
1284
+ let metadataPrefix = 'P_'; // P_ = Plain
1285
+ if (shouldCompressData) {
1286
+ this.logger.debug(`Compressing cookie value for: ${name}`);
1287
+ const compressedData = await compress(value);
1288
+ processedValue = this.encryptionHelper['arrayBufferToBase64'](compressedData.buffer);
1289
+ metadataPrefix = 'C_'; // C_ = Compressed
1290
+ }
1291
+ // Encrypt key name
1292
+ const encryptedKey = await this.encryptionHelper.encryptKey(name);
1293
+ // Serialize and Encrypt Value
1294
+ // Adding metadata prefix to avoid bloating value with massive StoredValue JSON headers
1295
+ const encryptedData = await this.encryptionHelper.encrypt(processedValue);
1296
+ const finalValue = metadataPrefix + encryptedData;
1297
+ // Prepare Cookie string
1298
+ const mergedOptions = { ...this.config.cookieOptions, ...options };
1299
+ const cookieOptionsStr = this.serializeOptions(mergedOptions);
1300
+ const cookieString = `${encodeURIComponent(encryptedKey)}=${encodeURIComponent(finalValue)}${cookieOptionsStr}`;
1301
+ // Verificación de tamaño de cookie
1302
+ if (cookieString.length > 4096) {
1303
+ this.logger.warn(`Cookie '${name}' es muy grande (${cookieString.length} bytes). Puede ser rechazada por el navegador.`);
1304
+ }
1305
+ document.cookie = cookieString;
1306
+ this.logger.verbose(`Stored cookie: ${name}`);
1307
+ this.logger.timeEnd(`cookie:set:${name}`);
1308
+ }
1309
+ catch (error) {
1310
+ this.logger.error(`Error al guardar cookie encriptada para ${name}:`, error);
1311
+ // Fallback sin cifrar como último recurso
1312
+ const mergedOptions = { ...this.config.cookieOptions, ...options };
1313
+ document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${this.serializeOptions(mergedOptions)}`;
1314
+ }
1315
+ }
1316
+ /**
1317
+ * Pide una cookie por nombre
1318
+ * Retorna el string o null si no existe
1319
+ */
1320
+ getRawCookie(name) {
1321
+ const nameEQ = encodeURIComponent(name) + '=';
1322
+ const ca = document.cookie.split(';');
1323
+ for (let i = 0; i < ca.length; i++) {
1324
+ let c = ca[i];
1325
+ while (c.charAt(0) === ' ')
1326
+ c = c.substring(1, c.length);
1327
+ if (c.indexOf(nameEQ) === 0)
1328
+ return decodeURIComponent(c.substring(nameEQ.length, c.length));
1329
+ }
1330
+ return null;
1331
+ }
1332
+ /**
1333
+ * Recupera y desencripta un valor de cookie
1334
+ */
1335
+ async get(name) {
1336
+ this.logger.time(`cookie:get:${name}`);
1337
+ if (!EncryptionHelper.isSupported()) {
1338
+ return this.getRawCookie(name);
1339
+ }
1340
+ try {
1341
+ // Find encrypted key
1342
+ const encryptedKey = await this.encryptionHelper.encryptKey(name);
1343
+ let rawValue = this.getRawCookie(encryptedKey);
1344
+ if (!rawValue) {
1345
+ // Backward compatibility just in case fallback was used
1346
+ rawValue = this.getRawCookie(name);
1347
+ if (!rawValue) {
1348
+ this.logger.timeEnd(`cookie:get:${name}`);
1349
+ return null;
1350
+ }
1351
+ }
1352
+ // Check if it has our metadata prefixes
1353
+ if (!rawValue.startsWith('P_') && !rawValue.startsWith('C_')) {
1354
+ // Podría ser un fallback plano
1355
+ this.logger.timeEnd(`cookie:get:${name}`);
1356
+ return rawValue; // Asumimos que es plano
1357
+ }
1358
+ const isCompressed = rawValue.startsWith('C_');
1359
+ const encryptedData = rawValue.substring(2);
1360
+ // Decrypt
1361
+ const decryptedString = await this.encryptionHelper.decrypt(encryptedData);
1362
+ // Decompress if needed
1363
+ let finalValue = decryptedString;
1364
+ if (isCompressed) {
1365
+ const compressedBuffer = this.encryptionHelper['base64ToArrayBuffer'](decryptedString);
1366
+ finalValue = await decompress(new Uint8Array(compressedBuffer));
1367
+ }
1368
+ this.logger.timeEnd(`cookie:get:${name}`);
1369
+ return finalValue;
1370
+ }
1371
+ catch (error) {
1372
+ this.logger.error(`Error al recuperar cookie encriptada para ${name}:`, error);
1373
+ // Fallback
1374
+ return this.getRawCookie(name);
1375
+ }
1376
+ }
1377
+ /**
1378
+ * Elimina una cookie
1379
+ */
1380
+ async remove(name, options) {
1381
+ const mergedOptions = { ...this.config.cookieOptions, ...options };
1382
+ // Expirar la cookie configurándole una fecha del pasado
1383
+ const expireOptions = {
1384
+ ...mergedOptions,
1385
+ expires: new Date(0),
1386
+ maxAge: 0
1387
+ };
1388
+ if (!EncryptionHelper.isSupported()) {
1389
+ document.cookie = `${encodeURIComponent(name)}=${this.serializeOptions(expireOptions)}`;
1390
+ return;
1391
+ }
1392
+ const encryptedKey = await this.encryptionHelper.encryptKey(name);
1393
+ // Remove encrypted cookie
1394
+ document.cookie = `${encodeURIComponent(encryptedKey)}=${this.serializeOptions(expireOptions)}`;
1395
+ // Also remove plain version just in case
1396
+ document.cookie = `${encodeURIComponent(name)}=${this.serializeOptions(expireOptions)}`;
1397
+ }
1398
+ /**
1399
+ * Limpia la instancia actual (útil para testing o refresco)
1400
+ */
1401
+ destroy() {
1402
+ SecureCookie.instance = null;
1403
+ }
1404
+ }
1405
+ SecureCookie.instance = null;
1406
+
1162
1407
  const secureStorage$1 = SecureStorage.getInstance();
1163
1408
  /**
1164
1409
  * Función de debug para verificar el estado del sistema de encriptación
@@ -1211,6 +1456,202 @@ async function forceMigration(customKeys) {
1211
1456
  await debugEncryptionState();
1212
1457
  }
1213
1458
 
1459
+ /**
1460
+ * PlainStorage - Una interfaz consistente con SecureStorage pero sin cifrado.
1461
+ * Envuelve localStorage o sessionStorage con serialización JSON.
1462
+ */
1463
+ class PlainStorage {
1464
+ constructor(storageEngine = typeof window !== 'undefined' ? window.localStorage : {}) {
1465
+ this.storage = storageEngine;
1466
+ this.logger = new Logger({ prefix: 'PlainStorage' });
1467
+ }
1468
+ async set(key, value) {
1469
+ try {
1470
+ const serialized = typeof value === 'string' ? value : JSON.stringify(value);
1471
+ this.storage.setItem(key, serialized);
1472
+ }
1473
+ catch (error) {
1474
+ this.logger.error(`Error saving unencrypted item ${key}:`, error);
1475
+ }
1476
+ }
1477
+ async get(key) {
1478
+ try {
1479
+ const value = this.storage.getItem(key);
1480
+ if (value === null)
1481
+ return null;
1482
+ try {
1483
+ return JSON.parse(value);
1484
+ }
1485
+ catch {
1486
+ return value; // Fallback for plain strings
1487
+ }
1488
+ }
1489
+ catch (error) {
1490
+ this.logger.error(`Error reading unencrypted item ${key}:`, error);
1491
+ return null;
1492
+ }
1493
+ }
1494
+ async remove(key) {
1495
+ this.storage.removeItem(key);
1496
+ }
1497
+ clear() {
1498
+ this.storage.clear();
1499
+ }
1500
+ }
1501
+
1502
+ /**
1503
+ * Crea una instancia de almacenamiento encriptado.
1504
+ * @param engine 'localStorage' o 'sessionStorage'
1505
+ * @param secretKey Llave secreta opcional para derivación de claves
1506
+ * @param config Configuración adicional opcional
1507
+ * @returns Instancia de SecureStorage
1508
+ */
1509
+ function createEncryptedStorage(engine = 'localStorage', secretKey, config) {
1510
+ const defaultEngine = typeof window !== 'undefined'
1511
+ ? (engine === 'sessionStorage' ? window.sessionStorage : window.localStorage)
1512
+ : {};
1513
+ const mergedConfig = {
1514
+ ...config,
1515
+ encryption: {
1516
+ ...config?.encryption,
1517
+ ...(secretKey ? { appIdentifier: secretKey } : {})
1518
+ },
1519
+ storage: {
1520
+ ...config?.storage,
1521
+ storageEngine: defaultEngine
1522
+ }
1523
+ };
1524
+ return SecureStorage.getInstance(mergedConfig);
1525
+ }
1526
+ /**
1527
+ * Crea una instancia de almacenamiento plano (sin cifrar).
1528
+ * @param engine 'localStorage' o 'sessionStorage'
1529
+ * @returns Instancia de PlainStorage
1530
+ */
1531
+ function createPlainStorage(engine = 'localStorage') {
1532
+ const defaultEngine = typeof window !== 'undefined'
1533
+ ? (engine === 'sessionStorage' ? window.sessionStorage : window.localStorage)
1534
+ : {};
1535
+ return new PlainStorage(defaultEngine);
1536
+ }
1537
+
1538
+ /**
1539
+ * PlainCookie - Una interfaz consistente con SecureCookie pero sin cifrado.
1540
+ */
1541
+ class PlainCookie {
1542
+ constructor(defaultOptions) {
1543
+ this.logger = new Logger({ prefix: 'PlainCookie' });
1544
+ this.defaultOptions = { path: '/', ...defaultOptions };
1545
+ }
1546
+ serializeOptions(options) {
1547
+ let cookieString = '';
1548
+ if (options.expires)
1549
+ cookieString += `; expires=${options.expires.toUTCString()}`;
1550
+ if (options.maxAge)
1551
+ cookieString += `; max-age=${options.maxAge}`;
1552
+ if (options.domain)
1553
+ cookieString += `; domain=${options.domain}`;
1554
+ if (options.path)
1555
+ cookieString += `; path=${options.path}`;
1556
+ if (options.secure)
1557
+ cookieString += `; secure`;
1558
+ if (options.sameSite)
1559
+ cookieString += `; samesite=${options.sameSite}`;
1560
+ return cookieString;
1561
+ }
1562
+ async set(name, value, options) {
1563
+ try {
1564
+ const serialized = typeof value === 'string' ? value : JSON.stringify(value);
1565
+ const mergedOptions = { ...this.defaultOptions, ...options };
1566
+ document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(serialized)}${this.serializeOptions(mergedOptions)}`;
1567
+ }
1568
+ catch (error) {
1569
+ this.logger.error(`Error saving unencrypted cookie ${name}:`, error);
1570
+ }
1571
+ }
1572
+ async get(name) {
1573
+ try {
1574
+ const nameEQ = encodeURIComponent(name) + '=';
1575
+ const ca = document.cookie.split(';');
1576
+ for (let i = 0; i < ca.length; i++) {
1577
+ let c = ca[i];
1578
+ while (c.charAt(0) === ' ')
1579
+ c = c.substring(1, c.length);
1580
+ if (c.indexOf(nameEQ) === 0) {
1581
+ const decoded = decodeURIComponent(c.substring(nameEQ.length, c.length));
1582
+ try {
1583
+ return JSON.parse(decoded);
1584
+ }
1585
+ catch {
1586
+ return decoded;
1587
+ }
1588
+ }
1589
+ }
1590
+ return null;
1591
+ }
1592
+ catch (error) {
1593
+ this.logger.error(`Error reading unencrypted cookie ${name}:`, error);
1594
+ return null;
1595
+ }
1596
+ }
1597
+ async remove(name, options) {
1598
+ const mergedOptions = { ...this.defaultOptions, ...options };
1599
+ document.cookie = `${encodeURIComponent(name)}=${this.serializeOptions({ ...mergedOptions, expires: new Date(0), maxAge: 0 })}`;
1600
+ }
1601
+ clearAll() {
1602
+ const cookies = document.cookie.split(';');
1603
+ for (let i = 0; i < cookies.length; i++) {
1604
+ const cookie = cookies[i];
1605
+ const eqPos = cookie.indexOf('=');
1606
+ const name = eqPos > -1 ? cookie.substring(0, eqPos).trim() : cookie.trim();
1607
+ this.remove(decodeURIComponent(name));
1608
+ }
1609
+ }
1610
+ }
1611
+
1612
+ /**
1613
+ * Crea una instancia de manejador de cookies encriptadas.
1614
+ * @param secretKey Llave secreta opcional para derivación de claves
1615
+ * @param domain Dominio opcional (incluir punto inicial para subdominios ej. '.dominio.com')
1616
+ * @param config Configuración adicional opcional
1617
+ * @returns Instancia de SecureCookie
1618
+ */
1619
+ function createEncryptedCookieManager(secretKey, domain, config) {
1620
+ const mergedConfig = {
1621
+ ...config,
1622
+ encryption: {
1623
+ ...config?.encryption,
1624
+ ...(secretKey ? { appIdentifier: secretKey } : {})
1625
+ },
1626
+ cookieOptions: {
1627
+ ...config?.cookieOptions,
1628
+ ...(domain ? { domain } : {})
1629
+ }
1630
+ };
1631
+ return SecureCookie.getInstance(mergedConfig);
1632
+ }
1633
+ /**
1634
+ * Crea una instancia de manejador de cookies plano (sin cifrar).
1635
+ * @param defaultOptions Opciones por defecto para las cookies (ej. domain)
1636
+ * @returns Instancia de PlainCookie
1637
+ */
1638
+ function createPlainCookieManager(defaultOptions) {
1639
+ return new PlainCookie(defaultOptions);
1640
+ }
1641
+
1642
+ /**
1643
+ * Instancia predeterminada de almacenamiento local (sin cifrar).
1644
+ */
1645
+ const localStore = createPlainStorage('localStorage');
1646
+ /**
1647
+ * Instancia predeterminada de almacenamiento de sesión (sin cifrar).
1648
+ */
1649
+ const sessionStore = createPlainStorage('sessionStorage');
1650
+ /**
1651
+ * Instancia predeterminada de gestor de cookies (sin cifrar).
1652
+ */
1653
+ const cookies = createPlainCookieManager();
1654
+
1214
1655
  /**
1215
1656
  * @bantis/local-cipher - Core Module
1216
1657
  * Framework-agnostic client-side encryption for browser storage
@@ -1220,8 +1661,9 @@ async function forceMigration(customKeys) {
1220
1661
  */
1221
1662
  // Core classes
1222
1663
  const secureStorage = SecureStorage.getInstance();
1664
+ const secureCookie = SecureCookie.getInstance();
1223
1665
  // Version
1224
- const VERSION = '2.1.0';
1666
+ const VERSION = '2.2.0';
1225
1667
 
1226
1668
  /******************************************************************************
1227
1669
  Copyright (c) Microsoft Corporation.
@@ -1476,7 +1918,7 @@ let SecureStorageService = (() => {
1476
1918
  * @returns Observable con el objeto parseado o null
1477
1919
  */
1478
1920
  getObject(key) {
1479
- return this.getItem(key).pipe(operators.map(value => value ? JSON.parse(value) : null), operators.catchError(() => rxjs.from([null])));
1921
+ return this.getItem(key).pipe(operators.map((value) => value ? JSON.parse(value) : null), operators.catchError(() => rxjs.from([null])));
1480
1922
  }
1481
1923
  /**
1482
1924
  * Registra un listener para un tipo de evento específico
@@ -1500,7 +1942,7 @@ let SecureStorageService = (() => {
1500
1942
  * @returns Observable con eventos del tipo especificado
1501
1943
  */
1502
1944
  onEvent$(eventType) {
1503
- return this.events$.pipe(operators.map(event => event.type === eventType ? event : null), operators.map(event => event));
1945
+ return this.events$.pipe(operators.map((event) => event.type === eventType ? event : null), operators.map((event) => event));
1504
1946
  }
1505
1947
  };
1506
1948
  __setFunctionName(_classThis, "SecureStorageService");
@@ -1514,6 +1956,50 @@ let SecureStorageService = (() => {
1514
1956
  return _classThis;
1515
1957
  })();
1516
1958
 
1959
+ let StorageService = (() => {
1960
+ let _classDecorators = [core.Injectable({
1961
+ providedIn: 'root'
1962
+ })];
1963
+ let _classDescriptor;
1964
+ let _classExtraInitializers = [];
1965
+ let _classThis;
1966
+ _classThis = class {
1967
+ constructor() {
1968
+ /** Acceso directo a localStorage sin cifrar */
1969
+ this.local = localStore;
1970
+ /** Acceso directo a sessionStorage sin cifrar */
1971
+ this.session = sessionStore;
1972
+ /** Acceso directo a cookies sin cifrar */
1973
+ this.cookies = cookies;
1974
+ }
1975
+ /**
1976
+ * Crea una instancia de almacenamiento sincronizado y cifrado
1977
+ * @param engine local o session storage
1978
+ * @param secretKey Llave de cifrado
1979
+ */
1980
+ createEncryptedStorage(engine, secretKey) {
1981
+ return createEncryptedStorage(engine, secretKey);
1982
+ }
1983
+ /**
1984
+ * Crea un gestor de cookies cifradas
1985
+ * @param secretKey Llave de cifrado
1986
+ * @param domain Configuración de subdominios
1987
+ */
1988
+ createEncryptedCookieManager(secretKey, domain) {
1989
+ return createEncryptedCookieManager(secretKey, domain);
1990
+ }
1991
+ };
1992
+ __setFunctionName(_classThis, "StorageService");
1993
+ (() => {
1994
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
1995
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
1996
+ _classThis = _classDescriptor.value;
1997
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
1998
+ __runInitializers(_classThis, _classExtraInitializers);
1999
+ })();
2000
+ return _classThis;
2001
+ })();
2002
+
1517
2003
  exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
1518
2004
  exports.EncryptionHelper = EncryptionHelper;
1519
2005
  exports.EventEmitter = EventEmitter;
@@ -1521,15 +2007,27 @@ exports.KeyRotation = KeyRotation;
1521
2007
  exports.LIBRARY_VERSION = LIBRARY_VERSION;
1522
2008
  exports.Logger = Logger;
1523
2009
  exports.NamespacedStorage = NamespacedStorage;
2010
+ exports.PlainCookie = PlainCookie;
2011
+ exports.PlainStorage = PlainStorage;
1524
2012
  exports.STORAGE_VERSION = STORAGE_VERSION;
2013
+ exports.SecureCookie = SecureCookie;
1525
2014
  exports.SecureStorage = SecureStorage;
1526
2015
  exports.SecureStorageService = SecureStorageService;
2016
+ exports.StorageService = StorageService;
1527
2017
  exports.VERSION = VERSION;
1528
2018
  exports.compress = compress;
2019
+ exports.cookies = cookies;
2020
+ exports.createEncryptedCookieManager = createEncryptedCookieManager;
2021
+ exports.createEncryptedStorage = createEncryptedStorage;
2022
+ exports.createPlainCookieManager = createPlainCookieManager;
2023
+ exports.createPlainStorage = createPlainStorage;
1529
2024
  exports.debugEncryptionState = debugEncryptionState;
1530
2025
  exports.decompress = decompress;
1531
2026
  exports.forceMigration = forceMigration;
1532
2027
  exports.isCompressionSupported = isCompressionSupported;
2028
+ exports.localStore = localStore;
2029
+ exports.secureCookie = secureCookie;
1533
2030
  exports.secureStorage = secureStorage;
2031
+ exports.sessionStore = sessionStore;
1534
2032
  exports.shouldCompress = shouldCompress;
1535
2033
  //# sourceMappingURL=angular.js.map