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.
- package/README.md +432 -345
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +191 -3
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +21 -41
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +181 -29
- package/android/src/main/java/com/nitrostorage/NitroStoragePackage.kt +2 -2
- package/app.plugin.js +9 -7
- package/cpp/bindings/HybridStorage.cpp +239 -10
- package/cpp/bindings/HybridStorage.hpp +10 -0
- package/cpp/core/NativeStorageAdapter.hpp +22 -0
- package/ios/IOSStorageAdapterCpp.hpp +25 -0
- package/ios/IOSStorageAdapterCpp.mm +315 -33
- package/lib/commonjs/Storage.types.js +23 -1
- package/lib/commonjs/Storage.types.js.map +1 -1
- package/lib/commonjs/index.js +680 -68
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +801 -133
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +112 -0
- package/lib/commonjs/internal.js.map +1 -0
- package/lib/module/Storage.types.js +22 -0
- package/lib/module/Storage.types.js.map +1 -1
- package/lib/module/index.js +660 -71
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +766 -125
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +100 -0
- package/lib/module/internal.js.map +1 -0
- package/lib/typescript/Storage.nitro.d.ts +10 -0
- package/lib/typescript/Storage.nitro.d.ts.map +1 -1
- package/lib/typescript/Storage.types.d.ts +20 -0
- package/lib/typescript/Storage.types.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +68 -9
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +79 -13
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +21 -0
- package/lib/typescript/internal.d.ts.map +1 -0
- package/lib/typescript/migration.d.ts +2 -3
- package/lib/typescript/migration.d.ts.map +1 -1
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +10 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +10 -0
- package/package.json +22 -8
- package/src/Storage.nitro.ts +11 -2
- package/src/Storage.types.ts +22 -0
- package/src/index.ts +943 -84
- package/src/index.web.ts +1082 -137
- package/src/internal.ts +144 -0
- 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
|
-
|
|
50
|
-
static auto method = AndroidStorageAdapterJava::javaClassStatic()->getStaticMethod<
|
|
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
|
|
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
|
-
|
|
26
|
-
|
|
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
|
|
34
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
|
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
|
|
67
|
+
private fun clearCorruptedStorage(name: String, key: MasterKey) {
|
|
55
68
|
try {
|
|
56
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
Log.i("NitroStorage", "
|
|
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
|
-
|
|
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).
|
|
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
|
|
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()
|
|
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
|
|
129
|
-
getInstanceOrThrow()
|
|
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()
|
|
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.
|
|
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 :
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
21
|
-
if (!mainApplication) {
|
|
23
|
+
if (!addBiometricPermissions) {
|
|
22
24
|
return config;
|
|
23
25
|
}
|
|
24
26
|
|