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.
- package/README.md +141 -30
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +22 -2
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +3 -0
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +54 -5
- package/cpp/bindings/HybridStorage.cpp +167 -22
- package/cpp/bindings/HybridStorage.hpp +12 -1
- package/cpp/core/NativeStorageAdapter.hpp +3 -0
- package/ios/IOSStorageAdapterCpp.hpp +16 -0
- package/ios/IOSStorageAdapterCpp.mm +135 -11
- package/lib/commonjs/index.js +466 -275
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +564 -270
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +25 -0
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/module/index.js +466 -277
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +564 -272
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +24 -0
- package/lib/module/internal.js.map +1 -1
- package/lib/typescript/Storage.nitro.d.ts +2 -0
- package/lib/typescript/Storage.nitro.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +38 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +40 -1
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +1 -0
- package/lib/typescript/internal.d.ts.map +1 -1
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +2 -0
- package/package.json +1 -1
- package/src/Storage.nitro.ts +2 -0
- package/src/index.ts +616 -296
- package/src/index.web.ts +728 -288
- 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
|
|
193
|
-
|
|
|
194
|
-
| `get()`
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
| `
|
|
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
|
|
253
|
-
|
|
|
254
|
-
| `storage.clear(scope)`
|
|
255
|
-
| `storage.clearAll()`
|
|
256
|
-
| `storage.clearNamespace(ns, scope)`
|
|
257
|
-
| `storage.clearBiometric()`
|
|
258
|
-
| `storage.has(key, scope)`
|
|
259
|
-
| `storage.getAllKeys(scope)`
|
|
260
|
-
| `storage.
|
|
261
|
-
| `storage.
|
|
262
|
-
| `storage.
|
|
263
|
-
| `storage.
|
|
264
|
-
| `storage.
|
|
265
|
-
| `storage.
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
226
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
|
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
|
}
|