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
package/README.md CHANGED
@@ -14,6 +14,10 @@ Synchronous Memory, Disk, and Secure storage in one unified API — powered by [
14
14
  - **Biometric storage** — hardware-backed biometric protection on iOS & Android
15
15
  - **Auth storage factory** — `createSecureAuthStorage` for multi-token auth flows
16
16
  - **Batch operations** — atomic multi-key get/set/remove via native batch APIs
17
+ - **Prefix queries** — fast key/value scans with `storage.getKeysByPrefix` and `storage.getByPrefix`
18
+ - **Versioned writes** — optimistic concurrency with `item.getWithVersion()` and `item.setIfVersion(...)`
19
+ - **Performance metrics** — observe operation timings and aggregate snapshots
20
+ - **Web secure backend override** — plug custom secure storage backend on web
17
21
  - **Transactions** — grouped writes with automatic rollback on error
18
22
  - **Migrations** — versioned data migrations with `registerMigration` / `migrateToLatest`
19
23
  - **MMKV migration** — drop-in `migrateFromMMKV` for painless migration from MMKV
@@ -31,6 +35,10 @@ Every feature in this package is documented with at least one runnable example i
31
35
  - Validation + recovery — see Feature Flags with Validation
32
36
  - Biometric + access control — see Biometric-protected Secrets
33
37
  - Global storage utilities (`clear*`, `has`, `getAll*`, `size`, secure write settings) — see Global utility examples and Storage Snapshots and Cleanup
38
+ - Prefix utilities (`getKeysByPrefix`, `getByPrefix`) — see Prefix Queries and Namespace Inspection
39
+ - Versioned item API (`getWithVersion`, `setIfVersion`) — see Optimistic Versioned Writes
40
+ - Metrics API (`setMetricsObserver`, `getMetricsSnapshot`, `resetMetrics`) — see Storage Metrics Instrumentation
41
+ - Web secure backend override (`setWebSecureStorageBackend`, `getWebSecureStorageBackend`) — see Custom Web Secure Backend
34
42
  - Batch APIs (`getBatch`, `setBatch`, `removeBatch`) — see Batch Operations and Bulk Bootstrap with Batch APIs
35
43
  - Transactions — see Transactions and Atomic Balance Transfer
36
44
  - Migrations (`registerMigration`, `migrateToLatest`) — see Migrations
@@ -185,21 +193,24 @@ function createStorageItem<T = undefined>(
185
193
  | `coalesceSecureWrites` | `boolean` | `false` | Batch same-tick Secure writes per key |
186
194
  | `namespace` | `string` | — | Prefix key as `namespace:key` for isolation |
187
195
  | `biometric` | `boolean` | `false` | Require biometric auth (Secure scope only) |
196
+ | `biometricLevel` | `BiometricLevel` | `None` | Biometric policy (`BiometryOrPasscode` / `BiometryOnly`) |
188
197
  | `accessControl` | `AccessControl` | — | Keychain access control level (native only) |
189
198
 
190
199
  **Returned `StorageItem<T>`:**
191
200
 
192
- | Method / Property | Type | Description |
193
- | ----------------- | ---------------------------------------- | -------------------------------------------------- |
194
- | `get()` | `() => T` | Read current value (synchronous) |
195
- | `set(value)` | `(value: T \| ((prev: T) => T)) => void` | Write a value or updater function |
196
- | `delete()` | `() => void` | Remove the stored value (resets to `defaultValue`) |
197
- | `has()` | `() => boolean` | Check if a value exists in storage |
198
- | `subscribe(cb)` | `(cb: () => void) => () => void` | Listen for changes, returns unsubscribe |
199
- | `serialize` | `(v: T) => string` | The item's serializer |
200
- | `deserialize` | `(v: string) => T` | The item's deserializer |
201
- | `scope` | `StorageScope` | The item's scope |
202
- | `key` | `string` | The resolved key (includes namespace prefix) |
201
+ | Method / Property | Type | Description |
202
+ | ------------------- | -------------------------------------------------------------------- | ------------------------------------------------------ |
203
+ | `get()` | `() => T` | Read current value (synchronous) |
204
+ | `getWithVersion()` | `() => { value: T; version: StorageVersion }` | Read value plus current storage version token |
205
+ | `set(value)` | `(value: T \| ((prev: T) => T)) => void` | Write a value or updater function |
206
+ | `setIfVersion(...)` | `(version: StorageVersion, value: T \| ((prev: T) => T)) => boolean` | Write only if version matches (optimistic concurrency) |
207
+ | `delete()` | `() => void` | Remove the stored value (resets to `defaultValue`) |
208
+ | `has()` | `() => boolean` | Check if a value exists in storage |
209
+ | `subscribe(cb)` | `(cb: () => void) => () => void` | Listen for changes, returns unsubscribe |
210
+ | `serialize` | `(v: T) => string` | The item's serializer |
211
+ | `deserialize` | `(v: string) => T` | The item's deserializer |
212
+ | `scope` | `StorageScope` | The item's scope |
213
+ | `key` | `string` | The resolved key (includes namespace prefix) |
203
214
 
204
215
  **Non-React subscription example:**
205
216
 
@@ -249,30 +260,41 @@ setToken("new-token");
249
260
  import { storage, StorageScope } from "react-native-nitro-storage";
250
261
  ```
251
262
 
252
- | Method | Description |
253
- | --------------------------------------- | ---------------------------------------------------------------------------- |
254
- | `storage.clear(scope)` | Clear all keys in a scope (`Secure` also clears biometric entries) |
255
- | `storage.clearAll()` | Clear Memory + Disk + Secure |
256
- | `storage.clearNamespace(ns, scope)` | Remove only keys matching a namespace |
257
- | `storage.clearBiometric()` | Remove all biometric-prefixed keys |
258
- | `storage.has(key, scope)` | Check if a key exists |
259
- | `storage.getAllKeys(scope)` | Get all key names |
260
- | `storage.getAll(scope)` | Get all key-value pairs as `Record<string, string>` |
261
- | `storage.size(scope)` | Number of stored keys |
262
- | `storage.setAccessControl(level)` | Set default secure access control for subsequent secure writes (native only) |
263
- | `storage.setSecureWritesAsync(enabled)` | Toggle async secure writes on Android (`false` by default) |
264
- | `storage.flushSecureWrites()` | Force flush of queued secure writes when coalescing is enabled |
265
- | `storage.setKeychainAccessGroup(group)` | Set keychain access group for app sharing (native only) |
263
+ | Method | Description |
264
+ | ---------------------------------------- | ---------------------------------------------------------------------------- |
265
+ | `storage.clear(scope)` | Clear all keys in a scope (`Secure` also clears biometric entries) |
266
+ | `storage.clearAll()` | Clear Memory + Disk + Secure |
267
+ | `storage.clearNamespace(ns, scope)` | Remove only keys matching a namespace |
268
+ | `storage.clearBiometric()` | Remove all biometric-prefixed keys |
269
+ | `storage.has(key, scope)` | Check if a key exists |
270
+ | `storage.getAllKeys(scope)` | Get all key names |
271
+ | `storage.getKeysByPrefix(prefix, scope)` | Get keys that start with a prefix |
272
+ | `storage.getByPrefix(prefix, scope)` | Get raw key-value pairs for keys matching a prefix |
273
+ | `storage.getAll(scope)` | Get all key-value pairs as `Record<string, string>` |
274
+ | `storage.size(scope)` | Number of stored keys |
275
+ | `storage.setAccessControl(level)` | Set default secure access control for subsequent secure writes (native only) |
276
+ | `storage.setSecureWritesAsync(enabled)` | Toggle async secure writes on Android (`false` by default) |
277
+ | `storage.flushSecureWrites()` | Force flush of queued secure writes when coalescing is enabled |
278
+ | `storage.setKeychainAccessGroup(group)` | Set keychain access group for app sharing (native only) |
279
+ | `storage.setMetricsObserver(observer?)` | Subscribe to per-operation timing events |
280
+ | `storage.getMetricsSnapshot()` | Get aggregate counters/latency stats keyed by operation |
281
+ | `storage.resetMetrics()` | Reset in-memory metrics counters |
266
282
 
267
283
  > `storage.getAll(StorageScope.Secure)` returns regular secure entries. Biometric-protected values are not included in this snapshot API.
268
284
 
269
285
  #### Global utility examples
270
286
 
271
287
  ```ts
272
- import { AccessControl, storage, StorageScope } from "react-native-nitro-storage";
288
+ import {
289
+ AccessControl,
290
+ storage,
291
+ StorageScope,
292
+ } from "react-native-nitro-storage";
273
293
 
274
294
  storage.has("session", StorageScope.Disk);
275
295
  storage.getAllKeys(StorageScope.Disk);
296
+ storage.getKeysByPrefix("user-42:", StorageScope.Disk);
297
+ storage.getByPrefix("user-42:", StorageScope.Disk);
276
298
  storage.getAll(StorageScope.Disk);
277
299
  storage.size(StorageScope.Disk);
278
300
 
@@ -303,6 +325,28 @@ storage.setSecureWritesAsync(true);
303
325
  storage.flushSecureWrites(); // deterministic durability boundary
304
326
  ```
305
327
 
328
+ #### Custom web secure backend
329
+
330
+ By default, web Secure scope uses `localStorage` with `__secure_` key prefixing. You can replace it with a custom backend (for example encrypted IndexedDB adapter).
331
+
332
+ ```ts
333
+ import {
334
+ getWebSecureStorageBackend,
335
+ setWebSecureStorageBackend,
336
+ } from "react-native-nitro-storage";
337
+
338
+ setWebSecureStorageBackend({
339
+ getItem: (key) => encryptedStore.get(key) ?? null,
340
+ setItem: (key, value) => encryptedStore.set(key, value),
341
+ removeItem: (key) => encryptedStore.delete(key),
342
+ clear: () => encryptedStore.clear(),
343
+ getAllKeys: () => encryptedStore.keys(),
344
+ });
345
+
346
+ const backend = getWebSecureStorageBackend();
347
+ console.log("custom backend active:", backend !== undefined);
348
+ ```
349
+
306
350
  ---
307
351
 
308
352
  ### `createSecureAuthStorage<K>(config, options?)`
@@ -318,7 +362,7 @@ function createSecureAuthStorage<K extends string>(
318
362
 
319
363
  - Default namespace: `"auth"`
320
364
  - Each key is a separate `StorageItem<string>` with `StorageScope.Secure`
321
- - Supports per-key TTL, biometric, and access control
365
+ - Supports per-key TTL, biometric level policy, and access control
322
366
 
323
367
  ---
324
368
 
@@ -550,13 +594,18 @@ const flagsItem = createStorageItem<FeatureFlags>({
550
594
  ### Biometric-protected Secrets
551
595
 
552
596
  ```ts
553
- import { AccessControl, createStorageItem, StorageScope } from "react-native-nitro-storage";
597
+ import {
598
+ AccessControl,
599
+ BiometricLevel,
600
+ createStorageItem,
601
+ StorageScope,
602
+ } from "react-native-nitro-storage";
554
603
 
555
604
  const paymentPin = createStorageItem<string>({
556
605
  key: "payment-pin",
557
606
  scope: StorageScope.Secure,
558
607
  defaultValue: "",
559
- biometric: true,
608
+ biometricLevel: BiometricLevel.BiometryOnly,
560
609
  accessControl: AccessControl.WhenPasscodeSetThisDeviceOnly,
561
610
  });
562
611
 
@@ -686,7 +735,11 @@ const compactItem = createStorageItem<{ id: number; active: boolean }>({
686
735
  ### Coalesced Secure Writes with Deterministic Flush
687
736
 
688
737
  ```ts
689
- import { createStorageItem, storage, StorageScope } from "react-native-nitro-storage";
738
+ import {
739
+ createStorageItem,
740
+ storage,
741
+ StorageScope,
742
+ } from "react-native-nitro-storage";
690
743
 
691
744
  const sessionToken = createStorageItem<string>({
692
745
  key: "session-token",
@@ -718,6 +771,58 @@ if (storage.has("legacy-flag", StorageScope.Disk)) {
718
771
  storage.clearBiometric();
719
772
  ```
720
773
 
774
+ ### Prefix Queries and Namespace Inspection
775
+
776
+ ```ts
777
+ import { storage, StorageScope } from "react-native-nitro-storage";
778
+
779
+ const userKeys = storage.getKeysByPrefix("user-42:", StorageScope.Disk);
780
+ const userRawEntries = storage.getByPrefix("user-42:", StorageScope.Disk);
781
+
782
+ console.log(userKeys);
783
+ console.log(userRawEntries);
784
+ ```
785
+
786
+ ### Optimistic Versioned Writes
787
+
788
+ ```ts
789
+ import { createStorageItem, StorageScope } from "react-native-nitro-storage";
790
+
791
+ const profileItem = createStorageItem({
792
+ key: "profile",
793
+ scope: StorageScope.Disk,
794
+ defaultValue: { name: "Guest" },
795
+ });
796
+
797
+ const snapshot = profileItem.getWithVersion();
798
+ const didWrite = profileItem.setIfVersion(snapshot.version, {
799
+ ...snapshot.value,
800
+ name: "Ada",
801
+ });
802
+
803
+ if (!didWrite) {
804
+ // value changed since snapshot; reload and retry
805
+ }
806
+ ```
807
+
808
+ ### Storage Metrics Instrumentation
809
+
810
+ ```ts
811
+ import { storage } from "react-native-nitro-storage";
812
+
813
+ storage.setMetricsObserver((event) => {
814
+ console.log(
815
+ `[nitro-storage] ${event.operation} scope=${event.scope} duration=${event.durationMs}ms keys=${event.keysCount}`,
816
+ );
817
+ });
818
+
819
+ const metrics = storage.getMetricsSnapshot();
820
+ console.log(metrics["item:get"]?.avgDurationMs);
821
+
822
+ storage.resetMetrics();
823
+ storage.setMetricsObserver(undefined);
824
+ ```
825
+
721
826
  ### Low-level Subscription (outside React)
722
827
 
723
828
  ```ts
@@ -769,6 +874,12 @@ import type {
769
874
  MigrationContext,
770
875
  Migration,
771
876
  TransactionContext,
877
+ StorageVersion,
878
+ VersionedValue,
879
+ StorageMetricsEvent,
880
+ StorageMetricsObserver,
881
+ StorageMetricSummary,
882
+ WebSecureStorageBackend,
772
883
  MMKVLike,
773
884
  SecureAuthStorageConfig,
774
885
  } from "react-native-nitro-storage";
@@ -90,6 +90,14 @@ std::vector<std::string> AndroidStorageAdapterCpp::getAllKeysDisk() {
90
90
  return fromJavaStringArray(keys);
91
91
  }
92
92
 
93
+ std::vector<std::string> AndroidStorageAdapterCpp::getKeysByPrefixDisk(const std::string& prefix) {
94
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
95
+ local_ref<JavaStringArray>(std::string)
96
+ >("getKeysByPrefixDisk");
97
+ auto keys = method(AndroidStorageAdapterJava::javaClassStatic(), prefix);
98
+ return fromJavaStringArray(keys);
99
+ }
100
+
93
101
  size_t AndroidStorageAdapterCpp::sizeDisk() {
94
102
  static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<jint()>("sizeDisk");
95
103
  return static_cast<size_t>(method(AndroidStorageAdapterJava::javaClassStatic()));
@@ -166,6 +174,14 @@ std::vector<std::string> AndroidStorageAdapterCpp::getAllKeysSecure() {
166
174
  return fromJavaStringArray(keys);
167
175
  }
168
176
 
177
+ std::vector<std::string> AndroidStorageAdapterCpp::getKeysByPrefixSecure(const std::string& prefix) {
178
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
179
+ local_ref<JavaStringArray>(std::string)
180
+ >("getKeysByPrefixSecure");
181
+ auto keys = method(AndroidStorageAdapterJava::javaClassStatic(), prefix);
182
+ return fromJavaStringArray(keys);
183
+ }
184
+
169
185
  size_t AndroidStorageAdapterCpp::sizeSecure() {
170
186
  static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<jint()>("sizeSecure");
171
187
  return static_cast<size_t>(method(AndroidStorageAdapterJava::javaClassStatic()));
@@ -222,8 +238,12 @@ void AndroidStorageAdapterCpp::setKeychainAccessGroup(const std::string& /*group
222
238
  // --- Biometric ---
223
239
 
224
240
  void AndroidStorageAdapterCpp::setSecureBiometric(const std::string& key, const std::string& value) {
225
- static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void(std::string, std::string)>("setSecureBiometric");
226
- method(AndroidStorageAdapterJava::javaClassStatic(), key, value);
241
+ setSecureBiometricWithLevel(key, value, 2);
242
+ }
243
+
244
+ void AndroidStorageAdapterCpp::setSecureBiometricWithLevel(const std::string& key, const std::string& value, int level) {
245
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void(std::string, std::string, jint)>("setSecureBiometricWithLevel");
246
+ method(AndroidStorageAdapterJava::javaClassStatic(), key, value, level);
227
247
  }
228
248
 
229
249
  std::optional<std::string> AndroidStorageAdapterCpp::getSecureBiometric(const std::string& key) {
@@ -30,6 +30,7 @@ public:
30
30
  void deleteDisk(const std::string& key) override;
31
31
  bool hasDisk(const std::string& key) override;
32
32
  std::vector<std::string> getAllKeysDisk() override;
33
+ std::vector<std::string> getKeysByPrefixDisk(const std::string& prefix) override;
33
34
  size_t sizeDisk() override;
34
35
  void setDiskBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) override;
35
36
  std::vector<std::optional<std::string>> getDiskBatch(const std::vector<std::string>& keys) override;
@@ -40,6 +41,7 @@ public:
40
41
  void deleteSecure(const std::string& key) override;
41
42
  bool hasSecure(const std::string& key) override;
42
43
  std::vector<std::string> getAllKeysSecure() override;
44
+ std::vector<std::string> getKeysByPrefixSecure(const std::string& prefix) override;
43
45
  size_t sizeSecure() override;
44
46
  void setSecureBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) override;
45
47
  std::vector<std::optional<std::string>> getSecureBatch(const std::vector<std::string>& keys) override;
@@ -53,6 +55,7 @@ public:
53
55
  void setKeychainAccessGroup(const std::string& group) override;
54
56
 
55
57
  void setSecureBiometric(const std::string& key, const std::string& value) override;
58
+ void setSecureBiometricWithLevel(const std::string& key, const std::string& value, int level) override;
56
59
  std::optional<std::string> getSecureBiometric(const std::string& key) override;
57
60
  void deleteSecureBiometric(const std::string& key) override;
58
61
  bool hasSecureBiometric(const std::string& key) override;
@@ -36,6 +36,9 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
36
36
 
37
37
  @Volatile
38
38
  private var secureWritesAsync = false
39
+
40
+ @Volatile
41
+ private var secureKeysCache: Array<String>? = null
39
42
 
40
43
  private fun initializeEncryptedPreferences(name: String, key: MasterKey): SharedPreferences {
41
44
  return try {
@@ -101,6 +104,30 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
101
104
  editor.commit()
102
105
  }
103
106
  }
107
+
108
+ private fun invalidateSecureKeysCache() {
109
+ secureKeysCache = null
110
+ }
111
+
112
+ private fun getSecureKeysCached(): Array<String> {
113
+ val cached = secureKeysCache
114
+ if (cached != null) {
115
+ return cached
116
+ }
117
+
118
+ synchronized(this) {
119
+ val existing = secureKeysCache
120
+ if (existing != null) {
121
+ return existing
122
+ }
123
+ val keys = linkedSetOf<String>()
124
+ keys.addAll(encryptedPreferences.all.keys)
125
+ keys.addAll(biometricPreferences.all.keys)
126
+ val built = keys.toTypedArray()
127
+ secureKeysCache = built
128
+ return built
129
+ }
130
+ }
104
131
 
105
132
  companion object {
106
133
  @Volatile
@@ -188,6 +215,13 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
188
215
  return getInstanceOrThrow().sharedPreferences.all.keys.toTypedArray()
189
216
  }
190
217
 
218
+ @JvmStatic
219
+ fun getKeysByPrefixDisk(prefix: String): Array<String> {
220
+ return getInstanceOrThrow().sharedPreferences.all.keys
221
+ .filter { it.startsWith(prefix) }
222
+ .toTypedArray()
223
+ }
224
+
191
225
  @JvmStatic
192
226
  fun sizeDisk(): Int {
193
227
  return getInstanceOrThrow().sharedPreferences.all.size
@@ -205,6 +239,7 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
205
239
  val inst = getInstanceOrThrow()
206
240
  val editor = inst.encryptedPreferences.edit().putString(key, value)
207
241
  inst.applySecureEditor(editor)
242
+ inst.invalidateSecureKeysCache()
208
243
  }
209
244
 
210
245
  @JvmStatic
@@ -216,6 +251,7 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
216
251
  editor.putString(keys[index], values[index])
217
252
  }
218
253
  inst.applySecureEditor(editor)
254
+ inst.invalidateSecureKeysCache()
219
255
  }
220
256
 
221
257
  @JvmStatic
@@ -236,6 +272,7 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
236
272
  val inst = getInstanceOrThrow()
237
273
  inst.applySecureEditor(inst.encryptedPreferences.edit().remove(key))
238
274
  inst.applySecureEditor(inst.biometricPreferences.edit().remove(key))
275
+ inst.invalidateSecureKeysCache()
239
276
  }
240
277
 
241
278
  @JvmStatic
@@ -249,6 +286,7 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
249
286
  }
250
287
  inst.applySecureEditor(secureEditor)
251
288
  inst.applySecureEditor(biometricEditor)
289
+ inst.invalidateSecureKeysCache()
252
290
  }
253
291
 
254
292
  @JvmStatic
@@ -260,15 +298,17 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
260
298
  @JvmStatic
261
299
  fun getAllKeysSecure(): Array<String> {
262
300
  val inst = getInstanceOrThrow()
263
- val keys = linkedSetOf<String>()
264
- keys.addAll(inst.encryptedPreferences.all.keys)
265
- keys.addAll(inst.biometricPreferences.all.keys)
266
- return keys.toTypedArray()
301
+ return inst.getSecureKeysCached()
302
+ }
303
+
304
+ @JvmStatic
305
+ fun getKeysByPrefixSecure(prefix: String): Array<String> {
306
+ return getAllKeysSecure().filter { it.startsWith(prefix) }.toTypedArray()
267
307
  }
268
308
 
269
309
  @JvmStatic
270
310
  fun sizeSecure(): Int {
271
- return getAllKeysSecure().size
311
+ return getInstanceOrThrow().getSecureKeysCached().size
272
312
  }
273
313
 
274
314
  @JvmStatic
@@ -276,15 +316,22 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
276
316
  val inst = getInstanceOrThrow()
277
317
  inst.applySecureEditor(inst.encryptedPreferences.edit().clear())
278
318
  inst.applySecureEditor(inst.biometricPreferences.edit().clear())
319
+ inst.invalidateSecureKeysCache()
279
320
  }
280
321
 
281
322
  // --- Biometric (separate encrypted store, requires recent biometric auth on Android) ---
282
323
 
283
324
  @JvmStatic
284
325
  fun setSecureBiometric(key: String, value: String) {
326
+ setSecureBiometricWithLevel(key, value, 2)
327
+ }
328
+
329
+ @JvmStatic
330
+ fun setSecureBiometricWithLevel(key: String, value: String, @Suppress("UNUSED_PARAMETER") level: Int) {
285
331
  val inst = getInstanceOrThrow()
286
332
  val editor = inst.biometricPreferences.edit().putString(key, value)
287
333
  inst.applySecureEditor(editor)
334
+ inst.invalidateSecureKeysCache()
288
335
  }
289
336
 
290
337
  @JvmStatic
@@ -296,6 +343,7 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
296
343
  fun deleteSecureBiometric(key: String) {
297
344
  val inst = getInstanceOrThrow()
298
345
  inst.applySecureEditor(inst.biometricPreferences.edit().remove(key))
346
+ inst.invalidateSecureKeysCache()
299
347
  }
300
348
 
301
349
  @JvmStatic
@@ -307,6 +355,7 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
307
355
  fun clearSecureBiometric() {
308
356
  val inst = getInstanceOrThrow()
309
357
  inst.applySecureEditor(inst.biometricPreferences.edit().clear())
358
+ inst.invalidateSecureKeysCache()
310
359
  }
311
360
  }
312
361
  }