react-native-nitro-storage 0.3.1 → 0.4.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.
- package/README.md +334 -34
- package/android/CMakeLists.txt +2 -0
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +26 -2
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +4 -0
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +90 -18
- package/cpp/bindings/HybridStorage.cpp +214 -23
- package/cpp/bindings/HybridStorage.hpp +31 -3
- package/cpp/core/NativeStorageAdapter.hpp +4 -0
- package/ios/IOSStorageAdapterCpp.hpp +17 -0
- package/ios/IOSStorageAdapterCpp.mm +140 -10
- package/lib/commonjs/index.js +555 -288
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +750 -309
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +25 -0
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/commonjs/storage-hooks.js +36 -0
- package/lib/commonjs/storage-hooks.js.map +1 -0
- package/lib/module/index.js +537 -287
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +732 -308
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +24 -0
- package/lib/module/internal.js.map +1 -1
- package/lib/module/storage-hooks.js +30 -0
- package/lib/module/storage-hooks.js.map +1 -0
- package/lib/typescript/Storage.nitro.d.ts +4 -0
- package/lib/typescript/Storage.nitro.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +41 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +45 -4
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +1 -0
- package/lib/typescript/internal.d.ts.map +1 -1
- package/lib/typescript/storage-hooks.d.ts +10 -0
- package/lib/typescript/storage-hooks.d.ts.map +1 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +4 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +4 -0
- package/package.json +5 -3
- package/src/Storage.nitro.ts +4 -0
- package/src/index.ts +704 -324
- package/src/index.web.ts +929 -346
- package/src/internal.ts +28 -0
- package/src/storage-hooks.ts +48 -0
|
@@ -33,6 +33,12 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
33
33
|
encryptedPreferences
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
+
|
|
37
|
+
@Volatile
|
|
38
|
+
private var secureWritesAsync = false
|
|
39
|
+
|
|
40
|
+
@Volatile
|
|
41
|
+
private var secureKeysCache: Array<String>? = null
|
|
36
42
|
|
|
37
43
|
private fun initializeEncryptedPreferences(name: String, key: MasterKey): SharedPreferences {
|
|
38
44
|
return try {
|
|
@@ -90,6 +96,38 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
90
96
|
}
|
|
91
97
|
}
|
|
92
98
|
}
|
|
99
|
+
|
|
100
|
+
private fun applySecureEditor(editor: SharedPreferences.Editor) {
|
|
101
|
+
if (secureWritesAsync) {
|
|
102
|
+
editor.apply()
|
|
103
|
+
} else {
|
|
104
|
+
editor.commit()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private fun invalidateSecureKeysCache() {
|
|
109
|
+
secureKeysCache = null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private fun getSecureKeysCached(): Array<String> {
|
|
113
|
+
val cached = secureKeysCache
|
|
114
|
+
if (cached != null) {
|
|
115
|
+
return cached
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
synchronized(this) {
|
|
119
|
+
val existing = secureKeysCache
|
|
120
|
+
if (existing != null) {
|
|
121
|
+
return existing
|
|
122
|
+
}
|
|
123
|
+
val keys = linkedSetOf<String>()
|
|
124
|
+
keys.addAll(encryptedPreferences.all.keys)
|
|
125
|
+
keys.addAll(biometricPreferences.all.keys)
|
|
126
|
+
val built = keys.toTypedArray()
|
|
127
|
+
secureKeysCache = built
|
|
128
|
+
return built
|
|
129
|
+
}
|
|
130
|
+
}
|
|
93
131
|
|
|
94
132
|
companion object {
|
|
95
133
|
@Volatile
|
|
@@ -118,6 +156,11 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
118
156
|
return getInstanceOrThrow().context
|
|
119
157
|
}
|
|
120
158
|
|
|
159
|
+
@JvmStatic
|
|
160
|
+
fun setSecureWritesAsync(enabled: Boolean) {
|
|
161
|
+
getInstanceOrThrow().secureWritesAsync = enabled
|
|
162
|
+
}
|
|
163
|
+
|
|
121
164
|
// --- Disk ---
|
|
122
165
|
|
|
123
166
|
@JvmStatic
|
|
@@ -172,6 +215,13 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
172
215
|
return getInstanceOrThrow().sharedPreferences.all.keys.toTypedArray()
|
|
173
216
|
}
|
|
174
217
|
|
|
218
|
+
@JvmStatic
|
|
219
|
+
fun getKeysByPrefixDisk(prefix: String): Array<String> {
|
|
220
|
+
return getInstanceOrThrow().sharedPreferences.all.keys
|
|
221
|
+
.filter { it.startsWith(prefix) }
|
|
222
|
+
.toTypedArray()
|
|
223
|
+
}
|
|
224
|
+
|
|
175
225
|
@JvmStatic
|
|
176
226
|
fun sizeDisk(): Int {
|
|
177
227
|
return getInstanceOrThrow().sharedPreferences.all.size
|
|
@@ -182,21 +232,26 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
182
232
|
getInstanceOrThrow().sharedPreferences.edit().clear().apply()
|
|
183
233
|
}
|
|
184
234
|
|
|
185
|
-
// --- Secure (
|
|
235
|
+
// --- Secure (sync commit by default, async apply when enabled) ---
|
|
186
236
|
|
|
187
237
|
@JvmStatic
|
|
188
238
|
fun setSecure(key: String, value: String) {
|
|
189
|
-
getInstanceOrThrow()
|
|
239
|
+
val inst = getInstanceOrThrow()
|
|
240
|
+
val editor = inst.encryptedPreferences.edit().putString(key, value)
|
|
241
|
+
inst.applySecureEditor(editor)
|
|
242
|
+
inst.invalidateSecureKeysCache()
|
|
190
243
|
}
|
|
191
244
|
|
|
192
245
|
@JvmStatic
|
|
193
246
|
fun setSecureBatch(keys: Array<String>, values: Array<String>) {
|
|
194
|
-
val
|
|
247
|
+
val inst = getInstanceOrThrow()
|
|
248
|
+
val editor = inst.encryptedPreferences.edit()
|
|
195
249
|
val count = minOf(keys.size, values.size)
|
|
196
250
|
for (index in 0 until count) {
|
|
197
251
|
editor.putString(keys[index], values[index])
|
|
198
252
|
}
|
|
199
|
-
|
|
253
|
+
inst.applySecureEditor(editor)
|
|
254
|
+
inst.invalidateSecureKeysCache()
|
|
200
255
|
}
|
|
201
256
|
|
|
202
257
|
@JvmStatic
|
|
@@ -215,8 +270,9 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
215
270
|
@JvmStatic
|
|
216
271
|
fun deleteSecure(key: String) {
|
|
217
272
|
val inst = getInstanceOrThrow()
|
|
218
|
-
inst.encryptedPreferences.edit().remove(key)
|
|
219
|
-
inst.biometricPreferences.edit().remove(key)
|
|
273
|
+
inst.applySecureEditor(inst.encryptedPreferences.edit().remove(key))
|
|
274
|
+
inst.applySecureEditor(inst.biometricPreferences.edit().remove(key))
|
|
275
|
+
inst.invalidateSecureKeysCache()
|
|
220
276
|
}
|
|
221
277
|
|
|
222
278
|
@JvmStatic
|
|
@@ -228,8 +284,9 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
228
284
|
secureEditor.remove(key)
|
|
229
285
|
biometricEditor.remove(key)
|
|
230
286
|
}
|
|
231
|
-
|
|
232
|
-
|
|
287
|
+
inst.applySecureEditor(secureEditor)
|
|
288
|
+
inst.applySecureEditor(biometricEditor)
|
|
289
|
+
inst.invalidateSecureKeysCache()
|
|
233
290
|
}
|
|
234
291
|
|
|
235
292
|
@JvmStatic
|
|
@@ -241,29 +298,40 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
241
298
|
@JvmStatic
|
|
242
299
|
fun getAllKeysSecure(): Array<String> {
|
|
243
300
|
val inst = getInstanceOrThrow()
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
301
|
+
return inst.getSecureKeysCached()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
@JvmStatic
|
|
305
|
+
fun getKeysByPrefixSecure(prefix: String): Array<String> {
|
|
306
|
+
return getAllKeysSecure().filter { it.startsWith(prefix) }.toTypedArray()
|
|
248
307
|
}
|
|
249
308
|
|
|
250
309
|
@JvmStatic
|
|
251
310
|
fun sizeSecure(): Int {
|
|
252
|
-
return
|
|
311
|
+
return getInstanceOrThrow().getSecureKeysCached().size
|
|
253
312
|
}
|
|
254
313
|
|
|
255
314
|
@JvmStatic
|
|
256
315
|
fun clearSecure() {
|
|
257
316
|
val inst = getInstanceOrThrow()
|
|
258
|
-
inst.encryptedPreferences.edit().clear()
|
|
259
|
-
inst.biometricPreferences.edit().clear()
|
|
317
|
+
inst.applySecureEditor(inst.encryptedPreferences.edit().clear())
|
|
318
|
+
inst.applySecureEditor(inst.biometricPreferences.edit().clear())
|
|
319
|
+
inst.invalidateSecureKeysCache()
|
|
260
320
|
}
|
|
261
321
|
|
|
262
322
|
// --- Biometric (separate encrypted store, requires recent biometric auth on Android) ---
|
|
263
323
|
|
|
264
324
|
@JvmStatic
|
|
265
325
|
fun setSecureBiometric(key: String, value: String) {
|
|
266
|
-
|
|
326
|
+
setSecureBiometricWithLevel(key, value, 2)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
@JvmStatic
|
|
330
|
+
fun setSecureBiometricWithLevel(key: String, value: String, @Suppress("UNUSED_PARAMETER") level: Int) {
|
|
331
|
+
val inst = getInstanceOrThrow()
|
|
332
|
+
val editor = inst.biometricPreferences.edit().putString(key, value)
|
|
333
|
+
inst.applySecureEditor(editor)
|
|
334
|
+
inst.invalidateSecureKeysCache()
|
|
267
335
|
}
|
|
268
336
|
|
|
269
337
|
@JvmStatic
|
|
@@ -273,7 +341,9 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
273
341
|
|
|
274
342
|
@JvmStatic
|
|
275
343
|
fun deleteSecureBiometric(key: String) {
|
|
276
|
-
getInstanceOrThrow()
|
|
344
|
+
val inst = getInstanceOrThrow()
|
|
345
|
+
inst.applySecureEditor(inst.biometricPreferences.edit().remove(key))
|
|
346
|
+
inst.invalidateSecureKeysCache()
|
|
277
347
|
}
|
|
278
348
|
|
|
279
349
|
@JvmStatic
|
|
@@ -283,7 +353,9 @@ class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
|
283
353
|
|
|
284
354
|
@JvmStatic
|
|
285
355
|
fun clearSecureBiometric() {
|
|
286
|
-
getInstanceOrThrow()
|
|
356
|
+
val inst = getInstanceOrThrow()
|
|
357
|
+
inst.applySecureEditor(inst.biometricPreferences.edit().clear())
|
|
358
|
+
inst.invalidateSecureKeysCache()
|
|
287
359
|
}
|
|
288
360
|
}
|
|
289
361
|
}
|
|
@@ -1,27 +1,32 @@
|
|
|
1
1
|
#include "HybridStorage.hpp"
|
|
2
2
|
#include <stdexcept>
|
|
3
3
|
|
|
4
|
+
#ifndef NITRO_STORAGE_DISABLE_PLATFORM_ADAPTER
|
|
4
5
|
#if __APPLE__
|
|
5
6
|
#include "../../ios/IOSStorageAdapterCpp.hpp"
|
|
6
7
|
#elif __ANDROID__
|
|
7
8
|
#include "../../android/src/main/cpp/AndroidStorageAdapterCpp.hpp"
|
|
8
9
|
#include <fbjni/fbjni.h>
|
|
9
10
|
#endif
|
|
11
|
+
#endif
|
|
10
12
|
|
|
11
13
|
namespace margelo::nitro::NitroStorage {
|
|
12
14
|
|
|
13
15
|
namespace {
|
|
14
16
|
constexpr auto kBatchMissingSentinel = "__nitro_storage_batch_missing__::v1";
|
|
17
|
+
constexpr int kDefaultBiometricLevel = 2;
|
|
15
18
|
} // namespace
|
|
16
19
|
|
|
17
20
|
HybridStorage::HybridStorage()
|
|
18
21
|
: HybridObject(TAG), HybridStorageSpec() {
|
|
22
|
+
#ifndef NITRO_STORAGE_DISABLE_PLATFORM_ADAPTER
|
|
19
23
|
#if __APPLE__
|
|
20
24
|
nativeAdapter_ = std::make_shared<::NitroStorage::IOSStorageAdapterCpp>();
|
|
21
25
|
#elif __ANDROID__
|
|
22
26
|
auto context = ::NitroStorage::AndroidStorageAdapterJava::getContext();
|
|
23
27
|
nativeAdapter_ = std::make_shared<::NitroStorage::AndroidStorageAdapterCpp>(context);
|
|
24
28
|
#endif
|
|
29
|
+
#endif
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
HybridStorage::HybridStorage(std::shared_ptr<::NitroStorage::NativeStorageAdapter> adapter)
|
|
@@ -70,7 +75,8 @@ void HybridStorage::set(const std::string& key, const std::string& value, double
|
|
|
70
75
|
}
|
|
71
76
|
break;
|
|
72
77
|
}
|
|
73
|
-
|
|
78
|
+
|
|
79
|
+
onKeySet(static_cast<int>(s), key);
|
|
74
80
|
notifyListeners(static_cast<int>(s), key, value);
|
|
75
81
|
}
|
|
76
82
|
|
|
@@ -139,7 +145,8 @@ void HybridStorage::remove(const std::string& key, double scope) {
|
|
|
139
145
|
}
|
|
140
146
|
break;
|
|
141
147
|
}
|
|
142
|
-
|
|
148
|
+
|
|
149
|
+
onKeyRemove(static_cast<int>(s), key);
|
|
143
150
|
notifyListeners(static_cast<int>(s), key, std::nullopt);
|
|
144
151
|
}
|
|
145
152
|
|
|
@@ -175,11 +182,56 @@ std::vector<std::string> HybridStorage::getAllKeys(double scope) {
|
|
|
175
182
|
return keys;
|
|
176
183
|
}
|
|
177
184
|
case Scope::Disk:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
185
|
+
case Scope::Secure: {
|
|
186
|
+
const int scopeValue = static_cast<int>(s);
|
|
187
|
+
ensureKeyIndexHydrated(scopeValue);
|
|
188
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
189
|
+
auto indexIt = keyIndex_.find(scopeValue);
|
|
190
|
+
if (indexIt == keyIndex_.end()) {
|
|
191
|
+
return {};
|
|
192
|
+
}
|
|
193
|
+
return toVector(indexIt->second);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
std::vector<std::string> HybridStorage::getKeysByPrefix(const std::string& prefix, double scope) {
|
|
200
|
+
Scope s = toScope(scope);
|
|
201
|
+
if (prefix.empty()) {
|
|
202
|
+
return getAllKeys(scope);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
switch (s) {
|
|
206
|
+
case Scope::Memory: {
|
|
207
|
+
std::lock_guard<std::mutex> lock(memoryMutex_);
|
|
208
|
+
std::vector<std::string> keys;
|
|
209
|
+
keys.reserve(memoryStore_.size());
|
|
210
|
+
for (const auto& [key, _] : memoryStore_) {
|
|
211
|
+
if (key.rfind(prefix, 0) == 0) {
|
|
212
|
+
keys.push_back(key);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return keys;
|
|
216
|
+
}
|
|
217
|
+
case Scope::Disk:
|
|
218
|
+
case Scope::Secure: {
|
|
219
|
+
const int scopeValue = static_cast<int>(s);
|
|
220
|
+
ensureKeyIndexHydrated(scopeValue);
|
|
221
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
222
|
+
std::vector<std::string> keys;
|
|
223
|
+
auto indexIt = keyIndex_.find(scopeValue);
|
|
224
|
+
if (indexIt == keyIndex_.end()) {
|
|
225
|
+
return keys;
|
|
226
|
+
}
|
|
227
|
+
keys.reserve(indexIt->second.size());
|
|
228
|
+
for (const auto& key : indexIt->second) {
|
|
229
|
+
if (key.rfind(prefix, 0) == 0) {
|
|
230
|
+
keys.push_back(key);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return keys;
|
|
234
|
+
}
|
|
183
235
|
}
|
|
184
236
|
return {};
|
|
185
237
|
}
|
|
@@ -193,11 +245,16 @@ double HybridStorage::size(double scope) {
|
|
|
193
245
|
return static_cast<double>(memoryStore_.size());
|
|
194
246
|
}
|
|
195
247
|
case Scope::Disk:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
248
|
+
case Scope::Secure: {
|
|
249
|
+
const int scopeValue = static_cast<int>(s);
|
|
250
|
+
ensureKeyIndexHydrated(scopeValue);
|
|
251
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
252
|
+
auto indexIt = keyIndex_.find(scopeValue);
|
|
253
|
+
if (indexIt == keyIndex_.end()) {
|
|
254
|
+
return 0.0;
|
|
255
|
+
}
|
|
256
|
+
return static_cast<double>(indexIt->second.size());
|
|
257
|
+
}
|
|
201
258
|
}
|
|
202
259
|
return 0.0;
|
|
203
260
|
}
|
|
@@ -257,7 +314,8 @@ void HybridStorage::clear(double scope) {
|
|
|
257
314
|
}
|
|
258
315
|
break;
|
|
259
316
|
}
|
|
260
|
-
|
|
317
|
+
|
|
318
|
+
onScopeClear(static_cast<int>(s));
|
|
261
319
|
notifyListeners(static_cast<int>(s), "", std::nullopt);
|
|
262
320
|
}
|
|
263
321
|
|
|
@@ -298,8 +356,13 @@ void HybridStorage::setBatch(const std::vector<std::string>& keys, const std::ve
|
|
|
298
356
|
break;
|
|
299
357
|
}
|
|
300
358
|
|
|
359
|
+
const auto scopeValue = static_cast<int>(s);
|
|
360
|
+
for (const auto& key : keys) {
|
|
361
|
+
onKeySet(scopeValue, key);
|
|
362
|
+
}
|
|
363
|
+
const auto listeners = copyListenersForScope(scopeValue);
|
|
301
364
|
for (size_t i = 0; i < keys.size(); ++i) {
|
|
302
|
-
notifyListeners(
|
|
365
|
+
notifyListeners(listeners, keys[i], values[i]);
|
|
303
366
|
}
|
|
304
367
|
}
|
|
305
368
|
|
|
@@ -392,9 +455,28 @@ void HybridStorage::removeBatch(const std::vector<std::string>& keys, double sco
|
|
|
392
455
|
break;
|
|
393
456
|
}
|
|
394
457
|
|
|
458
|
+
const auto scopeValue = static_cast<int>(s);
|
|
395
459
|
for (const auto& key : keys) {
|
|
396
|
-
|
|
460
|
+
onKeyRemove(scopeValue, key);
|
|
461
|
+
}
|
|
462
|
+
const auto listeners = copyListenersForScope(scopeValue);
|
|
463
|
+
for (const auto& key : keys) {
|
|
464
|
+
notifyListeners(listeners, key, std::nullopt);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
void HybridStorage::removeByPrefix(const std::string& prefix, double scope) {
|
|
469
|
+
if (prefix.empty()) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const auto prefixedKeys = getKeysByPrefix(prefix, scope);
|
|
474
|
+
|
|
475
|
+
if (prefixedKeys.empty()) {
|
|
476
|
+
return;
|
|
397
477
|
}
|
|
478
|
+
|
|
479
|
+
removeBatch(prefixedKeys, scope);
|
|
398
480
|
}
|
|
399
481
|
|
|
400
482
|
// --- Configuration ---
|
|
@@ -404,6 +486,11 @@ void HybridStorage::setSecureAccessControl(double level) {
|
|
|
404
486
|
nativeAdapter_->setSecureAccessControl(static_cast<int>(level));
|
|
405
487
|
}
|
|
406
488
|
|
|
489
|
+
void HybridStorage::setSecureWritesAsync(bool enabled) {
|
|
490
|
+
ensureAdapter();
|
|
491
|
+
nativeAdapter_->setSecureWritesAsync(enabled);
|
|
492
|
+
}
|
|
493
|
+
|
|
407
494
|
void HybridStorage::setKeychainAccessGroup(const std::string& group) {
|
|
408
495
|
ensureAdapter();
|
|
409
496
|
nativeAdapter_->setKeychainAccessGroup(group);
|
|
@@ -412,9 +499,18 @@ void HybridStorage::setKeychainAccessGroup(const std::string& group) {
|
|
|
412
499
|
// --- Biometric ---
|
|
413
500
|
|
|
414
501
|
void HybridStorage::setSecureBiometric(const std::string& key, const std::string& value) {
|
|
502
|
+
setSecureBiometricWithLevel(key, value, kDefaultBiometricLevel);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
void HybridStorage::setSecureBiometricWithLevel(const std::string& key, const std::string& value, double level) {
|
|
415
506
|
ensureAdapter();
|
|
416
507
|
try {
|
|
417
|
-
nativeAdapter_->
|
|
508
|
+
nativeAdapter_->setSecureBiometricWithLevel(
|
|
509
|
+
key,
|
|
510
|
+
value,
|
|
511
|
+
static_cast<int>(level)
|
|
512
|
+
);
|
|
513
|
+
onKeySet(static_cast<int>(Scope::Secure), key);
|
|
418
514
|
notifyListeners(static_cast<int>(Scope::Secure), key, value);
|
|
419
515
|
} catch (const std::exception& e) {
|
|
420
516
|
throw std::runtime_error(std::string("NitroStorage: Biometric set failed: ") + e.what());
|
|
@@ -434,6 +530,7 @@ void HybridStorage::deleteSecureBiometric(const std::string& key) {
|
|
|
434
530
|
ensureAdapter();
|
|
435
531
|
try {
|
|
436
532
|
nativeAdapter_->deleteSecureBiometric(key);
|
|
533
|
+
onKeyRemove(static_cast<int>(Scope::Secure), key);
|
|
437
534
|
notifyListeners(static_cast<int>(Scope::Secure), key, std::nullopt);
|
|
438
535
|
} catch (const std::exception& e) {
|
|
439
536
|
throw std::runtime_error(std::string("NitroStorage: Biometric delete failed: ") + e.what());
|
|
@@ -449,6 +546,7 @@ void HybridStorage::clearSecureBiometric() {
|
|
|
449
546
|
ensureAdapter();
|
|
450
547
|
try {
|
|
451
548
|
nativeAdapter_->clearSecureBiometric();
|
|
549
|
+
onScopeClear(static_cast<int>(Scope::Secure));
|
|
452
550
|
notifyListeners(static_cast<int>(Scope::Secure), "", std::nullopt);
|
|
453
551
|
} catch (const std::exception& e) {
|
|
454
552
|
throw std::runtime_error(std::string("NitroStorage: Biometric clear failed: ") + e.what());
|
|
@@ -457,11 +555,7 @@ void HybridStorage::clearSecureBiometric() {
|
|
|
457
555
|
|
|
458
556
|
// --- Internal ---
|
|
459
557
|
|
|
460
|
-
|
|
461
|
-
int scope,
|
|
462
|
-
const std::string& key,
|
|
463
|
-
const std::optional<std::string>& value
|
|
464
|
-
) {
|
|
558
|
+
std::vector<HybridStorage::Listener> HybridStorage::copyListenersForScope(int scope) {
|
|
465
559
|
std::vector<Listener> listenersCopy;
|
|
466
560
|
{
|
|
467
561
|
std::lock_guard<std::mutex> lock(listenersMutex_);
|
|
@@ -471,8 +565,15 @@ void HybridStorage::notifyListeners(
|
|
|
471
565
|
listenersCopy = it->second;
|
|
472
566
|
}
|
|
473
567
|
}
|
|
474
|
-
|
|
475
|
-
|
|
568
|
+
return listenersCopy;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
void HybridStorage::notifyListeners(
|
|
572
|
+
const std::vector<Listener>& listeners,
|
|
573
|
+
const std::string& key,
|
|
574
|
+
const std::optional<std::string>& value
|
|
575
|
+
) {
|
|
576
|
+
for (const auto& listener : listeners) {
|
|
476
577
|
try {
|
|
477
578
|
listener.callback(key, value);
|
|
478
579
|
} catch (...) {
|
|
@@ -481,6 +582,96 @@ void HybridStorage::notifyListeners(
|
|
|
481
582
|
}
|
|
482
583
|
}
|
|
483
584
|
|
|
585
|
+
void HybridStorage::notifyListeners(
|
|
586
|
+
int scope,
|
|
587
|
+
const std::string& key,
|
|
588
|
+
const std::optional<std::string>& value
|
|
589
|
+
) {
|
|
590
|
+
const auto listeners = copyListenersForScope(scope);
|
|
591
|
+
notifyListeners(listeners, key, value);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
std::vector<std::string> HybridStorage::toVector(const std::unordered_set<std::string>& keys) {
|
|
595
|
+
std::vector<std::string> values;
|
|
596
|
+
values.reserve(keys.size());
|
|
597
|
+
for (const auto& key : keys) {
|
|
598
|
+
values.push_back(key);
|
|
599
|
+
}
|
|
600
|
+
return values;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
void HybridStorage::ensureKeyIndexHydrated(int scope) {
|
|
604
|
+
if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
{
|
|
609
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
610
|
+
auto hydratedIt = keyIndexHydrated_.find(scope);
|
|
611
|
+
if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
ensureAdapter();
|
|
617
|
+
std::vector<std::string> keys;
|
|
618
|
+
try {
|
|
619
|
+
if (scope == static_cast<int>(Scope::Disk)) {
|
|
620
|
+
keys = nativeAdapter_->getAllKeysDisk();
|
|
621
|
+
} else {
|
|
622
|
+
keys = nativeAdapter_->getAllKeysSecure();
|
|
623
|
+
}
|
|
624
|
+
} catch (const std::exception& e) {
|
|
625
|
+
throw std::runtime_error(std::string("NitroStorage: Key index hydration failed: ") + e.what());
|
|
626
|
+
} catch (...) {
|
|
627
|
+
throw std::runtime_error("NitroStorage: Key index hydration failed");
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
631
|
+
auto& index = keyIndex_[scope];
|
|
632
|
+
index.clear();
|
|
633
|
+
for (const auto& key : keys) {
|
|
634
|
+
index.insert(key);
|
|
635
|
+
}
|
|
636
|
+
keyIndexHydrated_[scope] = true;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
void HybridStorage::onKeySet(int scope, const std::string& key) {
|
|
640
|
+
if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
645
|
+
auto hydratedIt = keyIndexHydrated_.find(scope);
|
|
646
|
+
if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
|
|
647
|
+
keyIndex_[scope].insert(key);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
void HybridStorage::onKeyRemove(int scope, const std::string& key) {
|
|
652
|
+
if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
657
|
+
auto hydratedIt = keyIndexHydrated_.find(scope);
|
|
658
|
+
if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
|
|
659
|
+
keyIndex_[scope].erase(key);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
void HybridStorage::onScopeClear(int scope) {
|
|
664
|
+
if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
std::lock_guard<std::mutex> lock(keyIndexMutex_);
|
|
669
|
+
auto hydratedIt = keyIndexHydrated_.find(scope);
|
|
670
|
+
if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
|
|
671
|
+
keyIndex_[scope].clear();
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
484
675
|
void HybridStorage::ensureAdapter() const {
|
|
485
676
|
if (!nativeAdapter_) {
|
|
486
677
|
throw std::runtime_error("NitroStorage: Native adapter not initialized");
|
|
@@ -3,13 +3,23 @@
|
|
|
3
3
|
#include "HybridStorageSpec.hpp"
|
|
4
4
|
#include "../core/NativeStorageAdapter.hpp"
|
|
5
5
|
#include <unordered_map>
|
|
6
|
+
#include <map>
|
|
6
7
|
#include <mutex>
|
|
7
8
|
#include <functional>
|
|
8
9
|
#include <memory>
|
|
9
10
|
#include <vector>
|
|
11
|
+
#include <unordered_set>
|
|
10
12
|
|
|
11
13
|
namespace margelo::nitro::NitroStorage {
|
|
12
14
|
|
|
15
|
+
#ifdef NITRO_STORAGE_USE_ORDERED_MAP_FOR_TESTS
|
|
16
|
+
template <typename Key, typename Value>
|
|
17
|
+
using HybridStorageMap = std::map<Key, Value>;
|
|
18
|
+
#else
|
|
19
|
+
template <typename Key, typename Value>
|
|
20
|
+
using HybridStorageMap = std::unordered_map<Key, Value>;
|
|
21
|
+
#endif
|
|
22
|
+
|
|
13
23
|
class HybridStorage : public HybridStorageSpec {
|
|
14
24
|
public:
|
|
15
25
|
HybridStorage();
|
|
@@ -22,17 +32,21 @@ public:
|
|
|
22
32
|
void clear(double scope) override;
|
|
23
33
|
bool has(const std::string& key, double scope) override;
|
|
24
34
|
std::vector<std::string> getAllKeys(double scope) override;
|
|
35
|
+
std::vector<std::string> getKeysByPrefix(const std::string& prefix, double scope) override;
|
|
25
36
|
double size(double scope) override;
|
|
26
37
|
void setBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values, double scope) override;
|
|
27
38
|
std::vector<std::string> getBatch(const std::vector<std::string>& keys, double scope) override;
|
|
28
39
|
void removeBatch(const std::vector<std::string>& keys, double scope) override;
|
|
40
|
+
void removeByPrefix(const std::string& prefix, double scope) override;
|
|
29
41
|
std::function<void()> addOnChange(
|
|
30
42
|
double scope,
|
|
31
43
|
const std::function<void(const std::string&, const std::optional<std::string>&)>& callback
|
|
32
44
|
) override;
|
|
33
45
|
void setSecureAccessControl(double level) override;
|
|
46
|
+
void setSecureWritesAsync(bool enabled) override;
|
|
34
47
|
void setKeychainAccessGroup(const std::string& group) override;
|
|
35
48
|
void setSecureBiometric(const std::string& key, const std::string& value) override;
|
|
49
|
+
void setSecureBiometricWithLevel(const std::string& key, const std::string& value, double level) override;
|
|
36
50
|
std::optional<std::string> getSecureBiometric(const std::string& key) override;
|
|
37
51
|
void deleteSecureBiometric(const std::string& key) override;
|
|
38
52
|
bool hasSecureBiometric(const std::string& key) override;
|
|
@@ -50,16 +64,30 @@ private:
|
|
|
50
64
|
std::function<void(const std::string&, const std::optional<std::string>&)> callback;
|
|
51
65
|
};
|
|
52
66
|
|
|
53
|
-
|
|
67
|
+
HybridStorageMap<std::string, std::string> memoryStore_;
|
|
54
68
|
std::mutex memoryMutex_;
|
|
55
69
|
|
|
56
70
|
std::shared_ptr<::NitroStorage::NativeStorageAdapter> nativeAdapter_;
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
|
|
72
|
+
HybridStorageMap<int, std::vector<Listener>> listeners_;
|
|
59
73
|
std::mutex listenersMutex_;
|
|
60
74
|
size_t nextListenerId_ = 0;
|
|
75
|
+
HybridStorageMap<int, std::unordered_set<std::string>> keyIndex_;
|
|
76
|
+
HybridStorageMap<int, bool> keyIndexHydrated_;
|
|
77
|
+
std::mutex keyIndexMutex_;
|
|
61
78
|
|
|
79
|
+
std::vector<Listener> copyListenersForScope(int scope);
|
|
80
|
+
void notifyListeners(
|
|
81
|
+
const std::vector<Listener>& listeners,
|
|
82
|
+
const std::string& key,
|
|
83
|
+
const std::optional<std::string>& value
|
|
84
|
+
);
|
|
62
85
|
void notifyListeners(int scope, const std::string& key, const std::optional<std::string>& value);
|
|
86
|
+
std::vector<std::string> toVector(const std::unordered_set<std::string>& keys);
|
|
87
|
+
void ensureKeyIndexHydrated(int scope);
|
|
88
|
+
void onKeySet(int scope, const std::string& key);
|
|
89
|
+
void onKeyRemove(int scope, const std::string& key);
|
|
90
|
+
void onScopeClear(int scope);
|
|
63
91
|
void ensureAdapter() const;
|
|
64
92
|
Scope toScope(double scopeValue);
|
|
65
93
|
};
|
|
@@ -15,6 +15,7 @@ public:
|
|
|
15
15
|
virtual void deleteDisk(const std::string& key) = 0;
|
|
16
16
|
virtual bool hasDisk(const std::string& key) = 0;
|
|
17
17
|
virtual std::vector<std::string> getAllKeysDisk() = 0;
|
|
18
|
+
virtual std::vector<std::string> getKeysByPrefixDisk(const std::string& prefix) = 0;
|
|
18
19
|
virtual size_t sizeDisk() = 0;
|
|
19
20
|
virtual void setDiskBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) = 0;
|
|
20
21
|
virtual std::vector<std::optional<std::string>> getDiskBatch(const std::vector<std::string>& keys) = 0;
|
|
@@ -25,6 +26,7 @@ public:
|
|
|
25
26
|
virtual void deleteSecure(const std::string& key) = 0;
|
|
26
27
|
virtual bool hasSecure(const std::string& key) = 0;
|
|
27
28
|
virtual std::vector<std::string> getAllKeysSecure() = 0;
|
|
29
|
+
virtual std::vector<std::string> getKeysByPrefixSecure(const std::string& prefix) = 0;
|
|
28
30
|
virtual size_t sizeSecure() = 0;
|
|
29
31
|
virtual void setSecureBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values) = 0;
|
|
30
32
|
virtual std::vector<std::optional<std::string>> getSecureBatch(const std::vector<std::string>& keys) = 0;
|
|
@@ -34,9 +36,11 @@ public:
|
|
|
34
36
|
virtual void clearSecure() = 0;
|
|
35
37
|
|
|
36
38
|
virtual void setSecureAccessControl(int level) = 0;
|
|
39
|
+
virtual void setSecureWritesAsync(bool enabled) = 0;
|
|
37
40
|
virtual void setKeychainAccessGroup(const std::string& group) = 0;
|
|
38
41
|
|
|
39
42
|
virtual void setSecureBiometric(const std::string& key, const std::string& value) = 0;
|
|
43
|
+
virtual void setSecureBiometricWithLevel(const std::string& key, const std::string& value, int level) = 0;
|
|
40
44
|
virtual std::optional<std::string> getSecureBiometric(const std::string& key) = 0;
|
|
41
45
|
virtual void deleteSecureBiometric(const std::string& key) = 0;
|
|
42
46
|
virtual bool hasSecureBiometric(const std::string& key) = 0;
|