react-native-nitro-storage 0.3.0 → 0.3.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.
Files changed (45) hide show
  1. package/README.md +414 -256
  2. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +98 -11
  3. package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +15 -0
  4. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +130 -33
  5. package/android/src/main/java/com/nitrostorage/NitroStoragePackage.kt +2 -2
  6. package/cpp/bindings/HybridStorage.cpp +121 -12
  7. package/cpp/bindings/HybridStorage.hpp +10 -0
  8. package/cpp/core/NativeStorageAdapter.hpp +15 -0
  9. package/ios/IOSStorageAdapterCpp.hpp +19 -0
  10. package/ios/IOSStorageAdapterCpp.mm +233 -32
  11. package/lib/commonjs/Storage.types.js +23 -1
  12. package/lib/commonjs/Storage.types.js.map +1 -1
  13. package/lib/commonjs/index.js +173 -32
  14. package/lib/commonjs/index.js.map +1 -1
  15. package/lib/commonjs/index.web.js +289 -49
  16. package/lib/commonjs/index.web.js.map +1 -1
  17. package/lib/commonjs/internal.js +10 -0
  18. package/lib/commonjs/internal.js.map +1 -1
  19. package/lib/module/Storage.types.js +22 -0
  20. package/lib/module/Storage.types.js.map +1 -1
  21. package/lib/module/index.js +163 -35
  22. package/lib/module/index.js.map +1 -1
  23. package/lib/module/index.web.js +278 -51
  24. package/lib/module/index.web.js.map +1 -1
  25. package/lib/module/internal.js +8 -0
  26. package/lib/module/internal.js.map +1 -1
  27. package/lib/typescript/Storage.nitro.d.ts +10 -0
  28. package/lib/typescript/Storage.nitro.d.ts.map +1 -1
  29. package/lib/typescript/Storage.types.d.ts +20 -0
  30. package/lib/typescript/Storage.types.d.ts.map +1 -1
  31. package/lib/typescript/index.d.ts +30 -7
  32. package/lib/typescript/index.d.ts.map +1 -1
  33. package/lib/typescript/index.web.d.ts +40 -7
  34. package/lib/typescript/index.web.d.ts.map +1 -1
  35. package/lib/typescript/internal.d.ts +2 -0
  36. package/lib/typescript/internal.d.ts.map +1 -1
  37. package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +10 -0
  38. package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +10 -0
  39. package/package.json +4 -1
  40. package/src/Storage.nitro.ts +11 -2
  41. package/src/Storage.types.ts +22 -0
  42. package/src/index.ts +270 -71
  43. package/src/index.web.ts +431 -90
  44. package/src/internal.ts +14 -4
  45. package/src/migration.ts +1 -1
@@ -16,13 +16,11 @@ local_ref<JavaStringArray> toJavaStringArray(const std::vector<std::string>& val
16
16
  return javaArray;
17
17
  }
18
18
 
19
- std::vector<std::optional<std::string>> fromJavaStringArray(alias_ref<JavaStringArray> values) {
19
+ std::vector<std::optional<std::string>> fromNullableJavaStringArray(alias_ref<JavaStringArray> values) {
20
20
  std::vector<std::optional<std::string>> parsedValues;
21
- if (!values) {
22
- return parsedValues;
23
- }
21
+ if (!values) return parsedValues;
24
22
 
25
- const auto size = values->size();
23
+ const jsize size = static_cast<jsize>(values->size());
26
24
  parsedValues.reserve(size);
27
25
  for (jsize i = 0; i < size; ++i) {
28
26
  auto currentValue = values->getElement(i);
@@ -35,6 +33,21 @@ std::vector<std::optional<std::string>> fromJavaStringArray(alias_ref<JavaString
35
33
  return parsedValues;
36
34
  }
37
35
 
36
+ std::vector<std::string> fromJavaStringArray(alias_ref<JavaStringArray> values) {
37
+ std::vector<std::string> result;
38
+ if (!values) return result;
39
+
40
+ const jsize size = static_cast<jsize>(values->size());
41
+ result.reserve(size);
42
+ for (jsize i = 0; i < size; ++i) {
43
+ auto currentValue = values->getElement(i);
44
+ if (currentValue) {
45
+ result.push_back(currentValue->toStdString());
46
+ }
47
+ }
48
+ return result;
49
+ }
50
+
38
51
  } // namespace
39
52
 
40
53
  AndroidStorageAdapterCpp::AndroidStorageAdapterCpp(alias_ref<JObject> context) {
@@ -45,6 +58,8 @@ AndroidStorageAdapterCpp::AndroidStorageAdapterCpp(alias_ref<JObject> context) {
45
58
 
46
59
  AndroidStorageAdapterCpp::~AndroidStorageAdapterCpp() = default;
47
60
 
61
+ // --- Disk ---
62
+
48
63
  void AndroidStorageAdapterCpp::setDisk(const std::string& key, const std::string& value) {
49
64
  static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void(std::string, std::string)>("setDisk");
50
65
  method(AndroidStorageAdapterJava::javaClassStatic(), key, value);
@@ -62,6 +77,24 @@ void AndroidStorageAdapterCpp::deleteDisk(const std::string& key) {
62
77
  method(AndroidStorageAdapterJava::javaClassStatic(), key);
63
78
  }
64
79
 
80
+ bool AndroidStorageAdapterCpp::hasDisk(const std::string& key) {
81
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<jboolean(std::string)>("hasDisk");
82
+ return method(AndroidStorageAdapterJava::javaClassStatic(), key);
83
+ }
84
+
85
+ std::vector<std::string> AndroidStorageAdapterCpp::getAllKeysDisk() {
86
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
87
+ local_ref<JavaStringArray>()
88
+ >("getAllKeysDisk");
89
+ auto keys = method(AndroidStorageAdapterJava::javaClassStatic());
90
+ return fromJavaStringArray(keys);
91
+ }
92
+
93
+ size_t AndroidStorageAdapterCpp::sizeDisk() {
94
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<jint()>("sizeDisk");
95
+ return static_cast<size_t>(method(AndroidStorageAdapterJava::javaClassStatic()));
96
+ }
97
+
65
98
  void AndroidStorageAdapterCpp::setDiskBatch(
66
99
  const std::vector<std::string>& keys,
67
100
  const std::vector<std::string>& values
@@ -84,7 +117,7 @@ std::vector<std::optional<std::string>> AndroidStorageAdapterCpp::getDiskBatch(
84
117
  local_ref<JavaStringArray>(alias_ref<JavaStringArray>)
85
118
  >("getDiskBatch");
86
119
  auto values = method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys);
87
- return fromJavaStringArray(values);
120
+ return fromNullableJavaStringArray(values);
88
121
  }
89
122
 
90
123
  void AndroidStorageAdapterCpp::deleteDiskBatch(const std::vector<std::string>& keys) {
@@ -96,6 +129,13 @@ void AndroidStorageAdapterCpp::deleteDiskBatch(const std::vector<std::string>& k
96
129
  method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys);
97
130
  }
98
131
 
132
+ void AndroidStorageAdapterCpp::clearDisk() {
133
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void()>("clearDisk");
134
+ method(AndroidStorageAdapterJava::javaClassStatic());
135
+ }
136
+
137
+ // --- Secure ---
138
+
99
139
  void AndroidStorageAdapterCpp::setSecure(const std::string& key, const std::string& value) {
100
140
  static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void(std::string, std::string)>("setSecure");
101
141
  method(AndroidStorageAdapterJava::javaClassStatic(), key, value);
@@ -113,6 +153,24 @@ void AndroidStorageAdapterCpp::deleteSecure(const std::string& key) {
113
153
  method(AndroidStorageAdapterJava::javaClassStatic(), key);
114
154
  }
115
155
 
156
+ bool AndroidStorageAdapterCpp::hasSecure(const std::string& key) {
157
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<jboolean(std::string)>("hasSecure");
158
+ return method(AndroidStorageAdapterJava::javaClassStatic(), key);
159
+ }
160
+
161
+ std::vector<std::string> AndroidStorageAdapterCpp::getAllKeysSecure() {
162
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
163
+ local_ref<JavaStringArray>()
164
+ >("getAllKeysSecure");
165
+ auto keys = method(AndroidStorageAdapterJava::javaClassStatic());
166
+ return fromJavaStringArray(keys);
167
+ }
168
+
169
+ size_t AndroidStorageAdapterCpp::sizeSecure() {
170
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<jint()>("sizeSecure");
171
+ return static_cast<size_t>(method(AndroidStorageAdapterJava::javaClassStatic()));
172
+ }
173
+
116
174
  void AndroidStorageAdapterCpp::setSecureBatch(
117
175
  const std::vector<std::string>& keys,
118
176
  const std::vector<std::string>& values
@@ -135,7 +193,7 @@ std::vector<std::optional<std::string>> AndroidStorageAdapterCpp::getSecureBatch
135
193
  local_ref<JavaStringArray>(alias_ref<JavaStringArray>)
136
194
  >("getSecureBatch");
137
195
  auto values = method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys);
138
- return fromJavaStringArray(values);
196
+ return fromNullableJavaStringArray(values);
139
197
  }
140
198
 
141
199
  void AndroidStorageAdapterCpp::deleteSecureBatch(const std::vector<std::string>& keys) {
@@ -147,13 +205,42 @@ void AndroidStorageAdapterCpp::deleteSecureBatch(const std::vector<std::string>&
147
205
  method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys);
148
206
  }
149
207
 
150
- void AndroidStorageAdapterCpp::clearDisk() {
151
- static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void()>("clearDisk");
208
+ void AndroidStorageAdapterCpp::clearSecure() {
209
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void()>("clearSecure");
152
210
  method(AndroidStorageAdapterJava::javaClassStatic());
153
211
  }
154
212
 
155
- void AndroidStorageAdapterCpp::clearSecure() {
156
- static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void()>("clearSecure");
213
+ // --- Config (no-ops on Android; access control / groups are iOS-specific) ---
214
+
215
+ void AndroidStorageAdapterCpp::setSecureAccessControl(int /*level*/) {}
216
+ void AndroidStorageAdapterCpp::setKeychainAccessGroup(const std::string& /*group*/) {}
217
+
218
+ // --- Biometric ---
219
+
220
+ void AndroidStorageAdapterCpp::setSecureBiometric(const std::string& key, const std::string& value) {
221
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void(std::string, std::string)>("setSecureBiometric");
222
+ method(AndroidStorageAdapterJava::javaClassStatic(), key, value);
223
+ }
224
+
225
+ std::optional<std::string> AndroidStorageAdapterCpp::getSecureBiometric(const std::string& key) {
226
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<jstring(std::string)>("getSecureBiometric");
227
+ auto result = method(AndroidStorageAdapterJava::javaClassStatic(), key);
228
+ if (!result) return std::nullopt;
229
+ return result->toStdString();
230
+ }
231
+
232
+ void AndroidStorageAdapterCpp::deleteSecureBiometric(const std::string& key) {
233
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void(std::string)>("deleteSecureBiometric");
234
+ method(AndroidStorageAdapterJava::javaClassStatic(), key);
235
+ }
236
+
237
+ bool AndroidStorageAdapterCpp::hasSecureBiometric(const std::string& key) {
238
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<jboolean(std::string)>("hasSecureBiometric");
239
+ return method(AndroidStorageAdapterJava::javaClassStatic(), key);
240
+ }
241
+
242
+ void AndroidStorageAdapterCpp::clearSecureBiometric() {
243
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void()>("clearSecureBiometric");
157
244
  method(AndroidStorageAdapterJava::javaClassStatic());
158
245
  }
159
246
 
@@ -28,6 +28,9 @@ public:
28
28
  void setDisk(const std::string& key, const std::string& value) override;
29
29
  std::optional<std::string> getDisk(const std::string& key) override;
30
30
  void deleteDisk(const std::string& key) override;
31
+ bool hasDisk(const std::string& key) override;
32
+ std::vector<std::string> getAllKeysDisk() override;
33
+ size_t sizeDisk() override;
31
34
  void setDiskBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) override;
32
35
  std::vector<std::optional<std::string>> getDiskBatch(const std::vector<std::string>& keys) override;
33
36
  void deleteDiskBatch(const std::vector<std::string>& keys) override;
@@ -35,12 +38,24 @@ public:
35
38
  void setSecure(const std::string& key, const std::string& value) override;
36
39
  std::optional<std::string> getSecure(const std::string& key) override;
37
40
  void deleteSecure(const std::string& key) override;
41
+ bool hasSecure(const std::string& key) override;
42
+ std::vector<std::string> getAllKeysSecure() override;
43
+ size_t sizeSecure() override;
38
44
  void setSecureBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) override;
39
45
  std::vector<std::optional<std::string>> getSecureBatch(const std::vector<std::string>& keys) override;
40
46
  void deleteSecureBatch(const std::vector<std::string>& keys) override;
41
47
 
42
48
  void clearDisk() override;
43
49
  void clearSecure() override;
50
+
51
+ void setSecureAccessControl(int level) override;
52
+ void setKeychainAccessGroup(const std::string& group) override;
53
+
54
+ void setSecureBiometric(const std::string& key, const std::string& value) override;
55
+ std::optional<std::string> getSecureBiometric(const std::string& key) override;
56
+ void deleteSecureBiometric(const std::string& key) override;
57
+ bool hasSecureBiometric(const std::string& key) override;
58
+ void clearSecureBiometric() override;
44
59
  };
45
60
 
46
61
  } // namespace NitroStorage
@@ -17,34 +17,46 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
17
17
  .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
18
18
  .build()
19
19
 
20
- private val encryptedPreferences: SharedPreferences = initializeEncryptedPreferences()
20
+ private val encryptedPreferences: SharedPreferences = initializeEncryptedPreferences("NitroStorageSecure", masterKey)
21
+
22
+ private val biometricMasterKeyAlias = "${context.packageName}.nitro_storage.biometric_key"
23
+
24
+ private val biometricPreferences: SharedPreferences by lazy {
25
+ try {
26
+ val bioKey = MasterKey.Builder(context, biometricMasterKeyAlias)
27
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
28
+ .setUserAuthenticationRequired(true, 30)
29
+ .build()
30
+ initializeEncryptedPreferences("NitroStorageBiometric", bioKey)
31
+ } catch (e: Exception) {
32
+ Log.w("NitroStorage", "Biometric storage unavailable, falling back to regular encrypted storage: ${e.message}")
33
+ encryptedPreferences
34
+ }
35
+ }
21
36
 
22
- private fun initializeEncryptedPreferences(): SharedPreferences {
37
+ private fun initializeEncryptedPreferences(name: String, key: MasterKey): SharedPreferences {
23
38
  return try {
24
39
  EncryptedSharedPreferences.create(
25
40
  context,
26
- "NitroStorageSecure",
27
- masterKey,
41
+ name,
42
+ key,
28
43
  EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
29
44
  EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
30
45
  )
31
46
  } catch (e: Exception) {
32
- // Handle corrupted keystore keys by clearing and re-initializing
33
47
  if (e is AEADBadTagException || e.cause is AEADBadTagException) {
34
- Log.w("NitroStorage", "Detected corrupted encryption keys, clearing secure storage...")
35
- clearCorruptedSecureStorage()
36
-
37
- // Retry initialization
48
+ Log.w("NitroStorage", "Detected corrupted encryption keys for $name, clearing...")
49
+ clearCorruptedStorage(name, key)
38
50
  EncryptedSharedPreferences.create(
39
51
  context,
40
- "NitroStorageSecure",
41
- masterKey,
52
+ name,
53
+ key,
42
54
  EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
43
55
  EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
44
56
  )
45
57
  } else {
46
58
  throw RuntimeException(
47
- "NitroStorage: Failed to initialize secure storage. " +
59
+ "NitroStorage: Failed to initialize $name. " +
48
60
  "This may be due to corrupted encryption keys. " +
49
61
  "Try clearing app data or reinstalling the app.", e
50
62
  )
@@ -52,19 +64,30 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
52
64
  }
53
65
  }
54
66
 
55
- private fun clearCorruptedSecureStorage() {
67
+ private fun clearCorruptedStorage(name: String, key: MasterKey) {
56
68
  try {
57
- // Delete the encrypted shared preferences file
58
- context.deleteSharedPreferences("NitroStorageSecure")
59
-
60
- // Delete the master key from Android Keystore
69
+ context.deleteSharedPreferences(name)
61
70
  val keyStore = KeyStore.getInstance("AndroidKeyStore")
62
71
  keyStore.load(null)
63
- keyStore.deleteEntry(masterKeyAlias)
64
-
65
- Log.i("NitroStorage", "Successfully cleared corrupted secure storage")
72
+ val alias = if (key === masterKey) masterKeyAlias else biometricMasterKeyAlias
73
+ keyStore.deleteEntry(alias)
74
+ Log.i("NitroStorage", "Cleared corrupted storage: $name")
66
75
  } catch (e: Exception) {
67
- Log.e("NitroStorage", "Failed to clear corrupted secure storage", e)
76
+ Log.e("NitroStorage", "Failed to clear corrupted storage: $name", e)
77
+ }
78
+ }
79
+
80
+ private fun getSecureSafe(prefs: SharedPreferences, key: String): String? {
81
+ return try {
82
+ prefs.getString(key, null)
83
+ } catch (e: Exception) {
84
+ if (e is AEADBadTagException || e.cause is AEADBadTagException) {
85
+ Log.w("NitroStorage", "Corrupt entry for key '$key', removing")
86
+ prefs.edit().remove(key).commit()
87
+ null
88
+ } else {
89
+ throw e
90
+ }
68
91
  }
69
92
  }
70
93
 
@@ -94,6 +117,8 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
94
117
  fun getContext(): Context {
95
118
  return getInstanceOrThrow().context
96
119
  }
120
+
121
+ // --- Disk ---
97
122
 
98
123
  @JvmStatic
99
124
  fun setDisk(key: String, value: String) {
@@ -136,10 +161,32 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
136
161
  }
137
162
  editor.apply()
138
163
  }
164
+
165
+ @JvmStatic
166
+ fun hasDisk(key: String): Boolean {
167
+ return getInstanceOrThrow().sharedPreferences.contains(key)
168
+ }
169
+
170
+ @JvmStatic
171
+ fun getAllKeysDisk(): Array<String> {
172
+ return getInstanceOrThrow().sharedPreferences.all.keys.toTypedArray()
173
+ }
174
+
175
+ @JvmStatic
176
+ fun sizeDisk(): Int {
177
+ return getInstanceOrThrow().sharedPreferences.all.size
178
+ }
179
+
180
+ @JvmStatic
181
+ fun clearDisk() {
182
+ getInstanceOrThrow().sharedPreferences.edit().clear().apply()
183
+ }
184
+
185
+ // --- Secure (uses commit for reliability) ---
139
186
 
140
187
  @JvmStatic
141
188
  fun setSecure(key: String, value: String) {
142
- getInstanceOrThrow().encryptedPreferences.edit().putString(key, value).apply()
189
+ getInstanceOrThrow().encryptedPreferences.edit().putString(key, value).commit()
143
190
  }
144
191
 
145
192
  @JvmStatic
@@ -149,44 +196,94 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
149
196
  for (index in 0 until count) {
150
197
  editor.putString(keys[index], values[index])
151
198
  }
152
- editor.apply()
199
+ editor.commit()
153
200
  }
154
201
 
155
202
  @JvmStatic
156
203
  fun getSecure(key: String): String? {
157
- return getInstanceOrThrow().encryptedPreferences.getString(key, null)
204
+ return getInstanceOrThrow().getSecureSafe(getInstanceOrThrow().encryptedPreferences, key)
158
205
  }
159
206
 
160
207
  @JvmStatic
161
208
  fun getSecureBatch(keys: Array<String>): Array<String?> {
162
- val prefs = getInstanceOrThrow().encryptedPreferences
209
+ val inst = getInstanceOrThrow()
163
210
  return Array(keys.size) { index ->
164
- prefs.getString(keys[index], null)
211
+ inst.getSecureSafe(inst.encryptedPreferences, keys[index])
165
212
  }
166
213
  }
167
214
 
168
215
  @JvmStatic
169
216
  fun deleteSecure(key: String) {
170
- getInstanceOrThrow().encryptedPreferences.edit().remove(key).apply()
217
+ val inst = getInstanceOrThrow()
218
+ inst.encryptedPreferences.edit().remove(key).commit()
219
+ inst.biometricPreferences.edit().remove(key).commit()
171
220
  }
172
221
 
173
222
  @JvmStatic
174
223
  fun deleteSecureBatch(keys: Array<String>) {
175
- val editor = getInstanceOrThrow().encryptedPreferences.edit()
224
+ val inst = getInstanceOrThrow()
225
+ val secureEditor = inst.encryptedPreferences.edit()
226
+ val biometricEditor = inst.biometricPreferences.edit()
176
227
  for (key in keys) {
177
- editor.remove(key)
228
+ secureEditor.remove(key)
229
+ biometricEditor.remove(key)
178
230
  }
179
- editor.apply()
231
+ secureEditor.commit()
232
+ biometricEditor.commit()
180
233
  }
181
234
 
182
235
  @JvmStatic
183
- fun clearDisk() {
184
- getInstanceOrThrow().sharedPreferences.edit().clear().apply()
236
+ fun hasSecure(key: String): Boolean {
237
+ val inst = getInstanceOrThrow()
238
+ return inst.encryptedPreferences.contains(key) || inst.biometricPreferences.contains(key)
239
+ }
240
+
241
+ @JvmStatic
242
+ fun getAllKeysSecure(): Array<String> {
243
+ val inst = getInstanceOrThrow()
244
+ val keys = linkedSetOf<String>()
245
+ keys.addAll(inst.encryptedPreferences.all.keys)
246
+ keys.addAll(inst.biometricPreferences.all.keys)
247
+ return keys.toTypedArray()
248
+ }
249
+
250
+ @JvmStatic
251
+ fun sizeSecure(): Int {
252
+ return getAllKeysSecure().size
185
253
  }
186
254
 
187
255
  @JvmStatic
188
256
  fun clearSecure() {
189
- getInstanceOrThrow().encryptedPreferences.edit().clear().apply()
257
+ val inst = getInstanceOrThrow()
258
+ inst.encryptedPreferences.edit().clear().commit()
259
+ inst.biometricPreferences.edit().clear().commit()
260
+ }
261
+
262
+ // --- Biometric (separate encrypted store, requires recent biometric auth on Android) ---
263
+
264
+ @JvmStatic
265
+ fun setSecureBiometric(key: String, value: String) {
266
+ getInstanceOrThrow().biometricPreferences.edit().putString(key, value).commit()
267
+ }
268
+
269
+ @JvmStatic
270
+ fun getSecureBiometric(key: String): String? {
271
+ return getInstanceOrThrow().getSecureSafe(getInstanceOrThrow().biometricPreferences, key)
272
+ }
273
+
274
+ @JvmStatic
275
+ fun deleteSecureBiometric(key: String) {
276
+ getInstanceOrThrow().biometricPreferences.edit().remove(key).commit()
277
+ }
278
+
279
+ @JvmStatic
280
+ fun hasSecureBiometric(key: String): Boolean {
281
+ return getInstanceOrThrow().biometricPreferences.contains(key)
282
+ }
283
+
284
+ @JvmStatic
285
+ fun clearSecureBiometric() {
286
+ getInstanceOrThrow().biometricPreferences.edit().clear().commit()
190
287
  }
191
288
  }
192
289
  }
@@ -1,13 +1,13 @@
1
1
  package com.nitrostorage
2
2
 
3
- import com.facebook.react.TurboReactPackage
3
+ import com.facebook.react.BaseReactPackage
4
4
  import com.facebook.react.bridge.NativeModule
5
5
  import com.facebook.react.bridge.ReactApplicationContext
6
6
  import com.facebook.react.module.model.ReactModuleInfo
7
7
  import com.facebook.react.module.model.ReactModuleInfoProvider
8
8
  import java.util.HashMap
9
9
 
10
- class NitroStoragePackage : TurboReactPackage() {
10
+ class NitroStoragePackage : BaseReactPackage() {
11
11
  override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12
12
  return null
13
13
  }
@@ -143,6 +143,65 @@ void HybridStorage::remove(const std::string& key, double scope) {
143
143
  notifyListeners(static_cast<int>(s), key, std::nullopt);
144
144
  }
145
145
 
146
+ bool HybridStorage::has(const std::string& key, double scope) {
147
+ Scope s = toScope(scope);
148
+
149
+ switch (s) {
150
+ case Scope::Memory: {
151
+ std::lock_guard<std::mutex> lock(memoryMutex_);
152
+ return memoryStore_.find(key) != memoryStore_.end();
153
+ }
154
+ case Scope::Disk:
155
+ ensureAdapter();
156
+ return nativeAdapter_->hasDisk(key);
157
+ case Scope::Secure:
158
+ ensureAdapter();
159
+ return nativeAdapter_->hasSecure(key);
160
+ }
161
+ return false;
162
+ }
163
+
164
+ std::vector<std::string> HybridStorage::getAllKeys(double scope) {
165
+ Scope s = toScope(scope);
166
+
167
+ switch (s) {
168
+ case Scope::Memory: {
169
+ std::lock_guard<std::mutex> lock(memoryMutex_);
170
+ std::vector<std::string> keys;
171
+ keys.reserve(memoryStore_.size());
172
+ for (const auto& pair : memoryStore_) {
173
+ keys.push_back(pair.first);
174
+ }
175
+ return keys;
176
+ }
177
+ case Scope::Disk:
178
+ ensureAdapter();
179
+ return nativeAdapter_->getAllKeysDisk();
180
+ case Scope::Secure:
181
+ ensureAdapter();
182
+ return nativeAdapter_->getAllKeysSecure();
183
+ }
184
+ return {};
185
+ }
186
+
187
+ double HybridStorage::size(double scope) {
188
+ Scope s = toScope(scope);
189
+
190
+ switch (s) {
191
+ case Scope::Memory: {
192
+ std::lock_guard<std::mutex> lock(memoryMutex_);
193
+ return static_cast<double>(memoryStore_.size());
194
+ }
195
+ case Scope::Disk:
196
+ ensureAdapter();
197
+ return static_cast<double>(nativeAdapter_->sizeDisk());
198
+ case Scope::Secure:
199
+ ensureAdapter();
200
+ return static_cast<double>(nativeAdapter_->sizeSecure());
201
+ }
202
+ return 0.0;
203
+ }
204
+
146
205
  std::function<void()> HybridStorage::addOnChange(
147
206
  double scope,
148
207
  const std::function<void(const std::string&, const std::optional<std::string>&)>& callback
@@ -244,7 +303,6 @@ void HybridStorage::setBatch(const std::vector<std::string>& keys, const std::ve
244
303
  }
245
304
  }
246
305
 
247
-
248
306
  std::vector<std::string> HybridStorage::getBatch(const std::vector<std::string>& keys, double scope) {
249
307
  std::vector<std::string> results;
250
308
  results.reserve(keys.size());
@@ -276,11 +334,7 @@ std::vector<std::string> HybridStorage::getBatch(const std::vector<std::string>&
276
334
  }
277
335
 
278
336
  for (const auto& value : values) {
279
- if (value.has_value()) {
280
- results.push_back(*value);
281
- } else {
282
- results.push_back(kBatchMissingSentinel);
283
- }
337
+ results.push_back(value.has_value() ? *value : std::string(kBatchMissingSentinel));
284
338
  }
285
339
  return results;
286
340
  }
@@ -296,11 +350,7 @@ std::vector<std::string> HybridStorage::getBatch(const std::vector<std::string>&
296
350
  }
297
351
 
298
352
  for (const auto& value : values) {
299
- if (value.has_value()) {
300
- results.push_back(*value);
301
- } else {
302
- results.push_back(kBatchMissingSentinel);
303
- }
353
+ results.push_back(value.has_value() ? *value : std::string(kBatchMissingSentinel));
304
354
  }
305
355
  return results;
306
356
  }
@@ -309,7 +359,6 @@ std::vector<std::string> HybridStorage::getBatch(const std::vector<std::string>&
309
359
  return results;
310
360
  }
311
361
 
312
-
313
362
  void HybridStorage::removeBatch(const std::vector<std::string>& keys, double scope) {
314
363
  Scope s = toScope(scope);
315
364
 
@@ -348,6 +397,66 @@ void HybridStorage::removeBatch(const std::vector<std::string>& keys, double sco
348
397
  }
349
398
  }
350
399
 
400
+ // --- Configuration ---
401
+
402
+ void HybridStorage::setSecureAccessControl(double level) {
403
+ ensureAdapter();
404
+ nativeAdapter_->setSecureAccessControl(static_cast<int>(level));
405
+ }
406
+
407
+ void HybridStorage::setKeychainAccessGroup(const std::string& group) {
408
+ ensureAdapter();
409
+ nativeAdapter_->setKeychainAccessGroup(group);
410
+ }
411
+
412
+ // --- Biometric ---
413
+
414
+ void HybridStorage::setSecureBiometric(const std::string& key, const std::string& value) {
415
+ ensureAdapter();
416
+ try {
417
+ nativeAdapter_->setSecureBiometric(key, value);
418
+ notifyListeners(static_cast<int>(Scope::Secure), key, value);
419
+ } catch (const std::exception& e) {
420
+ throw std::runtime_error(std::string("NitroStorage: Biometric set failed: ") + e.what());
421
+ }
422
+ }
423
+
424
+ std::optional<std::string> HybridStorage::getSecureBiometric(const std::string& key) {
425
+ ensureAdapter();
426
+ try {
427
+ return nativeAdapter_->getSecureBiometric(key);
428
+ } catch (const std::exception& e) {
429
+ throw std::runtime_error(std::string("NitroStorage: Biometric get failed: ") + e.what());
430
+ }
431
+ }
432
+
433
+ void HybridStorage::deleteSecureBiometric(const std::string& key) {
434
+ ensureAdapter();
435
+ try {
436
+ nativeAdapter_->deleteSecureBiometric(key);
437
+ notifyListeners(static_cast<int>(Scope::Secure), key, std::nullopt);
438
+ } catch (const std::exception& e) {
439
+ throw std::runtime_error(std::string("NitroStorage: Biometric delete failed: ") + e.what());
440
+ }
441
+ }
442
+
443
+ bool HybridStorage::hasSecureBiometric(const std::string& key) {
444
+ ensureAdapter();
445
+ return nativeAdapter_->hasSecureBiometric(key);
446
+ }
447
+
448
+ void HybridStorage::clearSecureBiometric() {
449
+ ensureAdapter();
450
+ try {
451
+ nativeAdapter_->clearSecureBiometric();
452
+ notifyListeners(static_cast<int>(Scope::Secure), "", std::nullopt);
453
+ } catch (const std::exception& e) {
454
+ throw std::runtime_error(std::string("NitroStorage: Biometric clear failed: ") + e.what());
455
+ }
456
+ }
457
+
458
+ // --- Internal ---
459
+
351
460
  void HybridStorage::notifyListeners(
352
461
  int scope,
353
462
  const std::string& key,
@@ -20,6 +20,9 @@ public:
20
20
  std::optional<std::string> get(const std::string& key, double scope) override;
21
21
  void remove(const std::string& key, double scope) override;
22
22
  void clear(double scope) override;
23
+ bool has(const std::string& key, double scope) override;
24
+ std::vector<std::string> getAllKeys(double scope) override;
25
+ double size(double scope) override;
23
26
  void setBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values, double scope) override;
24
27
  std::vector<std::string> getBatch(const std::vector<std::string>& keys, double scope) override;
25
28
  void removeBatch(const std::vector<std::string>& keys, double scope) override;
@@ -27,6 +30,13 @@ public:
27
30
  double scope,
28
31
  const std::function<void(const std::string&, const std::optional<std::string>&)>& callback
29
32
  ) override;
33
+ void setSecureAccessControl(double level) override;
34
+ void setKeychainAccessGroup(const std::string& group) override;
35
+ void setSecureBiometric(const std::string& key, const std::string& value) override;
36
+ std::optional<std::string> getSecureBiometric(const std::string& key) override;
37
+ void deleteSecureBiometric(const std::string& key) override;
38
+ bool hasSecureBiometric(const std::string& key) override;
39
+ void clearSecureBiometric() override;
30
40
 
31
41
  private:
32
42
  enum class Scope {