react-native-nitro-storage 0.4.1 → 0.4.3
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 +39 -0
- package/android/build.gradle +0 -12
- package/android/consumer-rules.pro +26 -4
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +7 -10
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +0 -4
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +172 -77
- package/cpp/bindings/HybridStorage.cpp +120 -69
- package/cpp/bindings/HybridStorage.hpp +4 -0
- package/ios/IOSStorageAdapterCpp.hpp +2 -1
- package/ios/IOSStorageAdapterCpp.mm +264 -49
- package/lib/commonjs/index.js +73 -21
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +120 -42
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +51 -23
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/module/index.js +72 -21
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +119 -42
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +51 -23
- package/lib/module/internal.js.map +1 -1
- package/lib/typescript/index.d.ts +4 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +5 -1
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +83 -28
- package/src/index.web.ts +135 -50
- package/src/internal.ts +51 -23
package/README.md
CHANGED
|
@@ -47,6 +47,8 @@ Every feature in this package is documented with at least one runnable example i
|
|
|
47
47
|
- Transactions — see Transactions and Atomic Balance Transfer
|
|
48
48
|
- Migrations (`registerMigration`, `migrateToLatest`) — see Migrations
|
|
49
49
|
- MMKV migration (`migrateFromMMKV`) — see MMKV Migration and Migrating From MMKV
|
|
50
|
+
- Raw string API (`getString`, `setString`, `deleteString`) — see Raw String API
|
|
51
|
+
- Keychain locked detection (`isKeychainLockedError`) — see `isKeychainLockedError(err)`
|
|
50
52
|
- Auth storage factory (`createSecureAuthStorage`) — see Auth Token Management
|
|
51
53
|
|
|
52
54
|
## Requirements
|
|
@@ -280,6 +282,9 @@ import { storage, StorageScope } from "react-native-nitro-storage";
|
|
|
280
282
|
| `storage.setSecureWritesAsync(enabled)` | Toggle async secure writes on Android (`false` by default) |
|
|
281
283
|
| `storage.flushSecureWrites()` | Force flush of queued secure writes when coalescing is enabled |
|
|
282
284
|
| `storage.setKeychainAccessGroup(group)` | Set keychain access group for app sharing (native only) |
|
|
285
|
+
| `storage.getString(key, scope)` | Read a raw string value directly (bypasses serialization) |
|
|
286
|
+
| `storage.setString(key, value, scope)` | Write a raw string value directly (bypasses serialization) |
|
|
287
|
+
| `storage.deleteString(key, scope)` | Delete a raw string value by key |
|
|
283
288
|
| `storage.import(data, scope)` | Bulk-load a `Record<string, string>` of raw key/value pairs into a scope |
|
|
284
289
|
| `storage.setMetricsObserver(observer?)` | Subscribe to per-operation timing events |
|
|
285
290
|
| `storage.getMetricsSnapshot()` | Get aggregate counters/latency stats keyed by operation |
|
|
@@ -509,6 +514,40 @@ const migrated = migrateFromMMKV(mmkv, myStorageItem, true);
|
|
|
509
514
|
|
|
510
515
|
---
|
|
511
516
|
|
|
517
|
+
### Raw String API
|
|
518
|
+
|
|
519
|
+
For cases where you want to bypass `createStorageItem` serialization entirely and work with raw key/value strings:
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
import { storage, StorageScope } from "react-native-nitro-storage";
|
|
523
|
+
|
|
524
|
+
storage.setString("raw-key", "raw-value", StorageScope.Disk);
|
|
525
|
+
const value = storage.getString("raw-key", StorageScope.Disk); // "raw-value" | undefined
|
|
526
|
+
storage.deleteString("raw-key", StorageScope.Disk);
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
These are synchronous and go directly to the native backend without any serialize/deserialize step.
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
### `isKeychainLockedError(err)`
|
|
534
|
+
|
|
535
|
+
Utility to detect iOS Keychain locked errors and Android key invalidation errors in secure storage operations. Returns `true` if the error was caused by a locked keychain (device locked, first unlock not yet performed, etc.) or an Android `KeyPermanentlyInvalidatedException` / `InvalidKeyException`. Always returns `false` on web.
|
|
536
|
+
|
|
537
|
+
```ts
|
|
538
|
+
import { isKeychainLockedError } from "react-native-nitro-storage";
|
|
539
|
+
|
|
540
|
+
try {
|
|
541
|
+
secureItem.get();
|
|
542
|
+
} catch (err) {
|
|
543
|
+
if (isKeychainLockedError(err)) {
|
|
544
|
+
// device is locked — retry after unlock
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
512
551
|
### Enums
|
|
513
552
|
|
|
514
553
|
#### `AccessControl`
|
package/android/build.gradle
CHANGED
|
@@ -14,19 +14,11 @@ def reactNativeArchitectures() {
|
|
|
14
14
|
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
def isNewArchitectureEnabled() {
|
|
18
|
-
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
19
|
-
}
|
|
20
|
-
|
|
21
17
|
apply plugin: "com.android.library"
|
|
22
18
|
apply plugin: "org.jetbrains.kotlin.android"
|
|
23
19
|
// Apply autolinking script for Nitro
|
|
24
20
|
apply from: "../nitrogen/generated/android/NitroStorage+autolinking.gradle"
|
|
25
21
|
|
|
26
|
-
if (isNewArchitectureEnabled()) {
|
|
27
|
-
apply plugin: "com.facebook.react"
|
|
28
|
-
}
|
|
29
|
-
|
|
30
22
|
def getExtOrDefault(name) {
|
|
31
23
|
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NitroStorage_" + name]
|
|
32
24
|
}
|
|
@@ -45,7 +37,6 @@ android {
|
|
|
45
37
|
defaultConfig {
|
|
46
38
|
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
47
39
|
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
48
|
-
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
49
40
|
consumerProguardFiles "consumer-rules.pro"
|
|
50
41
|
|
|
51
42
|
externalNativeBuild {
|
|
@@ -76,9 +67,6 @@ android {
|
|
|
76
67
|
sourceSets {
|
|
77
68
|
main {
|
|
78
69
|
java.srcDirs += ["src/main/java"]
|
|
79
|
-
if (isNewArchitectureEnabled()) {
|
|
80
|
-
java.srcDirs += ["${project.buildDir}/generated/source/codegen/java"]
|
|
81
|
-
}
|
|
82
70
|
}
|
|
83
71
|
}
|
|
84
72
|
}
|
|
@@ -1,4 +1,26 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
-
|
|
4
|
-
|
|
1
|
+
# NitroStorage - JNI-callable methods must survive R8/ProGuard shrinking
|
|
2
|
+
|
|
3
|
+
-keep class com.nitrostorage.AndroidStorageAdapter {
|
|
4
|
+
public static *** set*(...);
|
|
5
|
+
public static *** get*(...);
|
|
6
|
+
public static *** delete*(...);
|
|
7
|
+
public static *** has*(...);
|
|
8
|
+
public static *** clear*(...);
|
|
9
|
+
public static *** size*(...);
|
|
10
|
+
public static *** flush*(...);
|
|
11
|
+
public static void init(android.content.Context);
|
|
12
|
+
public static void setSecureWritesAsync(boolean);
|
|
13
|
+
public static void setSecureAccessControl(int);
|
|
14
|
+
public static void removeByPrefix(java.lang.String, int);
|
|
15
|
+
}
|
|
16
|
+
-keep class com.nitrostorage.AndroidStorageAdapter$Companion {
|
|
17
|
+
public <methods>;
|
|
18
|
+
}
|
|
19
|
+
-keep class com.nitrostorage.NitroStoragePackage {
|
|
20
|
+
<init>();
|
|
21
|
+
<clinit>();
|
|
22
|
+
*;
|
|
23
|
+
}
|
|
24
|
+
-keep class com.nitrostorage.NitroStoragePackage$Companion {
|
|
25
|
+
*;
|
|
26
|
+
}
|
|
@@ -34,26 +34,23 @@ std::vector<std::optional<std::string>> fromNullableJavaStringArray(alias_ref<Ja
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
std::vector<std::string> fromJavaStringArray(alias_ref<JavaStringArray> values) {
|
|
37
|
+
if (!values) return {};
|
|
38
|
+
const jsize size = values->size();
|
|
37
39
|
std::vector<std::string> result;
|
|
38
|
-
if (!values) return result;
|
|
39
|
-
|
|
40
|
-
const jsize size = static_cast<jsize>(values->size());
|
|
41
40
|
result.reserve(size);
|
|
42
41
|
for (jsize i = 0; i < size; ++i) {
|
|
43
42
|
auto currentValue = values->getElement(i);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
43
|
+
// Preserve null as empty string to maintain index alignment with caller
|
|
44
|
+
result.push_back(currentValue ? currentValue->toStdString() : std::string());
|
|
47
45
|
}
|
|
48
46
|
return result;
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
} // namespace
|
|
52
50
|
|
|
53
|
-
AndroidStorageAdapterCpp::AndroidStorageAdapterCpp(alias_ref<JObject> context) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
51
|
+
AndroidStorageAdapterCpp::AndroidStorageAdapterCpp(alias_ref<JObject> /*context*/) {
|
|
52
|
+
// Context is validated by AndroidStorageAdapter.getContext() on the Java side.
|
|
53
|
+
// The adapter calls static Java methods directly via fbjni.
|
|
57
54
|
}
|
|
58
55
|
|
|
59
56
|
AndroidStorageAdapterCpp::~AndroidStorageAdapterCpp() = default;
|
|
@@ -6,10 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
namespace NitroStorage {
|
|
8
8
|
|
|
9
|
-
struct JContext : facebook::jni::JavaClass<JContext> {
|
|
10
|
-
static constexpr auto kJavaDescriptor = "Landroid/content/Context;";
|
|
11
|
-
};
|
|
12
|
-
|
|
13
9
|
struct AndroidStorageAdapterJava : facebook::jni::JavaClass<AndroidStorageAdapterJava> {
|
|
14
10
|
static constexpr auto kJavaDescriptor = "Lcom/nitrostorage/AndroidStorageAdapter;";
|
|
15
11
|
|
|
@@ -8,15 +8,29 @@ import androidx.security.crypto.MasterKey
|
|
|
8
8
|
import java.security.KeyStore
|
|
9
9
|
import javax.crypto.AEADBadTagException
|
|
10
10
|
|
|
11
|
+
private fun Throwable.hasCause(type: Class<*>): Boolean {
|
|
12
|
+
var current: Throwable? = this
|
|
13
|
+
while (current != null) {
|
|
14
|
+
if (type.isInstance(current)) return true
|
|
15
|
+
current = current.cause
|
|
16
|
+
}
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
12
|
-
private val sharedPreferences: SharedPreferences =
|
|
21
|
+
private val sharedPreferences: SharedPreferences =
|
|
13
22
|
context.getSharedPreferences("NitroStorage", Context.MODE_PRIVATE)
|
|
14
23
|
|
|
15
24
|
private val masterKeyAlias = "${context.packageName}.nitro_storage.master_key"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.
|
|
19
|
-
|
|
25
|
+
|
|
26
|
+
private val masterKey: MasterKey = try {
|
|
27
|
+
MasterKey.Builder(context, masterKeyAlias)
|
|
28
|
+
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
|
29
|
+
.build()
|
|
30
|
+
} catch (e: Exception) {
|
|
31
|
+
throw RuntimeException("NitroStorage: Cannot create encryption key. Device may not support AES256-GCM.", e)
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
private val encryptedPreferences: SharedPreferences = initializeEncryptedPreferences("NitroStorageSecure", masterKey)
|
|
21
35
|
|
|
22
36
|
private val biometricMasterKeyAlias = "${context.packageName}.nitro_storage.biometric_key"
|
|
@@ -29,8 +43,9 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
29
43
|
.build()
|
|
30
44
|
initializeEncryptedPreferences("NitroStorageBiometric", bioKey)
|
|
31
45
|
} catch (e: Exception) {
|
|
32
|
-
Log.
|
|
33
|
-
|
|
46
|
+
Log.e("NitroStorage", "Biometric storage unavailable: ${e.message}")
|
|
47
|
+
throw RuntimeException("NitroStorage: Biometric storage is not available on this device. " +
|
|
48
|
+
"Ensure biometric hardware is present and credentials are enrolled.", e)
|
|
34
49
|
}
|
|
35
50
|
}
|
|
36
51
|
|
|
@@ -39,47 +54,63 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
39
54
|
|
|
40
55
|
@Volatile
|
|
41
56
|
private var secureKeysCache: Array<String>? = null
|
|
42
|
-
|
|
57
|
+
|
|
43
58
|
private fun initializeEncryptedPreferences(name: String, key: MasterKey): SharedPreferences {
|
|
44
59
|
return try {
|
|
45
60
|
EncryptedSharedPreferences.create(
|
|
46
|
-
context,
|
|
47
|
-
name,
|
|
48
|
-
key,
|
|
61
|
+
context, name, key,
|
|
49
62
|
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
|
50
63
|
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
|
51
64
|
)
|
|
52
65
|
} catch (e: Exception) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
when {
|
|
67
|
+
e.hasCause(AEADBadTagException::class.java) -> {
|
|
68
|
+
Log.w("NitroStorage", "Corrupted encryption keys for $name, attempting recovery...")
|
|
69
|
+
clearCorruptedStorage(name, key)
|
|
70
|
+
val freshAlias = if (name == "NitroStorageBiometric") biometricMasterKeyAlias else masterKeyAlias
|
|
71
|
+
val freshKey = MasterKey.Builder(context, freshAlias)
|
|
72
|
+
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
|
73
|
+
.apply {
|
|
74
|
+
if (name == "NitroStorageBiometric") {
|
|
75
|
+
setUserAuthenticationRequired(true, 30)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
.build()
|
|
79
|
+
try {
|
|
80
|
+
EncryptedSharedPreferences.create(
|
|
81
|
+
context, name, freshKey,
|
|
82
|
+
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
|
83
|
+
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
|
84
|
+
)
|
|
85
|
+
} catch (retryEx: Exception) {
|
|
86
|
+
throw RuntimeException("NitroStorage: Unrecoverable storage corruption in $name", retryEx)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else -> {
|
|
90
|
+
// Don't wipe on non-corruption failures (e.g., locked keystore)
|
|
91
|
+
throw RuntimeException(
|
|
92
|
+
"NitroStorage: Failed to initialize $name (${e::class.simpleName}). " +
|
|
93
|
+
"This may be a temporary keystore issue. If it persists, clear app data.", e
|
|
94
|
+
)
|
|
95
|
+
}
|
|
69
96
|
}
|
|
70
97
|
}
|
|
71
98
|
}
|
|
72
|
-
|
|
99
|
+
|
|
73
100
|
private fun clearCorruptedStorage(name: String, key: MasterKey) {
|
|
74
101
|
try {
|
|
75
102
|
context.deleteSharedPreferences(name)
|
|
76
103
|
val keyStore = KeyStore.getInstance("AndroidKeyStore")
|
|
77
104
|
keyStore.load(null)
|
|
78
|
-
val alias =
|
|
105
|
+
val alias = when {
|
|
106
|
+
name == "NitroStorageSecure" -> masterKeyAlias
|
|
107
|
+
name == "NitroStorageBiometric" -> biometricMasterKeyAlias
|
|
108
|
+
else -> masterKeyAlias
|
|
109
|
+
}
|
|
79
110
|
keyStore.deleteEntry(alias)
|
|
80
111
|
Log.i("NitroStorage", "Cleared corrupted storage: $name")
|
|
81
112
|
} catch (e: Exception) {
|
|
82
|
-
Log.e("NitroStorage", "Failed to clear corrupted storage: $
|
|
113
|
+
Log.e("NitroStorage", "Failed to clear corrupted storage $name: ${e.message}", e)
|
|
83
114
|
}
|
|
84
115
|
}
|
|
85
116
|
|
|
@@ -87,7 +118,7 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
87
118
|
return try {
|
|
88
119
|
prefs.getString(key, null)
|
|
89
120
|
} catch (e: Exception) {
|
|
90
|
-
if (e
|
|
121
|
+
if (e.hasCause(AEADBadTagException::class.java)) {
|
|
91
122
|
Log.w("NitroStorage", "Corrupt entry for key '$key', removing")
|
|
92
123
|
prefs.edit().remove(key).commit()
|
|
93
124
|
null
|
|
@@ -98,15 +129,21 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
98
129
|
}
|
|
99
130
|
|
|
100
131
|
private fun applySecureEditor(editor: SharedPreferences.Editor) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
132
|
+
try {
|
|
133
|
+
if (secureWritesAsync) {
|
|
134
|
+
editor.apply()
|
|
135
|
+
} else {
|
|
136
|
+
editor.commit()
|
|
137
|
+
}
|
|
138
|
+
} catch (e: Exception) {
|
|
139
|
+
throw RuntimeException("NitroStorage: Failed to write to secure storage: ${e.message}", e)
|
|
105
140
|
}
|
|
106
141
|
}
|
|
107
142
|
|
|
108
143
|
private fun invalidateSecureKeysCache() {
|
|
109
|
-
|
|
144
|
+
synchronized(this) {
|
|
145
|
+
secureKeysCache = null
|
|
146
|
+
}
|
|
110
147
|
}
|
|
111
148
|
|
|
112
149
|
private fun getSecureKeysCached(): Array<String> {
|
|
@@ -120,15 +157,20 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
120
157
|
if (existing != null) {
|
|
121
158
|
return existing
|
|
122
159
|
}
|
|
160
|
+
val INTERNAL_PREFIX = "__androidx_security_crypto_encrypted_prefs_"
|
|
123
161
|
val keys = linkedSetOf<String>()
|
|
124
|
-
keys.addAll(encryptedPreferences.all.keys)
|
|
125
|
-
|
|
162
|
+
keys.addAll(encryptedPreferences.all.keys.filter { !it.startsWith(INTERNAL_PREFIX) })
|
|
163
|
+
try {
|
|
164
|
+
keys.addAll(biometricPreferences.all.keys.filter { !it.startsWith(INTERNAL_PREFIX) })
|
|
165
|
+
} catch (e: Exception) {
|
|
166
|
+
Log.d("NitroStorage", "Biometric storage unavailable: ${e.message}")
|
|
167
|
+
}
|
|
126
168
|
val built = keys.toTypedArray()
|
|
127
169
|
secureKeysCache = built
|
|
128
170
|
return built
|
|
129
171
|
}
|
|
130
172
|
}
|
|
131
|
-
|
|
173
|
+
|
|
132
174
|
companion object {
|
|
133
175
|
@Volatile
|
|
134
176
|
private var instance: AndroidStorageAdapter? = null
|
|
@@ -162,7 +204,7 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
162
204
|
}
|
|
163
205
|
|
|
164
206
|
// --- Disk ---
|
|
165
|
-
|
|
207
|
+
|
|
166
208
|
@JvmStatic
|
|
167
209
|
fun setDisk(key: String, value: String) {
|
|
168
210
|
getInstanceOrThrow().sharedPreferences.edit().putString(key, value).apply()
|
|
@@ -177,7 +219,7 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
177
219
|
}
|
|
178
220
|
editor.apply()
|
|
179
221
|
}
|
|
180
|
-
|
|
222
|
+
|
|
181
223
|
@JvmStatic
|
|
182
224
|
fun getDisk(key: String): String? {
|
|
183
225
|
return getInstanceOrThrow().sharedPreferences.getString(key, null)
|
|
@@ -190,7 +232,7 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
190
232
|
prefs.getString(keys[index], null)
|
|
191
233
|
}
|
|
192
234
|
}
|
|
193
|
-
|
|
235
|
+
|
|
194
236
|
@JvmStatic
|
|
195
237
|
fun deleteDisk(key: String) {
|
|
196
238
|
getInstanceOrThrow().sharedPreferences.edit().remove(key).apply()
|
|
@@ -233,30 +275,35 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
233
275
|
}
|
|
234
276
|
|
|
235
277
|
// --- Secure (sync commit by default, async apply when enabled) ---
|
|
236
|
-
|
|
278
|
+
|
|
237
279
|
@JvmStatic
|
|
238
280
|
fun setSecure(key: String, value: String) {
|
|
239
281
|
val inst = getInstanceOrThrow()
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
282
|
+
synchronized(inst) {
|
|
283
|
+
val editor = inst.encryptedPreferences.edit().putString(key, value)
|
|
284
|
+
inst.applySecureEditor(editor)
|
|
285
|
+
inst.invalidateSecureKeysCache()
|
|
286
|
+
}
|
|
243
287
|
}
|
|
244
288
|
|
|
245
289
|
@JvmStatic
|
|
246
290
|
fun setSecureBatch(keys: Array<String>, values: Array<String>) {
|
|
247
291
|
val inst = getInstanceOrThrow()
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
292
|
+
synchronized(inst) {
|
|
293
|
+
val editor = inst.encryptedPreferences.edit()
|
|
294
|
+
val count = minOf(keys.size, values.size)
|
|
295
|
+
for (index in 0 until count) {
|
|
296
|
+
editor.putString(keys[index], values[index])
|
|
297
|
+
}
|
|
298
|
+
inst.applySecureEditor(editor)
|
|
299
|
+
inst.invalidateSecureKeysCache()
|
|
252
300
|
}
|
|
253
|
-
inst.applySecureEditor(editor)
|
|
254
|
-
inst.invalidateSecureKeysCache()
|
|
255
301
|
}
|
|
256
|
-
|
|
302
|
+
|
|
257
303
|
@JvmStatic
|
|
258
304
|
fun getSecure(key: String): String? {
|
|
259
|
-
|
|
305
|
+
val inst = getInstanceOrThrow()
|
|
306
|
+
return inst.getSecureSafe(inst.encryptedPreferences, key)
|
|
260
307
|
}
|
|
261
308
|
|
|
262
309
|
@JvmStatic
|
|
@@ -266,33 +313,54 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
266
313
|
inst.getSecureSafe(inst.encryptedPreferences, keys[index])
|
|
267
314
|
}
|
|
268
315
|
}
|
|
269
|
-
|
|
316
|
+
|
|
270
317
|
@JvmStatic
|
|
271
318
|
fun deleteSecure(key: String) {
|
|
272
319
|
val inst = getInstanceOrThrow()
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
320
|
+
synchronized(inst) {
|
|
321
|
+
inst.applySecureEditor(inst.encryptedPreferences.edit().remove(key))
|
|
322
|
+
try {
|
|
323
|
+
inst.applySecureEditor(inst.biometricPreferences.edit().remove(key))
|
|
324
|
+
} catch (e: Exception) {
|
|
325
|
+
Log.d("NitroStorage", "Biometric storage unavailable: ${e.message}")
|
|
326
|
+
}
|
|
327
|
+
inst.invalidateSecureKeysCache()
|
|
328
|
+
}
|
|
276
329
|
}
|
|
277
330
|
|
|
278
331
|
@JvmStatic
|
|
279
332
|
fun deleteSecureBatch(keys: Array<String>) {
|
|
280
333
|
val inst = getInstanceOrThrow()
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
334
|
+
synchronized(inst) {
|
|
335
|
+
val editor = inst.encryptedPreferences.edit()
|
|
336
|
+
for (key in keys) {
|
|
337
|
+
editor.remove(key)
|
|
338
|
+
}
|
|
339
|
+
inst.applySecureEditor(editor)
|
|
340
|
+
try {
|
|
341
|
+
val biometricEditor = inst.biometricPreferences.edit()
|
|
342
|
+
for (key in keys) {
|
|
343
|
+
biometricEditor.remove(key)
|
|
344
|
+
}
|
|
345
|
+
inst.applySecureEditor(biometricEditor)
|
|
346
|
+
} catch (e: Exception) {
|
|
347
|
+
Log.d("NitroStorage", "Biometric storage unavailable: ${e.message}")
|
|
348
|
+
}
|
|
349
|
+
inst.invalidateSecureKeysCache()
|
|
286
350
|
}
|
|
287
|
-
inst.applySecureEditor(secureEditor)
|
|
288
|
-
inst.applySecureEditor(biometricEditor)
|
|
289
|
-
inst.invalidateSecureKeysCache()
|
|
290
351
|
}
|
|
291
352
|
|
|
292
353
|
@JvmStatic
|
|
293
354
|
fun hasSecure(key: String): Boolean {
|
|
294
355
|
val inst = getInstanceOrThrow()
|
|
295
|
-
|
|
356
|
+
val hasInEncrypted = inst.encryptedPreferences.contains(key)
|
|
357
|
+
val hasInBiometric = try {
|
|
358
|
+
inst.biometricPreferences.contains(key)
|
|
359
|
+
} catch (e: Exception) {
|
|
360
|
+
Log.d("NitroStorage", "Biometric storage unavailable: ${e.message}")
|
|
361
|
+
false
|
|
362
|
+
}
|
|
363
|
+
return hasInEncrypted || hasInBiometric
|
|
296
364
|
}
|
|
297
365
|
|
|
298
366
|
@JvmStatic
|
|
@@ -315,7 +383,11 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
315
383
|
fun clearSecure() {
|
|
316
384
|
val inst = getInstanceOrThrow()
|
|
317
385
|
inst.applySecureEditor(inst.encryptedPreferences.edit().clear())
|
|
318
|
-
|
|
386
|
+
try {
|
|
387
|
+
inst.applySecureEditor(inst.biometricPreferences.edit().clear())
|
|
388
|
+
} catch (e: Exception) {
|
|
389
|
+
Log.d("NitroStorage", "Biometric storage unavailable: ${e.message}")
|
|
390
|
+
}
|
|
319
391
|
inst.invalidateSecureKeysCache()
|
|
320
392
|
}
|
|
321
393
|
|
|
@@ -329,33 +401,56 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
329
401
|
@JvmStatic
|
|
330
402
|
fun setSecureBiometricWithLevel(key: String, value: String, @Suppress("UNUSED_PARAMETER") level: Int) {
|
|
331
403
|
val inst = getInstanceOrThrow()
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
404
|
+
try {
|
|
405
|
+
val editor = inst.biometricPreferences.edit().putString(key, value)
|
|
406
|
+
inst.applySecureEditor(editor)
|
|
407
|
+
inst.invalidateSecureKeysCache()
|
|
408
|
+
} catch (e: Exception) {
|
|
409
|
+
throw RuntimeException("NitroStorage: Biometric storage unavailable on this device", e)
|
|
410
|
+
}
|
|
335
411
|
}
|
|
336
412
|
|
|
337
413
|
@JvmStatic
|
|
338
414
|
fun getSecureBiometric(key: String): String? {
|
|
339
|
-
|
|
415
|
+
val inst = getInstanceOrThrow()
|
|
416
|
+
return try {
|
|
417
|
+
inst.getSecureSafe(inst.biometricPreferences, key)
|
|
418
|
+
} catch (e: Exception) {
|
|
419
|
+
Log.d("NitroStorage", "Biometric storage unavailable: ${e.message}")
|
|
420
|
+
null
|
|
421
|
+
}
|
|
340
422
|
}
|
|
341
423
|
|
|
342
424
|
@JvmStatic
|
|
343
425
|
fun deleteSecureBiometric(key: String) {
|
|
344
426
|
val inst = getInstanceOrThrow()
|
|
345
|
-
|
|
346
|
-
|
|
427
|
+
try {
|
|
428
|
+
inst.applySecureEditor(inst.biometricPreferences.edit().remove(key))
|
|
429
|
+
inst.invalidateSecureKeysCache()
|
|
430
|
+
} catch (e: Exception) {
|
|
431
|
+
Log.d("NitroStorage", "Biometric storage unavailable: ${e.message}")
|
|
432
|
+
}
|
|
347
433
|
}
|
|
348
434
|
|
|
349
435
|
@JvmStatic
|
|
350
436
|
fun hasSecureBiometric(key: String): Boolean {
|
|
351
|
-
return
|
|
437
|
+
return try {
|
|
438
|
+
getInstanceOrThrow().biometricPreferences.contains(key)
|
|
439
|
+
} catch (e: Exception) {
|
|
440
|
+
Log.d("NitroStorage", "Biometric storage unavailable: ${e.message}")
|
|
441
|
+
false
|
|
442
|
+
}
|
|
352
443
|
}
|
|
353
444
|
|
|
354
445
|
@JvmStatic
|
|
355
446
|
fun clearSecureBiometric() {
|
|
356
447
|
val inst = getInstanceOrThrow()
|
|
357
|
-
|
|
358
|
-
|
|
448
|
+
try {
|
|
449
|
+
inst.applySecureEditor(inst.biometricPreferences.edit().clear())
|
|
450
|
+
inst.invalidateSecureKeysCache()
|
|
451
|
+
} catch (e: Exception) {
|
|
452
|
+
Log.d("NitroStorage", "Biometric storage unavailable: ${e.message}")
|
|
453
|
+
}
|
|
359
454
|
}
|
|
360
455
|
}
|
|
361
456
|
}
|