react-native-nitro-storage 0.1.4 → 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 (48) hide show
  1. package/README.md +432 -345
  2. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +191 -3
  3. package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +21 -41
  4. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +181 -29
  5. package/android/src/main/java/com/nitrostorage/NitroStoragePackage.kt +2 -2
  6. package/app.plugin.js +9 -7
  7. package/cpp/bindings/HybridStorage.cpp +239 -10
  8. package/cpp/bindings/HybridStorage.hpp +10 -0
  9. package/cpp/core/NativeStorageAdapter.hpp +22 -0
  10. package/ios/IOSStorageAdapterCpp.hpp +25 -0
  11. package/ios/IOSStorageAdapterCpp.mm +315 -33
  12. package/lib/commonjs/Storage.types.js +23 -1
  13. package/lib/commonjs/Storage.types.js.map +1 -1
  14. package/lib/commonjs/index.js +680 -68
  15. package/lib/commonjs/index.js.map +1 -1
  16. package/lib/commonjs/index.web.js +801 -133
  17. package/lib/commonjs/index.web.js.map +1 -1
  18. package/lib/commonjs/internal.js +112 -0
  19. package/lib/commonjs/internal.js.map +1 -0
  20. package/lib/module/Storage.types.js +22 -0
  21. package/lib/module/Storage.types.js.map +1 -1
  22. package/lib/module/index.js +660 -71
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/index.web.js +766 -125
  25. package/lib/module/index.web.js.map +1 -1
  26. package/lib/module/internal.js +100 -0
  27. package/lib/module/internal.js.map +1 -0
  28. package/lib/typescript/Storage.nitro.d.ts +10 -0
  29. package/lib/typescript/Storage.nitro.d.ts.map +1 -1
  30. package/lib/typescript/Storage.types.d.ts +20 -0
  31. package/lib/typescript/Storage.types.d.ts.map +1 -1
  32. package/lib/typescript/index.d.ts +68 -9
  33. package/lib/typescript/index.d.ts.map +1 -1
  34. package/lib/typescript/index.web.d.ts +79 -13
  35. package/lib/typescript/index.web.d.ts.map +1 -1
  36. package/lib/typescript/internal.d.ts +21 -0
  37. package/lib/typescript/internal.d.ts.map +1 -0
  38. package/lib/typescript/migration.d.ts +2 -3
  39. package/lib/typescript/migration.d.ts.map +1 -1
  40. package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +10 -0
  41. package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +10 -0
  42. package/package.json +22 -8
  43. package/src/Storage.nitro.ts +11 -2
  44. package/src/Storage.types.ts +22 -0
  45. package/src/index.ts +943 -84
  46. package/src/index.web.ts +1082 -137
  47. package/src/internal.ts +144 -0
  48. package/src/migration.ts +3 -3
@@ -3,6 +3,52 @@
3
3
  namespace NitroStorage {
4
4
 
5
5
  using namespace facebook::jni;
6
+ using JavaStringArray = JArrayClass<jstring>;
7
+
8
+ namespace {
9
+
10
+ local_ref<JavaStringArray> toJavaStringArray(const std::vector<std::string>& values) {
11
+ auto javaArray = JavaStringArray::newArray(static_cast<jsize>(values.size()));
12
+ for (size_t i = 0; i < values.size(); ++i) {
13
+ auto javaValue = make_jstring(values[i]);
14
+ javaArray->setElement(static_cast<jsize>(i), javaValue.get());
15
+ }
16
+ return javaArray;
17
+ }
18
+
19
+ std::vector<std::optional<std::string>> fromNullableJavaStringArray(alias_ref<JavaStringArray> values) {
20
+ std::vector<std::optional<std::string>> parsedValues;
21
+ if (!values) return parsedValues;
22
+
23
+ const jsize size = static_cast<jsize>(values->size());
24
+ parsedValues.reserve(size);
25
+ for (jsize i = 0; i < size; ++i) {
26
+ auto currentValue = values->getElement(i);
27
+ if (!currentValue) {
28
+ parsedValues.push_back(std::nullopt);
29
+ continue;
30
+ }
31
+ parsedValues.push_back(currentValue->toStdString());
32
+ }
33
+ return parsedValues;
34
+ }
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
+
51
+ } // namespace
6
52
 
7
53
  AndroidStorageAdapterCpp::AndroidStorageAdapterCpp(alias_ref<JObject> context) {
8
54
  if (!context) [[unlikely]] {
@@ -12,6 +58,8 @@ AndroidStorageAdapterCpp::AndroidStorageAdapterCpp(alias_ref<JObject> context) {
12
58
 
13
59
  AndroidStorageAdapterCpp::~AndroidStorageAdapterCpp() = default;
14
60
 
61
+ // --- Disk ---
62
+
15
63
  void AndroidStorageAdapterCpp::setDisk(const std::string& key, const std::string& value) {
16
64
  static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void(std::string, std::string)>("setDisk");
17
65
  method(AndroidStorageAdapterJava::javaClassStatic(), key, value);
@@ -29,6 +77,65 @@ void AndroidStorageAdapterCpp::deleteDisk(const std::string& key) {
29
77
  method(AndroidStorageAdapterJava::javaClassStatic(), key);
30
78
  }
31
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
+
98
+ void AndroidStorageAdapterCpp::setDiskBatch(
99
+ const std::vector<std::string>& keys,
100
+ const std::vector<std::string>& values
101
+ ) {
102
+ auto javaKeys = toJavaStringArray(keys);
103
+ auto javaValues = toJavaStringArray(values);
104
+ static auto method =
105
+ AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
106
+ void(alias_ref<JavaStringArray>, alias_ref<JavaStringArray>)
107
+ >("setDiskBatch");
108
+ method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys, javaValues);
109
+ }
110
+
111
+ std::vector<std::optional<std::string>> AndroidStorageAdapterCpp::getDiskBatch(
112
+ const std::vector<std::string>& keys
113
+ ) {
114
+ auto javaKeys = toJavaStringArray(keys);
115
+ static auto method =
116
+ AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
117
+ local_ref<JavaStringArray>(alias_ref<JavaStringArray>)
118
+ >("getDiskBatch");
119
+ auto values = method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys);
120
+ return fromNullableJavaStringArray(values);
121
+ }
122
+
123
+ void AndroidStorageAdapterCpp::deleteDiskBatch(const std::vector<std::string>& keys) {
124
+ auto javaKeys = toJavaStringArray(keys);
125
+ static auto method =
126
+ AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
127
+ void(alias_ref<JavaStringArray>)
128
+ >("deleteDiskBatch");
129
+ method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys);
130
+ }
131
+
132
+ void AndroidStorageAdapterCpp::clearDisk() {
133
+ static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void()>("clearDisk");
134
+ method(AndroidStorageAdapterJava::javaClassStatic());
135
+ }
136
+
137
+ // --- Secure ---
138
+
32
139
  void AndroidStorageAdapterCpp::setSecure(const std::string& key, const std::string& value) {
33
140
  static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void(std::string, std::string)>("setSecure");
34
141
  method(AndroidStorageAdapterJava::javaClassStatic(), key, value);
@@ -46,9 +153,56 @@ void AndroidStorageAdapterCpp::deleteSecure(const std::string& key) {
46
153
  method(AndroidStorageAdapterJava::javaClassStatic(), key);
47
154
  }
48
155
 
49
- void AndroidStorageAdapterCpp::clearDisk() {
50
- static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void()>("clearDisk");
51
- method(AndroidStorageAdapterJava::javaClassStatic());
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
+
174
+ void AndroidStorageAdapterCpp::setSecureBatch(
175
+ const std::vector<std::string>& keys,
176
+ const std::vector<std::string>& values
177
+ ) {
178
+ auto javaKeys = toJavaStringArray(keys);
179
+ auto javaValues = toJavaStringArray(values);
180
+ static auto method =
181
+ AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
182
+ void(alias_ref<JavaStringArray>, alias_ref<JavaStringArray>)
183
+ >("setSecureBatch");
184
+ method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys, javaValues);
185
+ }
186
+
187
+ std::vector<std::optional<std::string>> AndroidStorageAdapterCpp::getSecureBatch(
188
+ const std::vector<std::string>& keys
189
+ ) {
190
+ auto javaKeys = toJavaStringArray(keys);
191
+ static auto method =
192
+ AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
193
+ local_ref<JavaStringArray>(alias_ref<JavaStringArray>)
194
+ >("getSecureBatch");
195
+ auto values = method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys);
196
+ return fromNullableJavaStringArray(values);
197
+ }
198
+
199
+ void AndroidStorageAdapterCpp::deleteSecureBatch(const std::vector<std::string>& keys) {
200
+ auto javaKeys = toJavaStringArray(keys);
201
+ static auto method =
202
+ AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
203
+ void(alias_ref<JavaStringArray>)
204
+ >("deleteSecureBatch");
205
+ method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys);
52
206
  }
53
207
 
54
208
  void AndroidStorageAdapterCpp::clearSecure() {
@@ -56,4 +210,38 @@ void AndroidStorageAdapterCpp::clearSecure() {
56
210
  method(AndroidStorageAdapterJava::javaClassStatic());
57
211
  }
58
212
 
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");
244
+ method(AndroidStorageAdapterJava::javaClassStatic());
245
+ }
246
+
59
247
  } // namespace NitroStorage
@@ -18,47 +18,6 @@ struct AndroidStorageAdapterJava : facebook::jni::JavaClass<AndroidStorageAdapte
18
18
  return method(javaClassStatic());
19
19
  }
20
20
 
21
- void setDisk(std::string key, std::string value) {
22
- static auto method = javaClassStatic()->getMethod<void(std::string, std::string)>("setDisk");
23
- method(self(), key, value);
24
- }
25
-
26
- std::string getDisk(std::string key) {
27
- static auto method = javaClassStatic()->getMethod<jstring(std::string)>("getDisk");
28
- auto result = method(self(), key);
29
- return result ? result->toStdString() : "";
30
- }
31
-
32
- void deleteDisk(std::string key) {
33
- static auto method = javaClassStatic()->getMethod<void(std::string)>("deleteDisk");
34
- method(self(), key);
35
- }
36
-
37
- void setSecure(std::string key, std::string value) {
38
- static auto method = javaClassStatic()->getMethod<void(std::string, std::string)>("setSecure");
39
- method(self(), key, value);
40
- }
41
-
42
- std::string getSecure(std::string key) {
43
- static auto method = javaClassStatic()->getMethod<jstring(std::string)>("getSecure");
44
- auto result = method(self(), key);
45
- return result ? result->toStdString() : "";
46
- }
47
-
48
- void deleteSecure(std::string key) {
49
- static auto method = javaClassStatic()->getMethod<void(std::string)>("deleteSecure");
50
- method(self(), key);
51
- }
52
-
53
- void clearDisk() {
54
- static auto method = javaClassStatic()->getStaticMethod<void()>("clearDisk");
55
- method(javaClassStatic());
56
- }
57
-
58
- void clearSecure() {
59
- static auto method = javaClassStatic()->getStaticMethod<void()>("clearSecure");
60
- method(javaClassStatic());
61
- }
62
21
  };
63
22
 
64
23
  class AndroidStorageAdapterCpp : public NativeStorageAdapter {
@@ -69,13 +28,34 @@ public:
69
28
  void setDisk(const std::string& key, const std::string& value) override;
70
29
  std::optional<std::string> getDisk(const std::string& key) override;
71
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;
34
+ void setDiskBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) override;
35
+ std::vector<std::optional<std::string>> getDiskBatch(const std::vector<std::string>& keys) override;
36
+ void deleteDiskBatch(const std::vector<std::string>& keys) override;
72
37
 
73
38
  void setSecure(const std::string& key, const std::string& value) override;
74
39
  std::optional<std::string> getSecure(const std::string& key) override;
75
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;
44
+ void setSecureBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) override;
45
+ std::vector<std::optional<std::string>> getSecureBatch(const std::vector<std::string>& keys) override;
46
+ void deleteSecureBatch(const std::vector<std::string>& keys) override;
76
47
 
77
48
  void clearDisk() override;
78
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;
79
59
  };
80
60
 
81
61
  } // namespace NitroStorage
@@ -11,39 +11,52 @@ import javax.crypto.AEADBadTagException
11
11
  class AndroidStorageAdapter private constructor(private val context: Context) {
12
12
  private val sharedPreferences: SharedPreferences =
13
13
  context.getSharedPreferences("NitroStorage", Context.MODE_PRIVATE)
14
-
15
- private val masterKey: MasterKey = MasterKey.Builder(context)
14
+
15
+ private val masterKeyAlias = "${context.packageName}.nitro_storage.master_key"
16
+ private val masterKey: MasterKey = MasterKey.Builder(context, masterKeyAlias)
16
17
  .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
17
18
  .build()
18
19
 
19
- 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
+ }
20
36
 
21
- private fun initializeEncryptedPreferences(): SharedPreferences {
37
+ private fun initializeEncryptedPreferences(name: String, key: MasterKey): SharedPreferences {
22
38
  return try {
23
39
  EncryptedSharedPreferences.create(
24
40
  context,
25
- "NitroStorageSecure",
26
- masterKey,
41
+ name,
42
+ key,
27
43
  EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
28
44
  EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
29
45
  )
30
46
  } catch (e: Exception) {
31
- // Handle corrupted keystore keys by clearing and re-initializing
32
47
  if (e is AEADBadTagException || e.cause is AEADBadTagException) {
33
- Log.w("NitroStorage", "Detected corrupted encryption keys, clearing secure storage...")
34
- clearCorruptedSecureStorage()
35
-
36
- // Retry initialization
48
+ Log.w("NitroStorage", "Detected corrupted encryption keys for $name, clearing...")
49
+ clearCorruptedStorage(name, key)
37
50
  EncryptedSharedPreferences.create(
38
51
  context,
39
- "NitroStorageSecure",
40
- masterKey,
52
+ name,
53
+ key,
41
54
  EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
42
55
  EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
43
56
  )
44
57
  } else {
45
58
  throw RuntimeException(
46
- "NitroStorage: Failed to initialize secure storage. " +
59
+ "NitroStorage: Failed to initialize $name. " +
47
60
  "This may be due to corrupted encryption keys. " +
48
61
  "Try clearing app data or reinstalling the app.", e
49
62
  )
@@ -51,19 +64,30 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
51
64
  }
52
65
  }
53
66
 
54
- private fun clearCorruptedSecureStorage() {
67
+ private fun clearCorruptedStorage(name: String, key: MasterKey) {
55
68
  try {
56
- // Delete the encrypted shared preferences file
57
- context.deleteSharedPreferences("NitroStorageSecure")
58
-
59
- // Delete the master key from Android Keystore
69
+ context.deleteSharedPreferences(name)
60
70
  val keyStore = KeyStore.getInstance("AndroidKeyStore")
61
71
  keyStore.load(null)
62
- keyStore.deleteEntry(MasterKey.DEFAULT_MASTER_KEY_ALIAS)
63
-
64
- 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")
75
+ } catch (e: Exception) {
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)
65
83
  } catch (e: Exception) {
66
- Log.e("NitroStorage", "Failed to clear corrupted secure storage", e)
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
+ }
67
91
  }
68
92
  }
69
93
 
@@ -93,45 +117,173 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
93
117
  fun getContext(): Context {
94
118
  return getInstanceOrThrow().context
95
119
  }
120
+
121
+ // --- Disk ---
96
122
 
97
123
  @JvmStatic
98
124
  fun setDisk(key: String, value: String) {
99
125
  getInstanceOrThrow().sharedPreferences.edit().putString(key, value).apply()
100
126
  }
127
+
128
+ @JvmStatic
129
+ fun setDiskBatch(keys: Array<String>, values: Array<String>) {
130
+ val editor = getInstanceOrThrow().sharedPreferences.edit()
131
+ val count = minOf(keys.size, values.size)
132
+ for (index in 0 until count) {
133
+ editor.putString(keys[index], values[index])
134
+ }
135
+ editor.apply()
136
+ }
101
137
 
102
138
  @JvmStatic
103
139
  fun getDisk(key: String): String? {
104
140
  return getInstanceOrThrow().sharedPreferences.getString(key, null)
105
141
  }
142
+
143
+ @JvmStatic
144
+ fun getDiskBatch(keys: Array<String>): Array<String?> {
145
+ val prefs = getInstanceOrThrow().sharedPreferences
146
+ return Array(keys.size) { index ->
147
+ prefs.getString(keys[index], null)
148
+ }
149
+ }
106
150
 
107
151
  @JvmStatic
108
152
  fun deleteDisk(key: String) {
109
153
  getInstanceOrThrow().sharedPreferences.edit().remove(key).apply()
110
154
  }
155
+
156
+ @JvmStatic
157
+ fun deleteDiskBatch(keys: Array<String>) {
158
+ val editor = getInstanceOrThrow().sharedPreferences.edit()
159
+ for (key in keys) {
160
+ editor.remove(key)
161
+ }
162
+ editor.apply()
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) ---
111
186
 
112
187
  @JvmStatic
113
188
  fun setSecure(key: String, value: String) {
114
- getInstanceOrThrow().encryptedPreferences.edit().putString(key, value).apply()
189
+ getInstanceOrThrow().encryptedPreferences.edit().putString(key, value).commit()
190
+ }
191
+
192
+ @JvmStatic
193
+ fun setSecureBatch(keys: Array<String>, values: Array<String>) {
194
+ val editor = getInstanceOrThrow().encryptedPreferences.edit()
195
+ val count = minOf(keys.size, values.size)
196
+ for (index in 0 until count) {
197
+ editor.putString(keys[index], values[index])
198
+ }
199
+ editor.commit()
115
200
  }
116
201
 
117
202
  @JvmStatic
118
203
  fun getSecure(key: String): String? {
119
- return getInstanceOrThrow().encryptedPreferences.getString(key, null)
204
+ return getInstanceOrThrow().getSecureSafe(getInstanceOrThrow().encryptedPreferences, key)
205
+ }
206
+
207
+ @JvmStatic
208
+ fun getSecureBatch(keys: Array<String>): Array<String?> {
209
+ val inst = getInstanceOrThrow()
210
+ return Array(keys.size) { index ->
211
+ inst.getSecureSafe(inst.encryptedPreferences, keys[index])
212
+ }
120
213
  }
121
214
 
122
215
  @JvmStatic
123
216
  fun deleteSecure(key: String) {
124
- getInstanceOrThrow().encryptedPreferences.edit().remove(key).apply()
217
+ val inst = getInstanceOrThrow()
218
+ inst.encryptedPreferences.edit().remove(key).commit()
219
+ inst.biometricPreferences.edit().remove(key).commit()
125
220
  }
126
221
 
127
222
  @JvmStatic
128
- fun clearDisk() {
129
- getInstanceOrThrow().sharedPreferences.edit().clear().apply()
223
+ fun deleteSecureBatch(keys: Array<String>) {
224
+ val inst = getInstanceOrThrow()
225
+ val secureEditor = inst.encryptedPreferences.edit()
226
+ val biometricEditor = inst.biometricPreferences.edit()
227
+ for (key in keys) {
228
+ secureEditor.remove(key)
229
+ biometricEditor.remove(key)
230
+ }
231
+ secureEditor.commit()
232
+ biometricEditor.commit()
233
+ }
234
+
235
+ @JvmStatic
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
130
253
  }
131
254
 
132
255
  @JvmStatic
133
256
  fun clearSecure() {
134
- 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()
135
287
  }
136
288
  }
137
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
  }
package/app.plugin.js CHANGED
@@ -6,19 +6,21 @@ const {
6
6
  } = require("@expo/config-plugins");
7
7
 
8
8
  const withNitroStorage = (config, props = {}) => {
9
- const {
10
- faceIDPermission = "Allow $(PRODUCT_NAME) to use Face ID for secure authentication",
11
- } = props;
9
+ const defaultFaceIDPermission =
10
+ "Allow $(PRODUCT_NAME) to use Face ID for secure authentication";
11
+ const { faceIDPermission, addBiometricPermissions = false } = props;
12
12
 
13
13
  config = withInfoPlist(config, (config) => {
14
- config.modResults.NSFaceIDUsageDescription =
15
- faceIDPermission || config.modResults.NSFaceIDUsageDescription;
14
+ if (typeof faceIDPermission === "string" && faceIDPermission.trim() !== "") {
15
+ config.modResults.NSFaceIDUsageDescription = faceIDPermission;
16
+ } else if (!config.modResults.NSFaceIDUsageDescription) {
17
+ config.modResults.NSFaceIDUsageDescription = defaultFaceIDPermission;
18
+ }
16
19
  return config;
17
20
  });
18
21
 
19
22
  config = withAndroidManifest(config, (config) => {
20
- const mainApplication = config.modResults.manifest.application?.[0];
21
- if (!mainApplication) {
23
+ if (!addBiometricPermissions) {
22
24
  return config;
23
25
  }
24
26