react-native-nitro-storage 0.1.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +320 -391
  2. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +101 -0
  3. package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +6 -41
  4. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +125 -37
  5. package/app.plugin.js +9 -7
  6. package/cpp/bindings/HybridStorage.cpp +214 -19
  7. package/cpp/bindings/HybridStorage.hpp +1 -0
  8. package/cpp/core/NativeStorageAdapter.hpp +7 -0
  9. package/ios/IOSStorageAdapterCpp.hpp +6 -0
  10. package/ios/IOSStorageAdapterCpp.mm +90 -7
  11. package/lib/commonjs/index.js +537 -66
  12. package/lib/commonjs/index.js.map +1 -1
  13. package/lib/commonjs/index.web.js +558 -130
  14. package/lib/commonjs/index.web.js.map +1 -1
  15. package/lib/commonjs/internal.js +102 -0
  16. package/lib/commonjs/internal.js.map +1 -0
  17. package/lib/module/index.js +528 -67
  18. package/lib/module/index.js.map +1 -1
  19. package/lib/module/index.web.js +536 -122
  20. package/lib/module/index.web.js.map +1 -1
  21. package/lib/module/internal.js +92 -0
  22. package/lib/module/internal.js.map +1 -0
  23. package/lib/typescript/index.d.ts +42 -6
  24. package/lib/typescript/index.d.ts.map +1 -1
  25. package/lib/typescript/index.web.d.ts +45 -12
  26. package/lib/typescript/index.web.d.ts.map +1 -1
  27. package/lib/typescript/internal.d.ts +19 -0
  28. package/lib/typescript/internal.d.ts.map +1 -0
  29. package/lib/typescript/migration.d.ts +2 -3
  30. package/lib/typescript/migration.d.ts.map +1 -1
  31. package/nitrogen/generated/android/NitroStorage+autolinking.cmake +1 -1
  32. package/nitrogen/generated/android/NitroStorage+autolinking.gradle +1 -1
  33. package/nitrogen/generated/android/NitroStorageOnLoad.cpp +1 -1
  34. package/nitrogen/generated/android/NitroStorageOnLoad.hpp +1 -1
  35. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitrostorage/NitroStorageOnLoad.kt +1 -1
  36. package/nitrogen/generated/ios/NitroStorage+autolinking.rb +1 -1
  37. package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Bridge.cpp +1 -1
  38. package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Bridge.hpp +1 -1
  39. package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Umbrella.hpp +1 -1
  40. package/nitrogen/generated/ios/NitroStorageAutolinking.mm +1 -1
  41. package/nitrogen/generated/ios/NitroStorageAutolinking.swift +5 -1
  42. package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +1 -1
  43. package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +1 -1
  44. package/package.json +19 -8
  45. package/src/index.ts +734 -74
  46. package/src/index.web.ts +732 -128
  47. package/src/internal.ts +134 -0
  48. package/src/migration.ts +2 -2
@@ -3,6 +3,39 @@
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>> fromJavaStringArray(alias_ref<JavaStringArray> values) {
20
+ std::vector<std::optional<std::string>> parsedValues;
21
+ if (!values) {
22
+ return parsedValues;
23
+ }
24
+
25
+ const auto size = values->size();
26
+ parsedValues.reserve(size);
27
+ for (jsize i = 0; i < size; ++i) {
28
+ auto currentValue = values->getElement(i);
29
+ if (!currentValue) {
30
+ parsedValues.push_back(std::nullopt);
31
+ continue;
32
+ }
33
+ parsedValues.push_back(currentValue->toStdString());
34
+ }
35
+ return parsedValues;
36
+ }
37
+
38
+ } // namespace
6
39
 
7
40
  AndroidStorageAdapterCpp::AndroidStorageAdapterCpp(alias_ref<JObject> context) {
8
41
  if (!context) [[unlikely]] {
@@ -29,6 +62,40 @@ void AndroidStorageAdapterCpp::deleteDisk(const std::string& key) {
29
62
  method(AndroidStorageAdapterJava::javaClassStatic(), key);
30
63
  }
31
64
 
65
+ void AndroidStorageAdapterCpp::setDiskBatch(
66
+ const std::vector<std::string>& keys,
67
+ const std::vector<std::string>& values
68
+ ) {
69
+ auto javaKeys = toJavaStringArray(keys);
70
+ auto javaValues = toJavaStringArray(values);
71
+ static auto method =
72
+ AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
73
+ void(alias_ref<JavaStringArray>, alias_ref<JavaStringArray>)
74
+ >("setDiskBatch");
75
+ method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys, javaValues);
76
+ }
77
+
78
+ std::vector<std::optional<std::string>> AndroidStorageAdapterCpp::getDiskBatch(
79
+ const std::vector<std::string>& keys
80
+ ) {
81
+ auto javaKeys = toJavaStringArray(keys);
82
+ static auto method =
83
+ AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
84
+ local_ref<JavaStringArray>(alias_ref<JavaStringArray>)
85
+ >("getDiskBatch");
86
+ auto values = method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys);
87
+ return fromJavaStringArray(values);
88
+ }
89
+
90
+ void AndroidStorageAdapterCpp::deleteDiskBatch(const std::vector<std::string>& keys) {
91
+ auto javaKeys = toJavaStringArray(keys);
92
+ static auto method =
93
+ AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
94
+ void(alias_ref<JavaStringArray>)
95
+ >("deleteDiskBatch");
96
+ method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys);
97
+ }
98
+
32
99
  void AndroidStorageAdapterCpp::setSecure(const std::string& key, const std::string& value) {
33
100
  static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void(std::string, std::string)>("setSecure");
34
101
  method(AndroidStorageAdapterJava::javaClassStatic(), key, value);
@@ -46,6 +113,40 @@ void AndroidStorageAdapterCpp::deleteSecure(const std::string& key) {
46
113
  method(AndroidStorageAdapterJava::javaClassStatic(), key);
47
114
  }
48
115
 
116
+ void AndroidStorageAdapterCpp::setSecureBatch(
117
+ const std::vector<std::string>& keys,
118
+ const std::vector<std::string>& values
119
+ ) {
120
+ auto javaKeys = toJavaStringArray(keys);
121
+ auto javaValues = toJavaStringArray(values);
122
+ static auto method =
123
+ AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
124
+ void(alias_ref<JavaStringArray>, alias_ref<JavaStringArray>)
125
+ >("setSecureBatch");
126
+ method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys, javaValues);
127
+ }
128
+
129
+ std::vector<std::optional<std::string>> AndroidStorageAdapterCpp::getSecureBatch(
130
+ const std::vector<std::string>& keys
131
+ ) {
132
+ auto javaKeys = toJavaStringArray(keys);
133
+ static auto method =
134
+ AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
135
+ local_ref<JavaStringArray>(alias_ref<JavaStringArray>)
136
+ >("getSecureBatch");
137
+ auto values = method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys);
138
+ return fromJavaStringArray(values);
139
+ }
140
+
141
+ void AndroidStorageAdapterCpp::deleteSecureBatch(const std::vector<std::string>& keys) {
142
+ auto javaKeys = toJavaStringArray(keys);
143
+ static auto method =
144
+ AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
145
+ void(alias_ref<JavaStringArray>)
146
+ >("deleteSecureBatch");
147
+ method(AndroidStorageAdapterJava::javaClassStatic(), javaKeys);
148
+ }
149
+
49
150
  void AndroidStorageAdapterCpp::clearDisk() {
50
151
  static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<void()>("clearDisk");
51
152
  method(AndroidStorageAdapterJava::javaClassStatic());
@@ -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,10 +28,16 @@ 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
+ void setDiskBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) override;
32
+ std::vector<std::optional<std::string>> getDiskBatch(const std::vector<std::string>& keys) override;
33
+ void deleteDiskBatch(const std::vector<std::string>& keys) override;
72
34
 
73
35
  void setSecure(const std::string& key, const std::string& value) override;
74
36
  std::optional<std::string> getSecure(const std::string& key) override;
75
37
  void deleteSecure(const std::string& key) override;
38
+ void setSecureBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) override;
39
+ std::vector<std::optional<std::string>> getSecureBatch(const std::vector<std::string>& keys) override;
40
+ void deleteSecureBatch(const std::vector<std::string>& keys) override;
76
41
 
77
42
  void clearDisk() override;
78
43
  void clearSecure() override;
@@ -2,37 +2,83 @@ package com.nitrostorage
2
2
 
3
3
  import android.content.Context
4
4
  import android.content.SharedPreferences
5
+ import android.util.Log
5
6
  import androidx.security.crypto.EncryptedSharedPreferences
6
7
  import androidx.security.crypto.MasterKey
8
+ import java.security.KeyStore
9
+ import javax.crypto.AEADBadTagException
7
10
 
8
11
  class AndroidStorageAdapter private constructor(private val context: Context) {
9
12
  private val sharedPreferences: SharedPreferences =
10
13
  context.getSharedPreferences("NitroStorage", Context.MODE_PRIVATE)
11
-
12
- 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)
13
17
  .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
14
18
  .build()
15
19
 
16
- private val encryptedPreferences: SharedPreferences = try {
17
- EncryptedSharedPreferences.create(
18
- context,
19
- "NitroStorageSecure",
20
- masterKey,
21
- EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
22
- EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
23
- )
24
- } catch (e: Exception) {
25
- throw RuntimeException(
26
- "NitroStorage: Failed to initialize secure storage. " +
27
- "This may be due to corrupted encryption keys. " +
28
- "Try clearing app data or reinstalling the app.", e
29
- )
20
+ private val encryptedPreferences: SharedPreferences = initializeEncryptedPreferences()
21
+
22
+ private fun initializeEncryptedPreferences(): SharedPreferences {
23
+ return try {
24
+ EncryptedSharedPreferences.create(
25
+ context,
26
+ "NitroStorageSecure",
27
+ masterKey,
28
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
29
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
30
+ )
31
+ } catch (e: Exception) {
32
+ // Handle corrupted keystore keys by clearing and re-initializing
33
+ 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
38
+ EncryptedSharedPreferences.create(
39
+ context,
40
+ "NitroStorageSecure",
41
+ masterKey,
42
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
43
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
44
+ )
45
+ } else {
46
+ throw RuntimeException(
47
+ "NitroStorage: Failed to initialize secure storage. " +
48
+ "This may be due to corrupted encryption keys. " +
49
+ "Try clearing app data or reinstalling the app.", e
50
+ )
51
+ }
52
+ }
53
+ }
54
+
55
+ private fun clearCorruptedSecureStorage() {
56
+ try {
57
+ // Delete the encrypted shared preferences file
58
+ context.deleteSharedPreferences("NitroStorageSecure")
59
+
60
+ // Delete the master key from Android Keystore
61
+ val keyStore = KeyStore.getInstance("AndroidKeyStore")
62
+ keyStore.load(null)
63
+ keyStore.deleteEntry(masterKeyAlias)
64
+
65
+ Log.i("NitroStorage", "Successfully cleared corrupted secure storage")
66
+ } catch (e: Exception) {
67
+ Log.e("NitroStorage", "Failed to clear corrupted secure storage", e)
68
+ }
30
69
  }
31
70
 
32
71
  companion object {
33
72
  @Volatile
34
73
  private var instance: AndroidStorageAdapter? = null
35
74
 
75
+ private fun getInstanceOrThrow(): AndroidStorageAdapter {
76
+ return instance ?: throw IllegalStateException(
77
+ "NitroStorage not initialized. Call AndroidStorageAdapter.init(this) in your MainApplication.onCreate(), " +
78
+ "or add 'react-native-nitro-storage' to your Expo plugins array in app.json."
79
+ )
80
+ }
81
+
36
82
  @JvmStatic
37
83
  fun init(context: Context) {
38
84
  if (instance == null) {
@@ -46,59 +92,101 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
46
92
 
47
93
  @JvmStatic
48
94
  fun getContext(): Context {
49
- return instance?.context
50
- ?: throw IllegalStateException(
51
- "NitroStorage not initialized. Call AndroidStorageAdapter.init(this) in your MainApplication.onCreate(), " +
52
- "or add 'react-native-nitro-storage' to your Expo plugins array in app.json."
53
- )
95
+ return getInstanceOrThrow().context
54
96
  }
55
97
 
56
98
  @JvmStatic
57
99
  fun setDisk(key: String, value: String) {
58
- instance?.sharedPreferences?.edit()?.putString(key, value)?.apply()
59
- ?: throw IllegalStateException(
60
- "NitroStorage not initialized. Call AndroidStorageAdapter.init(this) in your MainApplication.onCreate(), " +
61
- "or add 'react-native-nitro-storage' to your Expo plugins array in app.json."
62
- )
100
+ getInstanceOrThrow().sharedPreferences.edit().putString(key, value).apply()
101
+ }
102
+
103
+ @JvmStatic
104
+ fun setDiskBatch(keys: Array<String>, values: Array<String>) {
105
+ val editor = getInstanceOrThrow().sharedPreferences.edit()
106
+ val count = minOf(keys.size, values.size)
107
+ for (index in 0 until count) {
108
+ editor.putString(keys[index], values[index])
109
+ }
110
+ editor.apply()
63
111
  }
64
112
 
65
113
  @JvmStatic
66
114
  fun getDisk(key: String): String? {
67
- return instance?.sharedPreferences?.getString(key, null)
115
+ return getInstanceOrThrow().sharedPreferences.getString(key, null)
116
+ }
117
+
118
+ @JvmStatic
119
+ fun getDiskBatch(keys: Array<String>): Array<String?> {
120
+ val prefs = getInstanceOrThrow().sharedPreferences
121
+ return Array(keys.size) { index ->
122
+ prefs.getString(keys[index], null)
123
+ }
68
124
  }
69
125
 
70
126
  @JvmStatic
71
127
  fun deleteDisk(key: String) {
72
- instance?.sharedPreferences?.edit()?.remove(key)?.apply()
128
+ getInstanceOrThrow().sharedPreferences.edit().remove(key).apply()
129
+ }
130
+
131
+ @JvmStatic
132
+ fun deleteDiskBatch(keys: Array<String>) {
133
+ val editor = getInstanceOrThrow().sharedPreferences.edit()
134
+ for (key in keys) {
135
+ editor.remove(key)
136
+ }
137
+ editor.apply()
73
138
  }
74
139
 
75
140
  @JvmStatic
76
141
  fun setSecure(key: String, value: String) {
77
- instance?.encryptedPreferences?.edit()?.putString(key, value)?.apply()
78
- ?: throw IllegalStateException(
79
- "NitroStorage not initialized. Call AndroidStorageAdapter.init(this) in your MainApplication.onCreate(), " +
80
- "or add 'react-native-nitro-storage' to your Expo plugins array in app.json."
81
- )
142
+ getInstanceOrThrow().encryptedPreferences.edit().putString(key, value).apply()
143
+ }
144
+
145
+ @JvmStatic
146
+ fun setSecureBatch(keys: Array<String>, values: Array<String>) {
147
+ val editor = getInstanceOrThrow().encryptedPreferences.edit()
148
+ val count = minOf(keys.size, values.size)
149
+ for (index in 0 until count) {
150
+ editor.putString(keys[index], values[index])
151
+ }
152
+ editor.apply()
82
153
  }
83
154
 
84
155
  @JvmStatic
85
156
  fun getSecure(key: String): String? {
86
- return instance?.encryptedPreferences?.getString(key, null)
157
+ return getInstanceOrThrow().encryptedPreferences.getString(key, null)
158
+ }
159
+
160
+ @JvmStatic
161
+ fun getSecureBatch(keys: Array<String>): Array<String?> {
162
+ val prefs = getInstanceOrThrow().encryptedPreferences
163
+ return Array(keys.size) { index ->
164
+ prefs.getString(keys[index], null)
165
+ }
87
166
  }
88
167
 
89
168
  @JvmStatic
90
169
  fun deleteSecure(key: String) {
91
- instance?.encryptedPreferences?.edit()?.remove(key)?.apply()
170
+ getInstanceOrThrow().encryptedPreferences.edit().remove(key).apply()
171
+ }
172
+
173
+ @JvmStatic
174
+ fun deleteSecureBatch(keys: Array<String>) {
175
+ val editor = getInstanceOrThrow().encryptedPreferences.edit()
176
+ for (key in keys) {
177
+ editor.remove(key)
178
+ }
179
+ editor.apply()
92
180
  }
93
181
 
94
182
  @JvmStatic
95
183
  fun clearDisk() {
96
- instance?.sharedPreferences?.edit()?.clear()?.apply()
184
+ getInstanceOrThrow().sharedPreferences.edit().clear().apply()
97
185
  }
98
186
 
99
187
  @JvmStatic
100
188
  fun clearSecure() {
101
- instance?.encryptedPreferences?.edit()?.clear()?.apply()
189
+ getInstanceOrThrow().encryptedPreferences.edit().clear().apply()
102
190
  }
103
191
  }
104
192
  }
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