react-native-mmkv 4.1.2 → 4.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.
- package/NitroMmkv.podspec +9 -2
- package/android/CMakeLists.txt +5 -0
- package/android/build.gradle +8 -3
- package/android/src/main/cpp/cpp-adapter.cpp +2 -1
- package/cpp/HybridMMKV.cpp +94 -58
- package/cpp/HybridMMKV.hpp +5 -0
- package/cpp/HybridMMKVFactory.cpp +1 -5
- package/cpp/MMKVTypes.hpp +12 -0
- package/cpp/ManagedMMBuffer.hpp +1 -1
- package/lib/__tests__/hooks.test.js +30 -1
- package/lib/createMMKV/createMMKV.web.js +17 -1
- package/lib/createMMKV/createMockMMKV.js +15 -1
- package/lib/hooks/createMMKVHook.js +9 -18
- package/lib/specs/MMKV.nitro.d.ts +46 -1
- package/lib/specs/MMKVFactory.nitro.d.ts +30 -1
- package/nitrogen/generated/android/NitroMmkvOnLoad.cpp +35 -25
- package/nitrogen/generated/android/NitroMmkvOnLoad.hpp +13 -4
- package/nitrogen/generated/android/c++/JHybridMMKVPlatformContextSpec.cpp +20 -26
- package/nitrogen/generated/android/c++/JHybridMMKVPlatformContextSpec.hpp +19 -22
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/mmkv/HybridMMKVPlatformContextSpec.kt +15 -18
- package/nitrogen/generated/ios/NitroMmkv-Swift-Cxx-Bridge.hpp +1 -1
- package/nitrogen/generated/ios/swift/HybridMMKVPlatformContextSpec.swift +0 -1
- package/nitrogen/generated/ios/swift/HybridMMKVPlatformContextSpec_cxx.swift +0 -1
- package/nitrogen/generated/shared/c++/Configuration.hpp +13 -2
- package/nitrogen/generated/shared/c++/EncryptionType.hpp +76 -0
- package/nitrogen/generated/shared/c++/HybridMMKVSpec.cpp +5 -0
- package/nitrogen/generated/shared/c++/HybridMMKVSpec.hpp +8 -0
- package/package.json +3 -3
- package/src/__tests__/hooks.test.tsx +37 -0
- package/src/createMMKV/createMMKV.web.ts +17 -1
- package/src/createMMKV/createMockMMKV.ts +15 -1
- package/src/hooks/createMMKVHook.ts +16 -19
- package/src/specs/MMKV.nitro.ts +46 -1
- package/src/specs/MMKVFactory.nitro.ts +31 -1
package/NitroMmkv.podspec
CHANGED
|
@@ -24,13 +24,20 @@ Pod::Spec.new do |s|
|
|
|
24
24
|
|
|
25
25
|
# Add MMKV Core dependency
|
|
26
26
|
s.compiler_flags = '-x objective-c++'
|
|
27
|
-
s.dependency 'MMKVCore', '2.
|
|
27
|
+
s.dependency 'MMKVCore', '2.4.0'
|
|
28
|
+
|
|
29
|
+
# Optionally configure MMKV log level via Podfile ($MMKVLogLevel) or env var (MMKV_LOG_LEVEL)
|
|
30
|
+
mmkv_log_level = $MMKVLogLevel || ENV['MMKV_LOG_LEVEL']
|
|
31
|
+
gcc_preprocessor_defs = "$(inherited) FOLLY_NO_CONFIG FOLLY_CFG_NO_COROUTINES"
|
|
32
|
+
if mmkv_log_level != nil && mmkv_log_level.to_s != ""
|
|
33
|
+
gcc_preprocessor_defs += " MMKV_LOG_LEVEL=#{mmkv_log_level}"
|
|
34
|
+
end
|
|
28
35
|
|
|
29
36
|
# TODO: Remove when no one uses RN 0.79 anymore
|
|
30
37
|
# Add support for React Native 0.79 or below
|
|
31
38
|
s.pod_target_xcconfig = {
|
|
32
39
|
"HEADER_SEARCH_PATHS" => ["${PODS_ROOT}/RCT-Folly"],
|
|
33
|
-
"GCC_PREPROCESSOR_DEFINITIONS" =>
|
|
40
|
+
"GCC_PREPROCESSOR_DEFINITIONS" => gcc_preprocessor_defs,
|
|
34
41
|
"OTHER_CPLUSPLUSFLAGS" => "$(inherited) -DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1"
|
|
35
42
|
}
|
|
36
43
|
|
package/android/CMakeLists.txt
CHANGED
|
@@ -5,6 +5,11 @@ set (PACKAGE_NAME NitroMmkv)
|
|
|
5
5
|
set (CMAKE_VERBOSE_MAKEFILE ON)
|
|
6
6
|
set (CMAKE_CXX_STANDARD 20)
|
|
7
7
|
|
|
8
|
+
# Optionally configure MMKV log level (passed from Gradle as -DMMKV_LOG_LEVEL=<0-4>)
|
|
9
|
+
if(DEFINED MMKV_LOG_LEVEL)
|
|
10
|
+
add_definitions(-DMMKV_LOG_LEVEL=${MMKV_LOG_LEVEL})
|
|
11
|
+
endif()
|
|
12
|
+
|
|
8
13
|
# Find all C++ files (shared and platform specifics)
|
|
9
14
|
file(GLOB_RECURSE shared_files RELATIVE ${CMAKE_SOURCE_DIR}
|
|
10
15
|
"../cpp/**.cpp"
|
package/android/build.gradle
CHANGED
|
@@ -5,7 +5,7 @@ buildscript {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
dependencies {
|
|
8
|
-
classpath "com.android.tools.build:gradle:
|
|
8
|
+
classpath "com.android.tools.build:gradle:9.1.0"
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -49,7 +49,12 @@ android {
|
|
|
49
49
|
externalNativeBuild {
|
|
50
50
|
cmake {
|
|
51
51
|
cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all"
|
|
52
|
-
|
|
52
|
+
def mmkvLogLevel = rootProject.hasProperty("MMKV_logLevel") ? rootProject.property("MMKV_logLevel") : null
|
|
53
|
+
if (mmkvLogLevel != null && mmkvLogLevel != "") {
|
|
54
|
+
arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DMMKV_LOG_LEVEL=${mmkvLogLevel}"
|
|
55
|
+
} else {
|
|
56
|
+
arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
|
|
57
|
+
}
|
|
53
58
|
abiFilters (*reactNativeArchitectures())
|
|
54
59
|
|
|
55
60
|
buildTypes {
|
|
@@ -140,6 +145,6 @@ dependencies {
|
|
|
140
145
|
implementation project(":react-native-nitro-modules")
|
|
141
146
|
|
|
142
147
|
// Add a dependency on mmkv core (this ships a C++ prefab)
|
|
143
|
-
implementation "io.github.zhongwuzw:mmkv:2.
|
|
148
|
+
implementation "io.github.zhongwuzw:mmkv:2.4.0"
|
|
144
149
|
}
|
|
145
150
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include "NitroMmkvOnLoad.hpp"
|
|
2
|
+
#include <fbjni/fbjni.h>
|
|
2
3
|
#include <jni.h>
|
|
3
4
|
|
|
4
5
|
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
|
|
5
|
-
return margelo::nitro::mmkv::
|
|
6
|
+
return facebook::jni::initialize(vm, []() { margelo::nitro::mmkv::registerAllNatives(); });
|
|
6
7
|
}
|
package/cpp/HybridMMKV.cpp
CHANGED
|
@@ -14,40 +14,51 @@
|
|
|
14
14
|
namespace margelo::nitro::mmkv {
|
|
15
15
|
|
|
16
16
|
HybridMMKV::HybridMMKV(const Configuration& config) : HybridObject(TAG) {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
MMKVMode mmkvMode = getMMKVMode(config);
|
|
18
|
+
if (config.readOnly.value_or(false)) {
|
|
19
|
+
mmkvMode = mmkvMode | MMKVMode::MMKV_READ_ONLY;
|
|
20
|
+
}
|
|
21
|
+
bool useAes256Encryption = config.encryptionType.value_or(EncryptionType::AES_128) == EncryptionType::AES_256;
|
|
22
|
+
std::string encryptionKey = config.encryptionKey.value_or("");
|
|
23
|
+
std::string* encryptionKeyPtr = encryptionKey.size() > 0 ? &encryptionKey : nullptr;
|
|
24
|
+
std::string rootPath = config.path.value_or("");
|
|
25
|
+
std::string* rootPathPtr = rootPath.size() > 0 ? &rootPath : nullptr;
|
|
26
|
+
bool compareBeforeSet = config.compareBeforeSet.value_or(false);
|
|
27
|
+
|
|
28
|
+
MMKVConfig mmkvConfig{.mode = mmkvMode,
|
|
29
|
+
.aes256 = useAes256Encryption,
|
|
30
|
+
.cryptKey = encryptionKeyPtr,
|
|
31
|
+
.rootPath = rootPathPtr,
|
|
32
|
+
.enableCompareBeforeSet = compareBeforeSet};
|
|
33
|
+
|
|
19
34
|
bool hasEncryptionKey = encryptionKey.size() > 0;
|
|
20
|
-
Logger::log(LogLevel::Info, TAG, "Creating MMKV instance \"%s\"... (Path: %s, Encrypted: %s)", config.id.c_str(),
|
|
35
|
+
Logger::log(LogLevel::Info, TAG, "Creating MMKV instance \"%s\"... (Path: %s, Encrypted: %s)", config.id.c_str(), rootPath.c_str(),
|
|
21
36
|
hasEncryptionKey ? "true" : "false");
|
|
22
37
|
|
|
23
|
-
|
|
24
|
-
std::string* encryptionKeyPtr = encryptionKey.size() > 0 ? &encryptionKey : nullptr;
|
|
25
|
-
MMKVMode mode = getMMKVMode(config);
|
|
26
|
-
if (config.readOnly.has_value() && config.readOnly.value()) {
|
|
27
|
-
Logger::log(LogLevel::Info, TAG, "Instance is read-only!");
|
|
28
|
-
mode = mode | ::mmkv::MMKV_READ_ONLY;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
#ifdef __APPLE__
|
|
32
|
-
instance = MMKV::mmkvWithID(config.id, mode, encryptionKeyPtr, pathPtr);
|
|
33
|
-
#else
|
|
34
|
-
instance = MMKV::mmkvWithID(config.id, DEFAULT_MMAP_SIZE, mode, encryptionKeyPtr, pathPtr);
|
|
35
|
-
#endif
|
|
38
|
+
instance = MMKV::mmkvWithID(config.id, mmkvConfig);
|
|
36
39
|
|
|
37
40
|
if (instance == nullptr) [[unlikely]] {
|
|
38
41
|
// Check if instanceId is invalid
|
|
39
|
-
if (config.id.empty())
|
|
42
|
+
if (config.id.empty()) {
|
|
40
43
|
throw std::runtime_error("Failed to create MMKV instance! `id` cannot be empty!");
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
if (useAes256Encryption) {
|
|
47
|
+
// With AES-256, the max key length is 32 bytes.
|
|
48
|
+
if (encryptionKey.size() > 32) [[unlikely]] {
|
|
49
|
+
throw std::runtime_error("Failed to create MMKV instance! `encryptionKey` cannot be longer "
|
|
50
|
+
"than 32 bytes with AES-256 encryption!");
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
// With AES-128, the max key length is 16 bytes.
|
|
54
|
+
if (encryptionKey.size() > 16) [[unlikely]] {
|
|
55
|
+
throw std::runtime_error("Failed to create MMKV instance! `encryptionKey` cannot be longer "
|
|
56
|
+
"than 16 bytes with AES-128 encryption!");
|
|
57
|
+
}
|
|
47
58
|
}
|
|
48
59
|
|
|
49
60
|
// Check if path is maybe invalid
|
|
50
|
-
if (
|
|
61
|
+
if (rootPath.empty()) [[unlikely]] {
|
|
51
62
|
throw std::runtime_error("Failed to create MMKV instance! `path` cannot be empty!");
|
|
52
63
|
}
|
|
53
64
|
|
|
@@ -58,13 +69,27 @@ HybridMMKV::HybridMMKV(const Configuration& config) : HybridObject(TAG) {
|
|
|
58
69
|
std::string HybridMMKV::getId() {
|
|
59
70
|
return instance->mmapID();
|
|
60
71
|
}
|
|
72
|
+
|
|
73
|
+
double HybridMMKV::getLength() {
|
|
74
|
+
return instance->count();
|
|
75
|
+
}
|
|
76
|
+
|
|
61
77
|
double HybridMMKV::getSize() {
|
|
78
|
+
return getByteSize();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
double HybridMMKV::getByteSize() {
|
|
62
82
|
return instance->actualSize();
|
|
63
83
|
}
|
|
84
|
+
|
|
64
85
|
bool HybridMMKV::getIsReadOnly() {
|
|
65
86
|
return instance->isReadOnly();
|
|
66
87
|
}
|
|
67
88
|
|
|
89
|
+
bool HybridMMKV::getIsEncrypted() {
|
|
90
|
+
return instance->isEncryptionEnabled();
|
|
91
|
+
}
|
|
92
|
+
|
|
68
93
|
// helper: overload pattern matching for lambdas
|
|
69
94
|
template <class... Ts>
|
|
70
95
|
struct overloaded : Ts... {
|
|
@@ -79,31 +104,32 @@ void HybridMMKV::set(const std::string& key, const std::variant<bool, std::share
|
|
|
79
104
|
}
|
|
80
105
|
|
|
81
106
|
// Pattern-match each potential value in std::variant
|
|
82
|
-
bool
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (!
|
|
107
|
+
bool successful = std::visit(overloaded{[&](bool b) {
|
|
108
|
+
// boolean
|
|
109
|
+
return instance->set(b, key);
|
|
110
|
+
},
|
|
111
|
+
[&](const std::shared_ptr<ArrayBuffer>& buf) {
|
|
112
|
+
// ArrayBuffer
|
|
113
|
+
MMBuffer buffer(buf->data(), buf->size(), MMBufferCopyFlag::MMBufferNoCopy);
|
|
114
|
+
return instance->set(std::move(buffer), key);
|
|
115
|
+
},
|
|
116
|
+
[&](const std::string& string) {
|
|
117
|
+
// string
|
|
118
|
+
return instance->set(string, key);
|
|
119
|
+
},
|
|
120
|
+
[&](double number) {
|
|
121
|
+
// number
|
|
122
|
+
return instance->set(number, key);
|
|
123
|
+
}},
|
|
124
|
+
value);
|
|
125
|
+
if (!successful) [[unlikely]] {
|
|
101
126
|
throw std::runtime_error("Failed to set value for key \"" + key + "\"!");
|
|
102
127
|
}
|
|
103
128
|
|
|
104
129
|
// Notify on changed
|
|
105
130
|
MMKVValueChangedListenerRegistry::notifyOnValueChanged(instance->mmapID(), key);
|
|
106
131
|
}
|
|
132
|
+
|
|
107
133
|
std::optional<bool> HybridMMKV::getBoolean(const std::string& key) {
|
|
108
134
|
bool hasValue;
|
|
109
135
|
bool result = instance->getBool(key, /* defaultValue */ false, &hasValue);
|
|
@@ -113,6 +139,7 @@ std::optional<bool> HybridMMKV::getBoolean(const std::string& key) {
|
|
|
113
139
|
return std::nullopt;
|
|
114
140
|
}
|
|
115
141
|
}
|
|
142
|
+
|
|
116
143
|
std::optional<std::string> HybridMMKV::getString(const std::string& key) {
|
|
117
144
|
std::string result;
|
|
118
145
|
bool hasValue = instance->getString(key, result, /* inplaceModification */ true);
|
|
@@ -122,6 +149,7 @@ std::optional<std::string> HybridMMKV::getString(const std::string& key) {
|
|
|
122
149
|
return std::nullopt;
|
|
123
150
|
}
|
|
124
151
|
}
|
|
152
|
+
|
|
125
153
|
std::optional<double> HybridMMKV::getNumber(const std::string& key) {
|
|
126
154
|
bool hasValue;
|
|
127
155
|
double result = instance->getDouble(key, /* defaultValue */ 0.0, &hasValue);
|
|
@@ -131,25 +159,21 @@ std::optional<double> HybridMMKV::getNumber(const std::string& key) {
|
|
|
131
159
|
return std::nullopt;
|
|
132
160
|
}
|
|
133
161
|
}
|
|
162
|
+
|
|
134
163
|
std::optional<std::shared_ptr<ArrayBuffer>> HybridMMKV::getBuffer(const std::string& key) {
|
|
135
164
|
MMBuffer result;
|
|
136
|
-
#ifdef __APPLE__
|
|
137
|
-
// iOS: Convert std::string to NSString* for MMKVCore pod compatibility
|
|
138
|
-
bool hasValue = instance->getBytes(@(key.c_str()), result);
|
|
139
|
-
#else
|
|
140
|
-
// Android/other platforms: Use std::string directly (converts to
|
|
141
|
-
// std::string_view)
|
|
142
165
|
bool hasValue = instance->getBytes(key, result);
|
|
143
|
-
#endif
|
|
144
166
|
if (hasValue) {
|
|
145
167
|
return std::make_shared<ManagedMMBuffer>(std::move(result));
|
|
146
168
|
} else {
|
|
147
169
|
return std::nullopt;
|
|
148
170
|
}
|
|
149
171
|
}
|
|
172
|
+
|
|
150
173
|
bool HybridMMKV::contains(const std::string& key) {
|
|
151
174
|
return instance->containsKey(key);
|
|
152
175
|
}
|
|
176
|
+
|
|
153
177
|
bool HybridMMKV::remove(const std::string& key) {
|
|
154
178
|
bool wasRemoved = instance->removeValueForKey(key);
|
|
155
179
|
if (wasRemoved) {
|
|
@@ -158,9 +182,11 @@ bool HybridMMKV::remove(const std::string& key) {
|
|
|
158
182
|
}
|
|
159
183
|
return wasRemoved;
|
|
160
184
|
}
|
|
185
|
+
|
|
161
186
|
std::vector<std::string> HybridMMKV::getAllKeys() {
|
|
162
187
|
return instance->allKeys();
|
|
163
188
|
}
|
|
189
|
+
|
|
164
190
|
void HybridMMKV::clearAll() {
|
|
165
191
|
auto keysBefore = getAllKeys();
|
|
166
192
|
instance->clearAll();
|
|
@@ -169,19 +195,30 @@ void HybridMMKV::clearAll() {
|
|
|
169
195
|
MMKVValueChangedListenerRegistry::notifyOnValueChanged(instance->mmapID(), key);
|
|
170
196
|
}
|
|
171
197
|
}
|
|
198
|
+
|
|
172
199
|
void HybridMMKV::recrypt(const std::optional<std::string>& key) {
|
|
173
|
-
bool successful = false;
|
|
174
200
|
if (key.has_value()) {
|
|
175
|
-
|
|
176
|
-
successful = instance->reKey(key.value());
|
|
201
|
+
encrypt(key.value(), std::nullopt);
|
|
177
202
|
} else {
|
|
178
|
-
|
|
179
|
-
successful = instance->reKey(std::string());
|
|
203
|
+
decrypt();
|
|
180
204
|
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
void HybridMMKV::encrypt(const std::string& key, std::optional<EncryptionType> encryptionType) {
|
|
208
|
+
bool isAes256Encryption = encryptionType == EncryptionType::AES_256;
|
|
209
|
+
bool successful = instance->reKey(key, isAes256Encryption);
|
|
181
210
|
if (!successful) {
|
|
182
|
-
throw std::runtime_error("Failed to
|
|
211
|
+
throw std::runtime_error("Failed to encrypt MMKV instance!");
|
|
183
212
|
}
|
|
184
213
|
}
|
|
214
|
+
|
|
215
|
+
void HybridMMKV::decrypt() {
|
|
216
|
+
bool successful = instance->reKey("");
|
|
217
|
+
if (!successful) [[unlikely]] {
|
|
218
|
+
throw std::runtime_error("Failed to decrypt MMKV instance!");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
185
222
|
void HybridMMKV::trim() {
|
|
186
223
|
instance->trim();
|
|
187
224
|
instance->clearMemoryCache();
|
|
@@ -190,7 +227,7 @@ void HybridMMKV::trim() {
|
|
|
190
227
|
Listener HybridMMKV::addOnValueChangedListener(const std::function<void(const std::string& /* key */)>& onValueChanged) {
|
|
191
228
|
// Add listener
|
|
192
229
|
auto mmkvID = instance->mmapID();
|
|
193
|
-
auto listenerID = MMKVValueChangedListenerRegistry::addListener(
|
|
230
|
+
auto listenerID = MMKVValueChangedListenerRegistry::addListener(mmkvID, onValueChanged);
|
|
194
231
|
|
|
195
232
|
return Listener([=]() {
|
|
196
233
|
// remove()
|
|
@@ -207,14 +244,13 @@ MMKVMode HybridMMKV::getMMKVMode(const Configuration& config) {
|
|
|
207
244
|
return ::mmkv::MMKV_SINGLE_PROCESS;
|
|
208
245
|
case Mode::MULTI_PROCESS:
|
|
209
246
|
return ::mmkv::MMKV_MULTI_PROCESS;
|
|
210
|
-
default:
|
|
211
|
-
[[unlikely]] throw std::runtime_error("Invalid MMKV Mode value!");
|
|
212
247
|
}
|
|
248
|
+
throw std::runtime_error("Invalid MMKV Mode value!");
|
|
213
249
|
}
|
|
214
250
|
|
|
215
251
|
double HybridMMKV::importAllFrom(const std::shared_ptr<HybridMMKVSpec>& other) {
|
|
216
252
|
auto hybridMMKV = std::dynamic_pointer_cast<HybridMMKV>(other);
|
|
217
|
-
if (hybridMMKV == nullptr) {
|
|
253
|
+
if (hybridMMKV == nullptr) [[unlikely]] {
|
|
218
254
|
throw std::runtime_error("The given `MMKV` instance is not of type `HybridMMKV`!");
|
|
219
255
|
}
|
|
220
256
|
|
package/cpp/HybridMMKV.hpp
CHANGED
|
@@ -21,7 +21,10 @@ public:
|
|
|
21
21
|
// Properties
|
|
22
22
|
std::string getId() override;
|
|
23
23
|
double getSize() override;
|
|
24
|
+
double getByteSize() override;
|
|
25
|
+
double getLength() override;
|
|
24
26
|
bool getIsReadOnly() override;
|
|
27
|
+
bool getIsEncrypted() override;
|
|
25
28
|
|
|
26
29
|
public:
|
|
27
30
|
// Methods
|
|
@@ -35,6 +38,8 @@ public:
|
|
|
35
38
|
std::vector<std::string> getAllKeys() override;
|
|
36
39
|
void clearAll() override;
|
|
37
40
|
void recrypt(const std::optional<std::string>& key) override;
|
|
41
|
+
void encrypt(const std::string& key, std::optional<EncryptionType> encryptionType) override;
|
|
42
|
+
void decrypt() override;
|
|
38
43
|
void trim() override;
|
|
39
44
|
Listener addOnValueChangedListener(const std::function<void(const std::string& /* key */)>& onValueChanged) override;
|
|
40
45
|
double importAllFrom(const std::shared_ptr<HybridMMKVSpec>& other) override;
|
|
@@ -18,11 +18,7 @@ std::string HybridMMKVFactory::getDefaultMMKVInstanceId() {
|
|
|
18
18
|
void HybridMMKVFactory::initializeMMKV(const std::string& rootPath) {
|
|
19
19
|
Logger::log(LogLevel::Info, TAG, "Initializing MMKV with rootPath=%s", rootPath.c_str());
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
MMKVLogLevel logLevel = ::mmkv::MMKVLogDebug;
|
|
23
|
-
#else
|
|
24
|
-
MMKVLogLevel logLevel = ::mmkv::MMKVLogWarning;
|
|
25
|
-
#endif
|
|
21
|
+
MMKVLogLevel logLevel = static_cast<MMKVLogLevel>(MMKV_LOG_LEVEL);
|
|
26
22
|
MMKV::initializeMMKV(rootPath, logLevel);
|
|
27
23
|
}
|
|
28
24
|
|
package/cpp/MMKVTypes.hpp
CHANGED
|
@@ -48,3 +48,15 @@ constexpr auto MMKV_READ_ONLY = ::MMKVMode::MMKV_READ_ONLY;
|
|
|
48
48
|
*/
|
|
49
49
|
|
|
50
50
|
using namespace mmkv;
|
|
51
|
+
|
|
52
|
+
// Default MMKV log level if not configured at build time
|
|
53
|
+
#ifndef MMKV_LOG_LEVEL
|
|
54
|
+
#ifdef NITRO_DEBUG
|
|
55
|
+
#define MMKV_LOG_LEVEL 0
|
|
56
|
+
#else
|
|
57
|
+
#define MMKV_LOG_LEVEL 3
|
|
58
|
+
#endif
|
|
59
|
+
#endif
|
|
60
|
+
#if MMKV_LOG_LEVEL < 0 || MMKV_LOG_LEVEL > 4
|
|
61
|
+
#error "MMKV_LOG_LEVEL must be between 0 (Debug) and 4 (None)"
|
|
62
|
+
#endif
|
package/cpp/ManagedMMBuffer.hpp
CHANGED
|
@@ -15,7 +15,7 @@ namespace margelo::nitro::mmkv {
|
|
|
15
15
|
/**
|
|
16
16
|
An ArrayBuffer subclass that manages MMBuffer memory (by ownership).
|
|
17
17
|
*/
|
|
18
|
-
class ManagedMMBuffer : public ArrayBuffer {
|
|
18
|
+
class ManagedMMBuffer final : public ArrayBuffer {
|
|
19
19
|
public:
|
|
20
20
|
explicit ManagedMMBuffer(MMBuffer&& buffer) : _buffer(std::move(buffer)) {}
|
|
21
21
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Button, Text } from 'react-native';
|
|
3
|
-
import { act, fireEvent, render, renderHook, screen, cleanup, } from '@testing-library/react-native';
|
|
3
|
+
import { act, fireEvent, render, renderHook, screen, cleanup, waitFor, } from '@testing-library/react-native';
|
|
4
4
|
import { createMMKV, useMMKVNumber, useMMKVString } from '..';
|
|
5
5
|
const mmkv = createMMKV();
|
|
6
6
|
beforeEach(() => {
|
|
@@ -67,3 +67,32 @@ test('functional updates to hooks', () => {
|
|
|
67
67
|
'4',
|
|
68
68
|
]);
|
|
69
69
|
});
|
|
70
|
+
test('useMMKV hook does not miss updates that happen during subscription setup', async () => {
|
|
71
|
+
const raceKey = 'race-key';
|
|
72
|
+
const raceMMKV = createMMKV();
|
|
73
|
+
let simulatedRaceDone = false;
|
|
74
|
+
const originalSubscribe = raceMMKV.addOnValueChangedListener.bind(raceMMKV);
|
|
75
|
+
raceMMKV.addOnValueChangedListener = ((listener) => {
|
|
76
|
+
if (!simulatedRaceDone) {
|
|
77
|
+
simulatedRaceDone = true;
|
|
78
|
+
raceMMKV.set(raceKey, 'updated-before-subscribe');
|
|
79
|
+
}
|
|
80
|
+
return originalSubscribe(listener);
|
|
81
|
+
});
|
|
82
|
+
const { result } = renderHook(() => useMMKVString(raceKey, raceMMKV));
|
|
83
|
+
await waitFor(() => {
|
|
84
|
+
expect(result.current[0]).toBe('updated-before-subscribe');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
test('useMMKV hook stays consistent during rapid updates', async () => {
|
|
88
|
+
const raceKey = 'rapid-key';
|
|
89
|
+
const { result } = renderHook(() => useMMKVNumber(raceKey, mmkv));
|
|
90
|
+
act(() => {
|
|
91
|
+
for (let i = 1; i <= 100; i++) {
|
|
92
|
+
mmkv.set(raceKey, i);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
await waitFor(() => {
|
|
96
|
+
expect(result.current[0]).toBe(100);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -24,8 +24,18 @@ export function createMMKV(config = { id: 'mmkv.default' }) {
|
|
|
24
24
|
};
|
|
25
25
|
return {
|
|
26
26
|
id: config.id,
|
|
27
|
-
|
|
27
|
+
get length() {
|
|
28
|
+
return getLocalStorage().length;
|
|
29
|
+
},
|
|
30
|
+
get size() {
|
|
31
|
+
return this.byteSize;
|
|
32
|
+
},
|
|
33
|
+
get byteSize() {
|
|
34
|
+
// estimate - assumes UTF8
|
|
35
|
+
return JSON.stringify(getLocalStorage()).length;
|
|
36
|
+
},
|
|
28
37
|
isReadOnly: false,
|
|
38
|
+
isEncrypted: false,
|
|
29
39
|
clearAll: () => {
|
|
30
40
|
const storage = getLocalStorage();
|
|
31
41
|
const keys = Object.keys(storage);
|
|
@@ -90,6 +100,12 @@ export function createMMKV(config = { id: 'mmkv.default' }) {
|
|
|
90
100
|
recrypt: () => {
|
|
91
101
|
throw new Error('`recrypt(..)` is not supported on Web!');
|
|
92
102
|
},
|
|
103
|
+
encrypt: () => {
|
|
104
|
+
throw new Error('`encrypt(..)` is not supported on Web!');
|
|
105
|
+
},
|
|
106
|
+
decrypt: () => {
|
|
107
|
+
throw new Error('`decrypt(..)` is not supported on Web!');
|
|
108
|
+
},
|
|
93
109
|
trim: () => {
|
|
94
110
|
// no-op
|
|
95
111
|
},
|
|
@@ -11,10 +11,18 @@ export function createMockMMKV(config = { id: 'mmkv.default' }) {
|
|
|
11
11
|
};
|
|
12
12
|
return {
|
|
13
13
|
id: config.id,
|
|
14
|
-
get
|
|
14
|
+
get length() {
|
|
15
15
|
return storage.size;
|
|
16
16
|
},
|
|
17
|
+
get size() {
|
|
18
|
+
return this.byteSize;
|
|
19
|
+
},
|
|
20
|
+
get byteSize() {
|
|
21
|
+
// estimate - assumes UTF8
|
|
22
|
+
return JSON.stringify(storage).length;
|
|
23
|
+
},
|
|
17
24
|
isReadOnly: false,
|
|
25
|
+
isEncrypted: false,
|
|
18
26
|
clearAll: () => {
|
|
19
27
|
const keysBefore = storage.keys();
|
|
20
28
|
storage.clear();
|
|
@@ -57,6 +65,12 @@ export function createMockMMKV(config = { id: 'mmkv.default' }) {
|
|
|
57
65
|
recrypt: () => {
|
|
58
66
|
console.warn('Encryption is not supported in mocked MMKV instances!');
|
|
59
67
|
},
|
|
68
|
+
encrypt: () => {
|
|
69
|
+
console.warn('Encryption is not supported in mocked MMKV instances!');
|
|
70
|
+
},
|
|
71
|
+
decrypt: () => {
|
|
72
|
+
console.warn('Encryption is not supported in mocked MMKV instances!');
|
|
73
|
+
},
|
|
60
74
|
trim: () => {
|
|
61
75
|
// no-op
|
|
62
76
|
},
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { useCallback,
|
|
1
|
+
import { useCallback, useSyncExternalStore } from 'react';
|
|
2
2
|
import { getDefaultMMKVInstance } from '../createMMKV/getDefaultMMKVInstance';
|
|
3
3
|
export function createMMKVHook(getter) {
|
|
4
4
|
return (key, instance) => {
|
|
5
5
|
const mmkv = instance ?? getDefaultMMKVInstance();
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return
|
|
13
|
-
}, [mmkv, key,
|
|
6
|
+
const value = useSyncExternalStore(useCallback((onStoreChange) => {
|
|
7
|
+
const listener = mmkv.addOnValueChangedListener((changedKey) => {
|
|
8
|
+
if (changedKey === key) {
|
|
9
|
+
onStoreChange();
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
return () => listener.remove();
|
|
13
|
+
}, [key, mmkv]), useCallback(() => getter(mmkv, key), [key, mmkv]), useCallback(() => getter(mmkv, key), [key, mmkv]));
|
|
14
14
|
// update value by user set
|
|
15
15
|
const set = useCallback((v) => {
|
|
16
16
|
const newValue = typeof v === 'function' ? v(getter(mmkv, key)) : v;
|
|
@@ -35,15 +35,6 @@ export function createMMKVHook(getter) {
|
|
|
35
35
|
throw new Error(`MMKV: Type ${typeof newValue} is not supported!`);
|
|
36
36
|
}
|
|
37
37
|
}, [key, mmkv]);
|
|
38
|
-
// update value if it changes somewhere else (second hook, same key)
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
const listener = mmkv.addOnValueChangedListener((changedKey) => {
|
|
41
|
-
if (changedKey === key) {
|
|
42
|
-
setBump((b) => b + 1);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
return () => listener.remove();
|
|
46
|
-
}, [key, mmkv]);
|
|
47
38
|
return [value, set];
|
|
48
39
|
};
|
|
49
40
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HybridObject } from 'react-native-nitro-modules';
|
|
2
|
+
import type { EncryptionType } from './MMKVFactory.nitro';
|
|
2
3
|
export interface Listener {
|
|
3
4
|
remove: () => void;
|
|
4
5
|
}
|
|
@@ -10,15 +11,31 @@ export interface MMKV extends HybridObject<{
|
|
|
10
11
|
* Get the ID of this {@linkcode MMKV} instance.
|
|
11
12
|
*/
|
|
12
13
|
readonly id: string;
|
|
14
|
+
/**
|
|
15
|
+
* Get the current amount of key/value pairs stored in
|
|
16
|
+
* this storage.
|
|
17
|
+
*/
|
|
18
|
+
readonly length: number;
|
|
13
19
|
/**
|
|
14
20
|
* Get the current total size of the storage, in bytes.
|
|
21
|
+
* @deprecated Use {@linkcode byteSize} instead.
|
|
15
22
|
*/
|
|
16
23
|
readonly size: number;
|
|
17
24
|
/**
|
|
18
|
-
*
|
|
25
|
+
* Get the current total size of the storage, in bytes.
|
|
26
|
+
*/
|
|
27
|
+
readonly byteSize: number;
|
|
28
|
+
/**
|
|
29
|
+
* Get whether this instance is in read-only mode or not.
|
|
19
30
|
* If this is `true`, you can only use "get"-functions.
|
|
20
31
|
*/
|
|
21
32
|
readonly isReadOnly: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Get whether this instance is encrypted, or not.
|
|
35
|
+
* @see {@linkcode encrypt | encrypt(...)}
|
|
36
|
+
* @see {@linkcode decrypt | decrypt()}
|
|
37
|
+
*/
|
|
38
|
+
readonly isEncrypted: boolean;
|
|
22
39
|
/**
|
|
23
40
|
* Set a {@linkcode value} for the given {@linkcode key}.
|
|
24
41
|
*
|
|
@@ -77,8 +94,36 @@ export interface MMKV extends HybridObject<{
|
|
|
77
94
|
* Encryption keys can have a maximum length of 16 bytes.
|
|
78
95
|
*
|
|
79
96
|
* @throws an Error if the instance cannot be recrypted.
|
|
97
|
+
* @deprecated Use {@linkcode encrypt | encrypt(...)} or {@linkcode decrypt | decrypt()} instead.
|
|
80
98
|
*/
|
|
81
99
|
recrypt(key: string | undefined): void;
|
|
100
|
+
/**
|
|
101
|
+
* Encrypts the data in this MMKV instance with the
|
|
102
|
+
* given {@linkcode key} and {@linkcode encryptionType}.
|
|
103
|
+
*
|
|
104
|
+
* If this MMKV instance is already encrypted ({@linkcode isEncrypted}),
|
|
105
|
+
* it will re-encrypt the data.
|
|
106
|
+
*
|
|
107
|
+
* Future attempts to open this MMKV instance will require the same
|
|
108
|
+
* {@linkcode key} and {@linkcode encryptionType}, otherwise reads
|
|
109
|
+
* will fail.
|
|
110
|
+
*
|
|
111
|
+
* The {@linkcode key} must be 16-bytes in {@linkcode EncryptionType | 'AES-128'}
|
|
112
|
+
* encryption (the default), or 32-bytes in {@linkcode EncryptionType | 'AES-256'}
|
|
113
|
+
* encryption.
|
|
114
|
+
*
|
|
115
|
+
* @param key The encryption key to use. In AES-128 this must be 16-bytes, in AES-256 it must be 32-bytes long.
|
|
116
|
+
* @param encryptionType The encryption type to use. Default: AES-128
|
|
117
|
+
*/
|
|
118
|
+
encrypt(key: string, encryptionType?: EncryptionType): void;
|
|
119
|
+
/**
|
|
120
|
+
* Decrypts the data in this MMKV instance and removes
|
|
121
|
+
* the encryption key.
|
|
122
|
+
*
|
|
123
|
+
* Future attempts to open this MMKV instance must be done
|
|
124
|
+
* without an encryption key, as it is now plain-text.
|
|
125
|
+
*/
|
|
126
|
+
decrypt(): void;
|
|
82
127
|
/**
|
|
83
128
|
* Trims the storage space and clears memory cache.
|
|
84
129
|
*
|