react-native-nitro-storage 0.3.2 → 0.4.1
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 +192 -30
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +22 -2
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +3 -0
- package/android/src/main/cpp/cpp-adapter.cpp +3 -1
- 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 +522 -275
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +614 -270
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/indexeddb-backend.js +130 -0
- package/lib/commonjs/indexeddb-backend.js.map +1 -0
- package/lib/commonjs/internal.js +25 -0
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/module/index.js +516 -277
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +608 -272
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/indexeddb-backend.js +126 -0
- package/lib/module/indexeddb-backend.js.map +1 -0
- 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 +40 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +42 -1
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/indexeddb-backend.d.ts +29 -0
- package/lib/typescript/indexeddb-backend.d.ts.map +1 -0
- package/lib/typescript/internal.d.ts +1 -0
- package/lib/typescript/internal.d.ts.map +1 -1
- package/nitrogen/generated/android/NitroStorageOnLoad.cpp +22 -17
- package/nitrogen/generated/android/NitroStorageOnLoad.hpp +13 -4
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +2 -0
- package/package.json +7 -3
- package/src/Storage.nitro.ts +2 -0
- package/src/index.ts +671 -296
- package/src/index.web.ts +776 -288
- package/src/indexeddb-backend.ts +143 -0
- package/src/internal.ts +28 -0
package/README.md
CHANGED
|
@@ -14,6 +14,12 @@ 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
|
|
21
|
+
- **IndexedDB backend** — drop-in `createIndexedDBBackend` factory for persistent web Secure storage with large payloads
|
|
22
|
+
- **Bulk import** — load a raw `Record<string, string>` into any scope atomically with `storage.import`
|
|
17
23
|
- **Transactions** — grouped writes with automatic rollback on error
|
|
18
24
|
- **Migrations** — versioned data migrations with `registerMigration` / `migrateToLatest`
|
|
19
25
|
- **MMKV migration** — drop-in `migrateFromMMKV` for painless migration from MMKV
|
|
@@ -31,6 +37,12 @@ Every feature in this package is documented with at least one runnable example i
|
|
|
31
37
|
- Validation + recovery — see Feature Flags with Validation
|
|
32
38
|
- Biometric + access control — see Biometric-protected Secrets
|
|
33
39
|
- Global storage utilities (`clear*`, `has`, `getAll*`, `size`, secure write settings) — see Global utility examples and Storage Snapshots and Cleanup
|
|
40
|
+
- Prefix utilities (`getKeysByPrefix`, `getByPrefix`) — see Prefix Queries and Namespace Inspection
|
|
41
|
+
- Versioned item API (`getWithVersion`, `setIfVersion`) — see Optimistic Versioned Writes
|
|
42
|
+
- Metrics API (`setMetricsObserver`, `getMetricsSnapshot`, `resetMetrics`) — see Storage Metrics Instrumentation
|
|
43
|
+
- Web secure backend override (`setWebSecureStorageBackend`, `getWebSecureStorageBackend`) — see Custom Web Secure Backend
|
|
44
|
+
- IndexedDB backend factory (`createIndexedDBBackend`) — see IndexedDB Backend for Web
|
|
45
|
+
- Bulk import (`storage.import`) — see Bulk Data Import
|
|
34
46
|
- Batch APIs (`getBatch`, `setBatch`, `removeBatch`) — see Batch Operations and Bulk Bootstrap with Batch APIs
|
|
35
47
|
- Transactions — see Transactions and Atomic Balance Transfer
|
|
36
48
|
- Migrations (`registerMigration`, `migrateToLatest`) — see Migrations
|
|
@@ -185,21 +197,24 @@ function createStorageItem<T = undefined>(
|
|
|
185
197
|
| `coalesceSecureWrites` | `boolean` | `false` | Batch same-tick Secure writes per key |
|
|
186
198
|
| `namespace` | `string` | — | Prefix key as `namespace:key` for isolation |
|
|
187
199
|
| `biometric` | `boolean` | `false` | Require biometric auth (Secure scope only) |
|
|
200
|
+
| `biometricLevel` | `BiometricLevel` | `None` | Biometric policy (`BiometryOrPasscode` / `BiometryOnly`) |
|
|
188
201
|
| `accessControl` | `AccessControl` | — | Keychain access control level (native only) |
|
|
189
202
|
|
|
190
203
|
**Returned `StorageItem<T>`:**
|
|
191
204
|
|
|
192
|
-
| Method / Property
|
|
193
|
-
|
|
|
194
|
-
| `get()`
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
| `
|
|
205
|
+
| Method / Property | Type | Description |
|
|
206
|
+
| ------------------- | -------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
207
|
+
| `get()` | `() => T` | Read current value (synchronous) |
|
|
208
|
+
| `getWithVersion()` | `() => { value: T; version: StorageVersion }` | Read value plus current storage version token |
|
|
209
|
+
| `set(value)` | `(value: T \| ((prev: T) => T)) => void` | Write a value or updater function |
|
|
210
|
+
| `setIfVersion(...)` | `(version: StorageVersion, value: T \| ((prev: T) => T)) => boolean` | Write only if version matches (optimistic concurrency) |
|
|
211
|
+
| `delete()` | `() => void` | Remove the stored value (resets to `defaultValue`) |
|
|
212
|
+
| `has()` | `() => boolean` | Check if a value exists in storage |
|
|
213
|
+
| `subscribe(cb)` | `(cb: () => void) => () => void` | Listen for changes, returns unsubscribe |
|
|
214
|
+
| `serialize` | `(v: T) => string` | The item's serializer |
|
|
215
|
+
| `deserialize` | `(v: string) => T` | The item's deserializer |
|
|
216
|
+
| `scope` | `StorageScope` | The item's scope |
|
|
217
|
+
| `key` | `string` | The resolved key (includes namespace prefix) |
|
|
203
218
|
|
|
204
219
|
**Non-React subscription example:**
|
|
205
220
|
|
|
@@ -249,30 +264,42 @@ setToken("new-token");
|
|
|
249
264
|
import { storage, StorageScope } from "react-native-nitro-storage";
|
|
250
265
|
```
|
|
251
266
|
|
|
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.
|
|
267
|
+
| Method | Description |
|
|
268
|
+
| ---------------------------------------- | ---------------------------------------------------------------------------- |
|
|
269
|
+
| `storage.clear(scope)` | Clear all keys in a scope (`Secure` also clears biometric entries) |
|
|
270
|
+
| `storage.clearAll()` | Clear Memory + Disk + Secure |
|
|
271
|
+
| `storage.clearNamespace(ns, scope)` | Remove only keys matching a namespace |
|
|
272
|
+
| `storage.clearBiometric()` | Remove all biometric-prefixed keys |
|
|
273
|
+
| `storage.has(key, scope)` | Check if a key exists |
|
|
274
|
+
| `storage.getAllKeys(scope)` | Get all key names |
|
|
275
|
+
| `storage.getKeysByPrefix(prefix, scope)` | Get keys that start with a prefix |
|
|
276
|
+
| `storage.getByPrefix(prefix, scope)` | Get raw key-value pairs for keys matching a prefix |
|
|
277
|
+
| `storage.getAll(scope)` | Get all key-value pairs as `Record<string, string>` |
|
|
278
|
+
| `storage.size(scope)` | Number of stored keys |
|
|
279
|
+
| `storage.setAccessControl(level)` | Set default secure access control for subsequent secure writes (native only) |
|
|
280
|
+
| `storage.setSecureWritesAsync(enabled)` | Toggle async secure writes on Android (`false` by default) |
|
|
281
|
+
| `storage.flushSecureWrites()` | Force flush of queued secure writes when coalescing is enabled |
|
|
282
|
+
| `storage.setKeychainAccessGroup(group)` | Set keychain access group for app sharing (native only) |
|
|
283
|
+
| `storage.import(data, scope)` | Bulk-load a `Record<string, string>` of raw key/value pairs into a scope |
|
|
284
|
+
| `storage.setMetricsObserver(observer?)` | Subscribe to per-operation timing events |
|
|
285
|
+
| `storage.getMetricsSnapshot()` | Get aggregate counters/latency stats keyed by operation |
|
|
286
|
+
| `storage.resetMetrics()` | Reset in-memory metrics counters |
|
|
266
287
|
|
|
267
288
|
> `storage.getAll(StorageScope.Secure)` returns regular secure entries. Biometric-protected values are not included in this snapshot API.
|
|
268
289
|
|
|
269
290
|
#### Global utility examples
|
|
270
291
|
|
|
271
292
|
```ts
|
|
272
|
-
import {
|
|
293
|
+
import {
|
|
294
|
+
AccessControl,
|
|
295
|
+
storage,
|
|
296
|
+
StorageScope,
|
|
297
|
+
} from "react-native-nitro-storage";
|
|
273
298
|
|
|
274
299
|
storage.has("session", StorageScope.Disk);
|
|
275
300
|
storage.getAllKeys(StorageScope.Disk);
|
|
301
|
+
storage.getKeysByPrefix("user-42:", StorageScope.Disk);
|
|
302
|
+
storage.getByPrefix("user-42:", StorageScope.Disk);
|
|
276
303
|
storage.getAll(StorageScope.Disk);
|
|
277
304
|
storage.size(StorageScope.Disk);
|
|
278
305
|
|
|
@@ -303,6 +330,55 @@ storage.setSecureWritesAsync(true);
|
|
|
303
330
|
storage.flushSecureWrites(); // deterministic durability boundary
|
|
304
331
|
```
|
|
305
332
|
|
|
333
|
+
#### Custom web secure backend
|
|
334
|
+
|
|
335
|
+
By default, web Secure scope uses `localStorage` with `__secure_` key prefixing. You can replace it with a custom backend (for example encrypted IndexedDB adapter).
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
import {
|
|
339
|
+
getWebSecureStorageBackend,
|
|
340
|
+
setWebSecureStorageBackend,
|
|
341
|
+
} from "react-native-nitro-storage";
|
|
342
|
+
|
|
343
|
+
setWebSecureStorageBackend({
|
|
344
|
+
getItem: (key) => encryptedStore.get(key) ?? null,
|
|
345
|
+
setItem: (key, value) => encryptedStore.set(key, value),
|
|
346
|
+
removeItem: (key) => encryptedStore.delete(key),
|
|
347
|
+
clear: () => encryptedStore.clear(),
|
|
348
|
+
getAllKeys: () => encryptedStore.keys(),
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const backend = getWebSecureStorageBackend();
|
|
352
|
+
console.log("custom backend active:", backend !== undefined);
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
### IndexedDB Backend for Web
|
|
358
|
+
|
|
359
|
+
The default web Secure backend uses `localStorage`, which is synchronous and size-limited. For large payloads or when you need true persistence across tab reloads, use the built-in IndexedDB-backed factory.
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
import { setWebSecureStorageBackend } from "react-native-nitro-storage";
|
|
363
|
+
import { createIndexedDBBackend } from "react-native-nitro-storage/indexeddb-backend";
|
|
364
|
+
|
|
365
|
+
// call once at app startup, before rendering any components that read secure items
|
|
366
|
+
const backend = await createIndexedDBBackend();
|
|
367
|
+
setWebSecureStorageBackend(backend);
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**How it works:**
|
|
371
|
+
|
|
372
|
+
- **Async init**: `createIndexedDBBackend()` opens (or creates) the IndexedDB database and hydrates an in-memory cache from all stored entries before resolving.
|
|
373
|
+
- **Synchronous reads**: all `getItem` calls are served from the in-memory cache — no async overhead after init.
|
|
374
|
+
- **Fire-and-forget writes**: `setItem`, `removeItem`, and `clear` update the cache synchronously, then persist to IndexedDB in the background. The cache is always the authoritative source.
|
|
375
|
+
- **Custom database/store**: optionally pass `dbName` and `storeName` to isolate databases per environment or tenant.
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
const backend = await createIndexedDBBackend("my-app-db", "secure-kv");
|
|
379
|
+
setWebSecureStorageBackend(backend);
|
|
380
|
+
```
|
|
381
|
+
|
|
306
382
|
---
|
|
307
383
|
|
|
308
384
|
### `createSecureAuthStorage<K>(config, options?)`
|
|
@@ -318,7 +394,7 @@ function createSecureAuthStorage<K extends string>(
|
|
|
318
394
|
|
|
319
395
|
- Default namespace: `"auth"`
|
|
320
396
|
- Each key is a separate `StorageItem<string>` with `StorageScope.Secure`
|
|
321
|
-
- Supports per-key TTL, biometric, and access control
|
|
397
|
+
- Supports per-key TTL, biometric level policy, and access control
|
|
322
398
|
|
|
323
399
|
---
|
|
324
400
|
|
|
@@ -550,13 +626,18 @@ const flagsItem = createStorageItem<FeatureFlags>({
|
|
|
550
626
|
### Biometric-protected Secrets
|
|
551
627
|
|
|
552
628
|
```ts
|
|
553
|
-
import {
|
|
629
|
+
import {
|
|
630
|
+
AccessControl,
|
|
631
|
+
BiometricLevel,
|
|
632
|
+
createStorageItem,
|
|
633
|
+
StorageScope,
|
|
634
|
+
} from "react-native-nitro-storage";
|
|
554
635
|
|
|
555
636
|
const paymentPin = createStorageItem<string>({
|
|
556
637
|
key: "payment-pin",
|
|
557
638
|
scope: StorageScope.Secure,
|
|
558
639
|
defaultValue: "",
|
|
559
|
-
|
|
640
|
+
biometricLevel: BiometricLevel.BiometryOnly,
|
|
560
641
|
accessControl: AccessControl.WhenPasscodeSetThisDeviceOnly,
|
|
561
642
|
});
|
|
562
643
|
|
|
@@ -686,7 +767,11 @@ const compactItem = createStorageItem<{ id: number; active: boolean }>({
|
|
|
686
767
|
### Coalesced Secure Writes with Deterministic Flush
|
|
687
768
|
|
|
688
769
|
```ts
|
|
689
|
-
import {
|
|
770
|
+
import {
|
|
771
|
+
createStorageItem,
|
|
772
|
+
storage,
|
|
773
|
+
StorageScope,
|
|
774
|
+
} from "react-native-nitro-storage";
|
|
690
775
|
|
|
691
776
|
const sessionToken = createStorageItem<string>({
|
|
692
777
|
key: "session-token",
|
|
@@ -702,6 +787,25 @@ sessionToken.set("token-v2");
|
|
|
702
787
|
storage.flushSecureWrites();
|
|
703
788
|
```
|
|
704
789
|
|
|
790
|
+
### Bulk Data Import
|
|
791
|
+
|
|
792
|
+
Load server-fetched data into storage in one atomic call. All keys become visible simultaneously before any listener fires.
|
|
793
|
+
|
|
794
|
+
```ts
|
|
795
|
+
import { storage, StorageScope } from "react-native-nitro-storage";
|
|
796
|
+
|
|
797
|
+
// seed local cache from a server response
|
|
798
|
+
const serverData = await fetchInitialData(); // Record<string, string>
|
|
799
|
+
storage.import(serverData, StorageScope.Disk);
|
|
800
|
+
|
|
801
|
+
// all imported keys are immediately readable
|
|
802
|
+
const value = storage.has("remote-config", StorageScope.Disk);
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
> `storage.import` writes raw string values directly — serialization is bypassed. Use it for bootstrapping data that was already serialized by the server or exported via `storage.getAll`.
|
|
806
|
+
|
|
807
|
+
---
|
|
808
|
+
|
|
705
809
|
### Storage Snapshots and Cleanup
|
|
706
810
|
|
|
707
811
|
```ts
|
|
@@ -718,6 +822,58 @@ if (storage.has("legacy-flag", StorageScope.Disk)) {
|
|
|
718
822
|
storage.clearBiometric();
|
|
719
823
|
```
|
|
720
824
|
|
|
825
|
+
### Prefix Queries and Namespace Inspection
|
|
826
|
+
|
|
827
|
+
```ts
|
|
828
|
+
import { storage, StorageScope } from "react-native-nitro-storage";
|
|
829
|
+
|
|
830
|
+
const userKeys = storage.getKeysByPrefix("user-42:", StorageScope.Disk);
|
|
831
|
+
const userRawEntries = storage.getByPrefix("user-42:", StorageScope.Disk);
|
|
832
|
+
|
|
833
|
+
console.log(userKeys);
|
|
834
|
+
console.log(userRawEntries);
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
### Optimistic Versioned Writes
|
|
838
|
+
|
|
839
|
+
```ts
|
|
840
|
+
import { createStorageItem, StorageScope } from "react-native-nitro-storage";
|
|
841
|
+
|
|
842
|
+
const profileItem = createStorageItem({
|
|
843
|
+
key: "profile",
|
|
844
|
+
scope: StorageScope.Disk,
|
|
845
|
+
defaultValue: { name: "Guest" },
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
const snapshot = profileItem.getWithVersion();
|
|
849
|
+
const didWrite = profileItem.setIfVersion(snapshot.version, {
|
|
850
|
+
...snapshot.value,
|
|
851
|
+
name: "Ada",
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
if (!didWrite) {
|
|
855
|
+
// value changed since snapshot; reload and retry
|
|
856
|
+
}
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
### Storage Metrics Instrumentation
|
|
860
|
+
|
|
861
|
+
```ts
|
|
862
|
+
import { storage } from "react-native-nitro-storage";
|
|
863
|
+
|
|
864
|
+
storage.setMetricsObserver((event) => {
|
|
865
|
+
console.log(
|
|
866
|
+
`[nitro-storage] ${event.operation} scope=${event.scope} duration=${event.durationMs}ms keys=${event.keysCount}`,
|
|
867
|
+
);
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
const metrics = storage.getMetricsSnapshot();
|
|
871
|
+
console.log(metrics["item:get"]?.avgDurationMs);
|
|
872
|
+
|
|
873
|
+
storage.resetMetrics();
|
|
874
|
+
storage.setMetricsObserver(undefined);
|
|
875
|
+
```
|
|
876
|
+
|
|
721
877
|
### Low-level Subscription (outside React)
|
|
722
878
|
|
|
723
879
|
```ts
|
|
@@ -769,6 +925,12 @@ import type {
|
|
|
769
925
|
MigrationContext,
|
|
770
926
|
Migration,
|
|
771
927
|
TransactionContext,
|
|
928
|
+
StorageVersion,
|
|
929
|
+
VersionedValue,
|
|
930
|
+
StorageMetricsEvent,
|
|
931
|
+
StorageMetricsObserver,
|
|
932
|
+
StorageMetricSummary,
|
|
933
|
+
WebSecureStorageBackend,
|
|
772
934
|
MMKVLike,
|
|
773
935
|
SecureAuthStorageConfig,
|
|
774
936
|
} 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;
|
|
@@ -3,5 +3,7 @@
|
|
|
3
3
|
#include "../../../nitrogen/generated/android/NitroStorageOnLoad.hpp"
|
|
4
4
|
|
|
5
5
|
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
|
|
6
|
-
return
|
|
6
|
+
return facebook::jni::initialize(vm, []() {
|
|
7
|
+
margelo::nitro::NitroStorage::registerAllNatives();
|
|
8
|
+
});
|
|
7
9
|
}
|
|
@@ -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
|
}
|