react-native-nitro-storage 0.3.2 → 0.4.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.
Files changed (36) hide show
  1. package/README.md +141 -30
  2. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +22 -2
  3. package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +3 -0
  4. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +54 -5
  5. package/cpp/bindings/HybridStorage.cpp +167 -22
  6. package/cpp/bindings/HybridStorage.hpp +12 -1
  7. package/cpp/core/NativeStorageAdapter.hpp +3 -0
  8. package/ios/IOSStorageAdapterCpp.hpp +16 -0
  9. package/ios/IOSStorageAdapterCpp.mm +135 -11
  10. package/lib/commonjs/index.js +466 -275
  11. package/lib/commonjs/index.js.map +1 -1
  12. package/lib/commonjs/index.web.js +564 -270
  13. package/lib/commonjs/index.web.js.map +1 -1
  14. package/lib/commonjs/internal.js +25 -0
  15. package/lib/commonjs/internal.js.map +1 -1
  16. package/lib/module/index.js +466 -277
  17. package/lib/module/index.js.map +1 -1
  18. package/lib/module/index.web.js +564 -272
  19. package/lib/module/index.web.js.map +1 -1
  20. package/lib/module/internal.js +24 -0
  21. package/lib/module/internal.js.map +1 -1
  22. package/lib/typescript/Storage.nitro.d.ts +2 -0
  23. package/lib/typescript/Storage.nitro.d.ts.map +1 -1
  24. package/lib/typescript/index.d.ts +38 -1
  25. package/lib/typescript/index.d.ts.map +1 -1
  26. package/lib/typescript/index.web.d.ts +40 -1
  27. package/lib/typescript/index.web.d.ts.map +1 -1
  28. package/lib/typescript/internal.d.ts +1 -0
  29. package/lib/typescript/internal.d.ts.map +1 -1
  30. package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +2 -0
  31. package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +2 -0
  32. package/package.json +1 -1
  33. package/src/Storage.nitro.ts +2 -0
  34. package/src/index.ts +616 -296
  35. package/src/index.web.ts +728 -288
  36. package/src/internal.ts +28 -0
@@ -14,6 +14,7 @@ namespace margelo::nitro::NitroStorage {
14
14
 
15
15
  namespace {
16
16
  constexpr auto kBatchMissingSentinel = "__nitro_storage_batch_missing__::v1";
17
+ constexpr int kDefaultBiometricLevel = 2;
17
18
  } // namespace
18
19
 
19
20
  HybridStorage::HybridStorage()
@@ -74,7 +75,8 @@ void HybridStorage::set(const std::string& key, const std::string& value, double
74
75
  }
75
76
  break;
76
77
  }
77
-
78
+
79
+ onKeySet(static_cast<int>(s), key);
78
80
  notifyListeners(static_cast<int>(s), key, value);
79
81
  }
80
82
 
@@ -143,7 +145,8 @@ void HybridStorage::remove(const std::string& key, double scope) {
143
145
  }
144
146
  break;
145
147
  }
146
-
148
+
149
+ onKeyRemove(static_cast<int>(s), key);
147
150
  notifyListeners(static_cast<int>(s), key, std::nullopt);
148
151
  }
149
152
 
@@ -179,11 +182,56 @@ std::vector<std::string> HybridStorage::getAllKeys(double scope) {
179
182
  return keys;
180
183
  }
181
184
  case Scope::Disk:
182
- ensureAdapter();
183
- return nativeAdapter_->getAllKeysDisk();
184
- case Scope::Secure:
185
- ensureAdapter();
186
- return nativeAdapter_->getAllKeysSecure();
185
+ case Scope::Secure: {
186
+ const int scopeValue = static_cast<int>(s);
187
+ ensureKeyIndexHydrated(scopeValue);
188
+ std::lock_guard<std::mutex> lock(keyIndexMutex_);
189
+ auto indexIt = keyIndex_.find(scopeValue);
190
+ if (indexIt == keyIndex_.end()) {
191
+ return {};
192
+ }
193
+ return toVector(indexIt->second);
194
+ }
195
+ }
196
+ return {};
197
+ }
198
+
199
+ std::vector<std::string> HybridStorage::getKeysByPrefix(const std::string& prefix, double scope) {
200
+ Scope s = toScope(scope);
201
+ if (prefix.empty()) {
202
+ return getAllKeys(scope);
203
+ }
204
+
205
+ switch (s) {
206
+ case Scope::Memory: {
207
+ std::lock_guard<std::mutex> lock(memoryMutex_);
208
+ std::vector<std::string> keys;
209
+ keys.reserve(memoryStore_.size());
210
+ for (const auto& [key, _] : memoryStore_) {
211
+ if (key.rfind(prefix, 0) == 0) {
212
+ keys.push_back(key);
213
+ }
214
+ }
215
+ return keys;
216
+ }
217
+ case Scope::Disk:
218
+ case Scope::Secure: {
219
+ const int scopeValue = static_cast<int>(s);
220
+ ensureKeyIndexHydrated(scopeValue);
221
+ std::lock_guard<std::mutex> lock(keyIndexMutex_);
222
+ std::vector<std::string> keys;
223
+ auto indexIt = keyIndex_.find(scopeValue);
224
+ if (indexIt == keyIndex_.end()) {
225
+ return keys;
226
+ }
227
+ keys.reserve(indexIt->second.size());
228
+ for (const auto& key : indexIt->second) {
229
+ if (key.rfind(prefix, 0) == 0) {
230
+ keys.push_back(key);
231
+ }
232
+ }
233
+ return keys;
234
+ }
187
235
  }
188
236
  return {};
189
237
  }
@@ -197,11 +245,16 @@ double HybridStorage::size(double scope) {
197
245
  return static_cast<double>(memoryStore_.size());
198
246
  }
199
247
  case Scope::Disk:
200
- ensureAdapter();
201
- return static_cast<double>(nativeAdapter_->sizeDisk());
202
- case Scope::Secure:
203
- ensureAdapter();
204
- return static_cast<double>(nativeAdapter_->sizeSecure());
248
+ case Scope::Secure: {
249
+ const int scopeValue = static_cast<int>(s);
250
+ ensureKeyIndexHydrated(scopeValue);
251
+ std::lock_guard<std::mutex> lock(keyIndexMutex_);
252
+ auto indexIt = keyIndex_.find(scopeValue);
253
+ if (indexIt == keyIndex_.end()) {
254
+ return 0.0;
255
+ }
256
+ return static_cast<double>(indexIt->second.size());
257
+ }
205
258
  }
206
259
  return 0.0;
207
260
  }
@@ -261,7 +314,8 @@ void HybridStorage::clear(double scope) {
261
314
  }
262
315
  break;
263
316
  }
264
-
317
+
318
+ onScopeClear(static_cast<int>(s));
265
319
  notifyListeners(static_cast<int>(s), "", std::nullopt);
266
320
  }
267
321
 
@@ -303,6 +357,9 @@ void HybridStorage::setBatch(const std::vector<std::string>& keys, const std::ve
303
357
  }
304
358
 
305
359
  const auto scopeValue = static_cast<int>(s);
360
+ for (const auto& key : keys) {
361
+ onKeySet(scopeValue, key);
362
+ }
306
363
  const auto listeners = copyListenersForScope(scopeValue);
307
364
  for (size_t i = 0; i < keys.size(); ++i) {
308
365
  notifyListeners(listeners, keys[i], values[i]);
@@ -399,6 +456,9 @@ void HybridStorage::removeBatch(const std::vector<std::string>& keys, double sco
399
456
  }
400
457
 
401
458
  const auto scopeValue = static_cast<int>(s);
459
+ for (const auto& key : keys) {
460
+ onKeyRemove(scopeValue, key);
461
+ }
402
462
  const auto listeners = copyListenersForScope(scopeValue);
403
463
  for (const auto& key : keys) {
404
464
  notifyListeners(listeners, key, std::nullopt);
@@ -410,14 +470,7 @@ void HybridStorage::removeByPrefix(const std::string& prefix, double scope) {
410
470
  return;
411
471
  }
412
472
 
413
- const auto keys = getAllKeys(scope);
414
- std::vector<std::string> prefixedKeys;
415
- prefixedKeys.reserve(keys.size());
416
- for (const auto& key : keys) {
417
- if (key.rfind(prefix, 0) == 0) {
418
- prefixedKeys.push_back(key);
419
- }
420
- }
473
+ const auto prefixedKeys = getKeysByPrefix(prefix, scope);
421
474
 
422
475
  if (prefixedKeys.empty()) {
423
476
  return;
@@ -446,9 +499,18 @@ void HybridStorage::setKeychainAccessGroup(const std::string& group) {
446
499
  // --- Biometric ---
447
500
 
448
501
  void HybridStorage::setSecureBiometric(const std::string& key, const std::string& value) {
502
+ setSecureBiometricWithLevel(key, value, kDefaultBiometricLevel);
503
+ }
504
+
505
+ void HybridStorage::setSecureBiometricWithLevel(const std::string& key, const std::string& value, double level) {
449
506
  ensureAdapter();
450
507
  try {
451
- nativeAdapter_->setSecureBiometric(key, value);
508
+ nativeAdapter_->setSecureBiometricWithLevel(
509
+ key,
510
+ value,
511
+ static_cast<int>(level)
512
+ );
513
+ onKeySet(static_cast<int>(Scope::Secure), key);
452
514
  notifyListeners(static_cast<int>(Scope::Secure), key, value);
453
515
  } catch (const std::exception& e) {
454
516
  throw std::runtime_error(std::string("NitroStorage: Biometric set failed: ") + e.what());
@@ -468,6 +530,7 @@ void HybridStorage::deleteSecureBiometric(const std::string& key) {
468
530
  ensureAdapter();
469
531
  try {
470
532
  nativeAdapter_->deleteSecureBiometric(key);
533
+ onKeyRemove(static_cast<int>(Scope::Secure), key);
471
534
  notifyListeners(static_cast<int>(Scope::Secure), key, std::nullopt);
472
535
  } catch (const std::exception& e) {
473
536
  throw std::runtime_error(std::string("NitroStorage: Biometric delete failed: ") + e.what());
@@ -483,6 +546,7 @@ void HybridStorage::clearSecureBiometric() {
483
546
  ensureAdapter();
484
547
  try {
485
548
  nativeAdapter_->clearSecureBiometric();
549
+ onScopeClear(static_cast<int>(Scope::Secure));
486
550
  notifyListeners(static_cast<int>(Scope::Secure), "", std::nullopt);
487
551
  } catch (const std::exception& e) {
488
552
  throw std::runtime_error(std::string("NitroStorage: Biometric clear failed: ") + e.what());
@@ -527,6 +591,87 @@ void HybridStorage::notifyListeners(
527
591
  notifyListeners(listeners, key, value);
528
592
  }
529
593
 
594
+ std::vector<std::string> HybridStorage::toVector(const std::unordered_set<std::string>& keys) {
595
+ std::vector<std::string> values;
596
+ values.reserve(keys.size());
597
+ for (const auto& key : keys) {
598
+ values.push_back(key);
599
+ }
600
+ return values;
601
+ }
602
+
603
+ void HybridStorage::ensureKeyIndexHydrated(int scope) {
604
+ if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
605
+ return;
606
+ }
607
+
608
+ {
609
+ std::lock_guard<std::mutex> lock(keyIndexMutex_);
610
+ auto hydratedIt = keyIndexHydrated_.find(scope);
611
+ if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
612
+ return;
613
+ }
614
+ }
615
+
616
+ ensureAdapter();
617
+ std::vector<std::string> keys;
618
+ try {
619
+ if (scope == static_cast<int>(Scope::Disk)) {
620
+ keys = nativeAdapter_->getAllKeysDisk();
621
+ } else {
622
+ keys = nativeAdapter_->getAllKeysSecure();
623
+ }
624
+ } catch (const std::exception& e) {
625
+ throw std::runtime_error(std::string("NitroStorage: Key index hydration failed: ") + e.what());
626
+ } catch (...) {
627
+ throw std::runtime_error("NitroStorage: Key index hydration failed");
628
+ }
629
+
630
+ std::lock_guard<std::mutex> lock(keyIndexMutex_);
631
+ auto& index = keyIndex_[scope];
632
+ index.clear();
633
+ for (const auto& key : keys) {
634
+ index.insert(key);
635
+ }
636
+ keyIndexHydrated_[scope] = true;
637
+ }
638
+
639
+ void HybridStorage::onKeySet(int scope, const std::string& key) {
640
+ if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
641
+ return;
642
+ }
643
+
644
+ std::lock_guard<std::mutex> lock(keyIndexMutex_);
645
+ auto hydratedIt = keyIndexHydrated_.find(scope);
646
+ if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
647
+ keyIndex_[scope].insert(key);
648
+ }
649
+ }
650
+
651
+ void HybridStorage::onKeyRemove(int scope, const std::string& key) {
652
+ if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
653
+ return;
654
+ }
655
+
656
+ std::lock_guard<std::mutex> lock(keyIndexMutex_);
657
+ auto hydratedIt = keyIndexHydrated_.find(scope);
658
+ if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
659
+ keyIndex_[scope].erase(key);
660
+ }
661
+ }
662
+
663
+ void HybridStorage::onScopeClear(int scope) {
664
+ if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
665
+ return;
666
+ }
667
+
668
+ std::lock_guard<std::mutex> lock(keyIndexMutex_);
669
+ auto hydratedIt = keyIndexHydrated_.find(scope);
670
+ if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
671
+ keyIndex_[scope].clear();
672
+ }
673
+ }
674
+
530
675
  void HybridStorage::ensureAdapter() const {
531
676
  if (!nativeAdapter_) {
532
677
  throw std::runtime_error("NitroStorage: Native adapter not initialized");
@@ -8,6 +8,7 @@
8
8
  #include <functional>
9
9
  #include <memory>
10
10
  #include <vector>
11
+ #include <unordered_set>
11
12
 
12
13
  namespace margelo::nitro::NitroStorage {
13
14
 
@@ -31,6 +32,7 @@ public:
31
32
  void clear(double scope) override;
32
33
  bool has(const std::string& key, double scope) override;
33
34
  std::vector<std::string> getAllKeys(double scope) override;
35
+ std::vector<std::string> getKeysByPrefix(const std::string& prefix, double scope) override;
34
36
  double size(double scope) override;
35
37
  void setBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values, double scope) override;
36
38
  std::vector<std::string> getBatch(const std::vector<std::string>& keys, double scope) override;
@@ -44,6 +46,7 @@ public:
44
46
  void setSecureWritesAsync(bool enabled) override;
45
47
  void setKeychainAccessGroup(const std::string& group) override;
46
48
  void setSecureBiometric(const std::string& key, const std::string& value) override;
49
+ void setSecureBiometricWithLevel(const std::string& key, const std::string& value, double level) override;
47
50
  std::optional<std::string> getSecureBiometric(const std::string& key) override;
48
51
  void deleteSecureBiometric(const std::string& key) override;
49
52
  bool hasSecureBiometric(const std::string& key) override;
@@ -65,10 +68,13 @@ private:
65
68
  std::mutex memoryMutex_;
66
69
 
67
70
  std::shared_ptr<::NitroStorage::NativeStorageAdapter> nativeAdapter_;
68
-
71
+
69
72
  HybridStorageMap<int, std::vector<Listener>> listeners_;
70
73
  std::mutex listenersMutex_;
71
74
  size_t nextListenerId_ = 0;
75
+ HybridStorageMap<int, std::unordered_set<std::string>> keyIndex_;
76
+ HybridStorageMap<int, bool> keyIndexHydrated_;
77
+ std::mutex keyIndexMutex_;
72
78
 
73
79
  std::vector<Listener> copyListenersForScope(int scope);
74
80
  void notifyListeners(
@@ -77,6 +83,11 @@ private:
77
83
  const std::optional<std::string>& value
78
84
  );
79
85
  void notifyListeners(int scope, const std::string& key, const std::optional<std::string>& value);
86
+ std::vector<std::string> toVector(const std::unordered_set<std::string>& keys);
87
+ void ensureKeyIndexHydrated(int scope);
88
+ void onKeySet(int scope, const std::string& key);
89
+ void onKeyRemove(int scope, const std::string& key);
90
+ void onScopeClear(int scope);
80
91
  void ensureAdapter() const;
81
92
  Scope toScope(double scopeValue);
82
93
  };
@@ -15,6 +15,7 @@ public:
15
15
  virtual void deleteDisk(const std::string& key) = 0;
16
16
  virtual bool hasDisk(const std::string& key) = 0;
17
17
  virtual std::vector<std::string> getAllKeysDisk() = 0;
18
+ virtual std::vector<std::string> getKeysByPrefixDisk(const std::string& prefix) = 0;
18
19
  virtual size_t sizeDisk() = 0;
19
20
  virtual void setDiskBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) = 0;
20
21
  virtual std::vector<std::optional<std::string>> getDiskBatch(const std::vector<std::string>& keys) = 0;
@@ -25,6 +26,7 @@ public:
25
26
  virtual void deleteSecure(const std::string& key) = 0;
26
27
  virtual bool hasSecure(const std::string& key) = 0;
27
28
  virtual std::vector<std::string> getAllKeysSecure() = 0;
29
+ virtual std::vector<std::string> getKeysByPrefixSecure(const std::string& prefix) = 0;
28
30
  virtual size_t sizeSecure() = 0;
29
31
  virtual void setSecureBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) = 0;
30
32
  virtual std::vector<std::optional<std::string>> getSecureBatch(const std::vector<std::string>& keys) = 0;
@@ -38,6 +40,7 @@ public:
38
40
  virtual void setKeychainAccessGroup(const std::string& group) = 0;
39
41
 
40
42
  virtual void setSecureBiometric(const std::string& key, const std::string& value) = 0;
43
+ virtual void setSecureBiometricWithLevel(const std::string& key, const std::string& value, int level) = 0;
41
44
  virtual std::optional<std::string> getSecureBiometric(const std::string& key) = 0;
42
45
  virtual void deleteSecureBiometric(const std::string& key) = 0;
43
46
  virtual bool hasSecureBiometric(const std::string& key) = 0;
@@ -1,6 +1,8 @@
1
1
  #pragma once
2
2
 
3
3
  #include "../core/NativeStorageAdapter.hpp"
4
+ #include <mutex>
5
+ #include <unordered_set>
4
6
 
5
7
  namespace NitroStorage {
6
8
 
@@ -14,6 +16,7 @@ public:
14
16
  void deleteDisk(const std::string& key) override;
15
17
  bool hasDisk(const std::string& key) override;
16
18
  std::vector<std::string> getAllKeysDisk() override;
19
+ std::vector<std::string> getKeysByPrefixDisk(const std::string& prefix) override;
17
20
  size_t sizeDisk() override;
18
21
  void setDiskBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) override;
19
22
  std::vector<std::optional<std::string>> getDiskBatch(const std::vector<std::string>& keys) override;
@@ -24,6 +27,7 @@ public:
24
27
  void deleteSecure(const std::string& key) override;
25
28
  bool hasSecure(const std::string& key) override;
26
29
  std::vector<std::string> getAllKeysSecure() override;
30
+ std::vector<std::string> getKeysByPrefixSecure(const std::string& prefix) override;
27
31
  size_t sizeSecure() override;
28
32
  void setSecureBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) override;
29
33
  std::vector<std::optional<std::string>> getSecureBatch(const std::vector<std::string>& keys) override;
@@ -37,6 +41,7 @@ public:
37
41
  void setKeychainAccessGroup(const std::string& group) override;
38
42
 
39
43
  void setSecureBiometric(const std::string& key, const std::string& value) override;
44
+ void setSecureBiometricWithLevel(const std::string& key, const std::string& value, int level) override;
40
45
  std::optional<std::string> getSecureBiometric(const std::string& key) override;
41
46
  void deleteSecureBiometric(const std::string& key) override;
42
47
  bool hasSecureBiometric(const std::string& key) override;
@@ -45,6 +50,17 @@ public:
45
50
  private:
46
51
  int accessControlLevel_ = 0;
47
52
  std::string keychainAccessGroup_;
53
+ mutable std::mutex secureKeysMutex_;
54
+ std::unordered_set<std::string> secureKeysCache_;
55
+ std::unordered_set<std::string> biometricKeysCache_;
56
+ bool secureKeyCacheHydrated_ = false;
57
+
58
+ void ensureSecureKeyCacheHydrated();
59
+ void markSecureKeySet(const std::string& key);
60
+ void markSecureKeyRemoved(const std::string& key);
61
+ void markBiometricKeySet(const std::string& key);
62
+ void markBiometricKeyRemoved(const std::string& key);
63
+ void clearSecureKeyCache();
48
64
  };
49
65
 
50
66
  } // namespace NitroStorage
@@ -79,6 +79,18 @@ std::vector<std::string> IOSStorageAdapterCpp::getAllKeysDisk() {
79
79
  return keys;
80
80
  }
81
81
 
82
+ std::vector<std::string> IOSStorageAdapterCpp::getKeysByPrefixDisk(const std::string& prefix) {
83
+ const auto keys = getAllKeysDisk();
84
+ std::vector<std::string> filtered;
85
+ filtered.reserve(keys.size());
86
+ for (const auto& key : keys) {
87
+ if (key.rfind(prefix, 0) == 0) {
88
+ filtered.push_back(key);
89
+ }
90
+ }
91
+ return filtered;
92
+ }
93
+
82
94
  size_t IOSStorageAdapterCpp::sizeDisk() {
83
95
  return [NitroDiskDefaults() dictionaryRepresentation].count;
84
96
  }
@@ -177,11 +189,27 @@ void IOSStorageAdapterCpp::setSecure(const std::string& key, const std::string&
177
189
 
178
190
  OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)updateAttributes);
179
191
 
192
+ if (status == errSecSuccess) {
193
+ markSecureKeySet(key);
194
+ return;
195
+ }
196
+
180
197
  if (status == errSecItemNotFound) {
181
198
  query[(__bridge id)kSecValueData] = data;
182
199
  query[(__bridge id)kSecAttrAccessible] = (__bridge id)accessControlAttr(accessControlLevel_);
183
- SecItemAdd((__bridge CFDictionaryRef)query, NULL);
200
+ const OSStatus addStatus = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
201
+ if (addStatus != errSecSuccess) {
202
+ throw std::runtime_error(
203
+ "NitroStorage: Secure set failed with status " + std::to_string(addStatus)
204
+ );
205
+ }
206
+ markSecureKeySet(key);
207
+ return;
184
208
  }
209
+
210
+ throw std::runtime_error(
211
+ "NitroStorage: Secure set failed with status " + std::to_string(status)
212
+ );
185
213
  }
186
214
 
187
215
  std::optional<std::string> IOSStorageAdapterCpp::getSecure(const std::string& key) {
@@ -207,6 +235,8 @@ void IOSStorageAdapterCpp::deleteSecure(const std::string& key) {
207
235
  SecItemDelete((__bridge CFDictionaryRef)secureQuery);
208
236
  NSMutableDictionary* biometricQuery = baseKeychainQuery(nsKey, kBiometricKeychainService, group);
209
237
  SecItemDelete((__bridge CFDictionaryRef)biometricQuery);
238
+ markSecureKeyRemoved(key);
239
+ markBiometricKeyRemoved(key);
210
240
  }
211
241
 
212
242
  bool IOSStorageAdapterCpp::hasSecure(const std::string& key) {
@@ -221,20 +251,36 @@ bool IOSStorageAdapterCpp::hasSecure(const std::string& key) {
221
251
  }
222
252
 
223
253
  std::vector<std::string> IOSStorageAdapterCpp::getAllKeysSecure() {
224
- NSString* group = keychainAccessGroup_.empty() ? nil : [NSString stringWithUTF8String:keychainAccessGroup_.c_str()];
225
- std::vector<std::string> keys = keychainAccountsForService(kKeychainService, group);
226
- std::unordered_set<std::string> seen(keys.begin(), keys.end());
227
- const std::vector<std::string> biometricKeys = keychainAccountsForService(kBiometricKeychainService, group);
228
- for (const auto& key : biometricKeys) {
229
- if (seen.insert(key).second) {
230
- keys.push_back(key);
231
- }
254
+ ensureSecureKeyCacheHydrated();
255
+ std::lock_guard<std::mutex> lock(secureKeysMutex_);
256
+ std::unordered_set<std::string> combined = secureKeysCache_;
257
+ combined.insert(biometricKeysCache_.begin(), biometricKeysCache_.end());
258
+ std::vector<std::string> keys;
259
+ keys.reserve(combined.size());
260
+ for (const auto& key : combined) {
261
+ keys.push_back(key);
232
262
  }
233
263
  return keys;
234
264
  }
235
265
 
266
+ std::vector<std::string> IOSStorageAdapterCpp::getKeysByPrefixSecure(const std::string& prefix) {
267
+ const auto keys = getAllKeysSecure();
268
+ std::vector<std::string> filtered;
269
+ filtered.reserve(keys.size());
270
+ for (const auto& key : keys) {
271
+ if (key.rfind(prefix, 0) == 0) {
272
+ filtered.push_back(key);
273
+ }
274
+ }
275
+ return filtered;
276
+ }
277
+
236
278
  size_t IOSStorageAdapterCpp::sizeSecure() {
237
- return getAllKeysSecure().size();
279
+ ensureSecureKeyCacheHydrated();
280
+ std::lock_guard<std::mutex> lock(secureKeysMutex_);
281
+ std::unordered_set<std::string> combined = secureKeysCache_;
282
+ combined.insert(biometricKeysCache_.begin(), biometricKeysCache_.end());
283
+ return combined.size();
238
284
  }
239
285
 
240
286
  void IOSStorageAdapterCpp::setSecureBatch(
@@ -282,6 +328,7 @@ void IOSStorageAdapterCpp::clearSecure() {
282
328
  biometricQuery[(__bridge id)kSecAttrAccessGroup] = group;
283
329
  }
284
330
  SecItemDelete((__bridge CFDictionaryRef)biometricQuery);
331
+ clearSecureKeyCache();
285
332
  }
286
333
 
287
334
  // --- Configuration ---
@@ -296,11 +343,21 @@ void IOSStorageAdapterCpp::setSecureWritesAsync(bool /*enabled*/) {
296
343
 
297
344
  void IOSStorageAdapterCpp::setKeychainAccessGroup(const std::string& group) {
298
345
  keychainAccessGroup_ = group;
346
+ clearSecureKeyCache();
299
347
  }
300
348
 
301
349
  // --- Biometric (separate Keychain service with biometric ACL) ---
302
350
 
303
351
  void IOSStorageAdapterCpp::setSecureBiometric(const std::string& key, const std::string& value) {
352
+ setSecureBiometricWithLevel(key, value, 2);
353
+ }
354
+
355
+ void IOSStorageAdapterCpp::setSecureBiometricWithLevel(const std::string& key, const std::string& value, int level) {
356
+ if (level == 0) {
357
+ setSecure(key, value);
358
+ return;
359
+ }
360
+
304
361
  NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
305
362
  NSData* data = [[NSString stringWithUTF8String:value.c_str()] dataUsingEncoding:NSUTF8StringEncoding];
306
363
  NSString* group = keychainAccessGroup_.empty() ? nil : [NSString stringWithUTF8String:keychainAccessGroup_.c_str()];
@@ -310,10 +367,14 @@ void IOSStorageAdapterCpp::setSecureBiometric(const std::string& key, const std:
310
367
  SecItemDelete((__bridge CFDictionaryRef)deleteQuery);
311
368
 
312
369
  CFErrorRef error = NULL;
370
+ SecAccessControlCreateFlags flags = kSecAccessControlBiometryCurrentSet;
371
+ if (level == 1) {
372
+ flags = kSecAccessControlUserPresence;
373
+ }
313
374
  SecAccessControlRef access = SecAccessControlCreateWithFlags(
314
375
  kCFAllocatorDefault,
315
376
  kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
316
- kSecAccessControlBiometryCurrentSet,
377
+ flags,
317
378
  &error
318
379
  );
319
380
 
@@ -330,6 +391,7 @@ void IOSStorageAdapterCpp::setSecureBiometric(const std::string& key, const std:
330
391
  if (status != errSecSuccess) {
331
392
  throw std::runtime_error("NitroStorage: Biometric set failed with status " + std::to_string(status));
332
393
  }
394
+ markBiometricKeySet(key);
333
395
  }
334
396
 
335
397
  std::optional<std::string> IOSStorageAdapterCpp::getSecureBiometric(const std::string& key) {
@@ -357,6 +419,7 @@ void IOSStorageAdapterCpp::deleteSecureBiometric(const std::string& key) {
357
419
  NSString* group = keychainAccessGroup_.empty() ? nil : [NSString stringWithUTF8String:keychainAccessGroup_.c_str()];
358
420
  NSMutableDictionary* query = baseKeychainQuery(nsKey, kBiometricKeychainService, group);
359
421
  SecItemDelete((__bridge CFDictionaryRef)query);
422
+ markBiometricKeyRemoved(key);
360
423
  }
361
424
 
362
425
  bool IOSStorageAdapterCpp::hasSecureBiometric(const std::string& key) {
@@ -376,6 +439,67 @@ void IOSStorageAdapterCpp::clearSecureBiometric() {
376
439
  query[(__bridge id)kSecAttrAccessGroup] = group;
377
440
  }
378
441
  SecItemDelete((__bridge CFDictionaryRef)query);
442
+ std::lock_guard<std::mutex> lock(secureKeysMutex_);
443
+ biometricKeysCache_.clear();
444
+ }
445
+
446
+ void IOSStorageAdapterCpp::ensureSecureKeyCacheHydrated() {
447
+ {
448
+ std::lock_guard<std::mutex> lock(secureKeysMutex_);
449
+ if (secureKeyCacheHydrated_) {
450
+ return;
451
+ }
452
+ }
453
+
454
+ NSString* group = keychainAccessGroup_.empty() ? nil : [NSString stringWithUTF8String:keychainAccessGroup_.c_str()];
455
+ const std::vector<std::string> secureKeys = keychainAccountsForService(kKeychainService, group);
456
+ const std::vector<std::string> biometricKeys = keychainAccountsForService(kBiometricKeychainService, group);
457
+
458
+ std::lock_guard<std::mutex> lock(secureKeysMutex_);
459
+ secureKeysCache_.clear();
460
+ biometricKeysCache_.clear();
461
+ secureKeysCache_.insert(secureKeys.begin(), secureKeys.end());
462
+ biometricKeysCache_.insert(biometricKeys.begin(), biometricKeys.end());
463
+ secureKeyCacheHydrated_ = true;
464
+ }
465
+
466
+ void IOSStorageAdapterCpp::markSecureKeySet(const std::string& key) {
467
+ std::lock_guard<std::mutex> lock(secureKeysMutex_);
468
+ if (!secureKeyCacheHydrated_) {
469
+ return;
470
+ }
471
+ secureKeysCache_.insert(key);
472
+ }
473
+
474
+ void IOSStorageAdapterCpp::markSecureKeyRemoved(const std::string& key) {
475
+ std::lock_guard<std::mutex> lock(secureKeysMutex_);
476
+ if (!secureKeyCacheHydrated_) {
477
+ return;
478
+ }
479
+ secureKeysCache_.erase(key);
480
+ }
481
+
482
+ void IOSStorageAdapterCpp::markBiometricKeySet(const std::string& key) {
483
+ std::lock_guard<std::mutex> lock(secureKeysMutex_);
484
+ if (!secureKeyCacheHydrated_) {
485
+ return;
486
+ }
487
+ biometricKeysCache_.insert(key);
488
+ }
489
+
490
+ void IOSStorageAdapterCpp::markBiometricKeyRemoved(const std::string& key) {
491
+ std::lock_guard<std::mutex> lock(secureKeysMutex_);
492
+ if (!secureKeyCacheHydrated_) {
493
+ return;
494
+ }
495
+ biometricKeysCache_.erase(key);
496
+ }
497
+
498
+ void IOSStorageAdapterCpp::clearSecureKeyCache() {
499
+ std::lock_guard<std::mutex> lock(secureKeysMutex_);
500
+ secureKeysCache_.clear();
501
+ biometricKeysCache_.clear();
502
+ secureKeyCacheHydrated_ = false;
379
503
  }
380
504
 
381
505
  } // namespace NitroStorage